1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 17:33:14 +01:00

Merge branch 'AD-419-decouple-piece-registration-from-' of bitbucket.org:ascribe/onion into AD-419-decouple-piece-registration-from-

Conflicts:
	js/components/ascribe_accordion_list/accordion_list_item_create_editions.js
This commit is contained in:
Tim Daubenschütz 2015-07-13 18:54:06 +02:00
commit 6e1cf55841
23 changed files with 655 additions and 514 deletions

View File

@ -18,7 +18,7 @@
</script>
</head>
<body>
<div id="main" class="container"></div>
<div id="main"></div>
<script src="<%= BASE_URL %>static/js/app.js"></script>
</body>
</html>

View File

@ -1,6 +1,6 @@
'use strict';
require("babel/polyfill");
require('babel/polyfill');
import React from 'react';
import Router from 'react-router';
@ -8,7 +8,8 @@ import Router from 'react-router';
import fetch from 'isomorphic-fetch';
import ApiUrls from './constants/api_urls';
import routes from './routes';
import constants from './constants/application_constants';
import getRoutes from './routes';
import requests from './utils/requests';
let headers = {
@ -28,9 +29,35 @@ requests.defaults({
}
});
Router.run(routes, Router.HistoryLocation, (AscribeApp) => {
React.render(
<AscribeApp />,
document.getElementById('main')
);
});
class AppGateway {
start() {
let subdomain = window.location.host.split('.')[0];
requests.get('whitelabel_settings', {'subdomain': subdomain})
.then(this.loadSubdomain.bind(this))
.catch(this.loadDefault.bind(this));
}
loadSubdomain(data) {
let settings = data.whitelabel;
constants.whitelabel = settings;
this.load('prize');
}
loadDefault() {
this.load('default');
}
load(type) {
Router.run(getRoutes(type), Router.HistoryLocation, (App) => {
React.render(
<App />,
document.getElementById('main')
);
});
}
}
let ag = new AppGateway();
ag.start();

View File

@ -6,14 +6,14 @@ import Header from '../components/header';
import Footer from '../components/footer';
import GlobalNotification from './global_notification';
let Link = Router.Link;
// let Link = Router.Link;
let RouteHandler = Router.RouteHandler;
let AscribeApp = React.createClass({
render() {
return (
<div>
<div className="container ascribe-default-app">
<Header />
<RouteHandler />
<Footer />

View File

@ -0,0 +1,123 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import UserStore from '../../stores/user_store';
import UserActions from '../../actions/user_actions';
import Form from './form';
import Property from './property';
import apiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils';
let LoginForm = React.createClass({
propTypes: {
redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool
},
mixins: [Router.Navigation],
getDefaultProps() {
return {
redirectOnLoggedIn: true,
redirectOnLoginSuccess: true
};
},
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
// if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email && this.props.redirectOnLoggedIn) {
this.transitionTo('pieces');
}
},
handleSuccess(){
let notification = new GlobalNotificationModel('Login successful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
// register_piece is waiting for a login success as login_container and it is wrapped
// in a slides_container component.
// The easiest way to check if the user was successfully logged in is to fetch the user
// in the user store (which is obviously only possible if the user is logged in), since
// register_piece is listening to the changes of the user_store.
UserActions.fetchCurrentUser();
/* Taken from http://stackoverflow.com/a/14916411 */
/*
We actually have to trick the Browser into showing the "save password" dialog
as Chrome expects the login page to be reloaded after the login.
Users on Stack Overflow claim this is a bug in chrome and should be fixed in the future.
Until then, we redirect the HARD way, but reloading the whole page using window.location
*/
if(this.props.redirectOnLoginSuccess) {
window.location = AppConstants.baseUrl + 'collection';
}
},
render() {
return (
<Form
ref="loginForm"
url={apiUrls.users_login}
handleSuccess={this.handleSuccess}
buttons={
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login">
{getLangText('Enter')} ascribe
</button>}
spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span>
}>
<Property
name='email'
label={getLangText('Email')}>
<input
type="email"
placeholder={getLangText('Enter your email')}
autoComplete="on"
name="username"
required/>
</Property>
<Property
name='password'
label={getLangText('Password')}>
<input
type="password"
placeholder={getLangText('Enter your password')}
autoComplete="on"
name="password"
required/>
</Property>
<hr />
</Form>
);
}
});
export default LoginForm;

View File

@ -0,0 +1,165 @@
'use strict';
import React from 'react';
import AppConstants from '../../constants/application_constants';
import Form from './form';
import Property from './property';
import FormPropertyHeader from './form_property_header';
import apiUrls from '../../constants/api_urls';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils';
let RegisterPieceForm = React.createClass({
getInitialState(){
return {
digitalWorkKey: null,
isUploadReady: false
};
},
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
});
},
isReadyForFormSubmission(files) {
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
if (files.length > 0 && files[0].status === 'upload successful') {
return true;
} else {
return false;
}
},
render() {
return (
<Form
className="ascribe-form-bordered"
ref='form'
url={apiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
buttons={<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}>
{getLangText('Register work')}
</button>}
spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span>
}>
<FormPropertyHeader>
<h3>{getLangText('Register your work')}</h3>
</FormPropertyHeader>
<Property
ignoreFocus={true}>
<FileUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.props.isFineUploaderEditable}/>
</Property>
<Property
name='artist_name'
label={getLangText('Artist Name')}>
<input
type="text"
placeholder="(e.g. Andy Warhol)"
required/>
</Property>
<Property
name='title'
label={getLangText('Title')}>
<input
type="text"
placeholder="(e.g. 32 Campbell's Soup Cans)"
required/>
</Property>
<Property
name='date_created'
label={getLangText('Year Created')}>
<input
type="number"
placeholder="(e.g. 1962)"
min={0}
required/>
</Property>
{this.props.children}
</Form>
);
}
});
let FileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// editable is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
editable: React.PropTypes.bool
},
render() {
return (
<ReactS3FineUploader
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: apiUrls.blob_digitalworks
}}
submitKey={this.props.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={this.props.editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}/>
);
}
});
export default RegisterPieceForm;

View File

@ -1,80 +1,132 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import { getLangText } from '../../utils/lang_utils';
import UserStore from '../../stores/user_store';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import Form from './form';
import Property from './property';
import FormPropertyHeader from './form_property_header';
import InputCheckbox from './input_checkbox';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import InputCheckbox from './input_checkbox';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close';
import { getLangText } from '../../utils/lang_utils.js'
let SignupForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_signup;
},
getFormData() {
return {
email: this.refs.email.state.value,
password: this.refs.password.state.value,
password_confirm: this.refs.password_confirm.state.value,
terms: this.refs.terms.state.value,
promo_code: this.refs.promo_code.state.value
};
propTypes: {
handleSuccess: React.PropTypes.func
},
renderForm() {
mixins: [Router.Navigation],
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
// if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email) {
this.transitionTo('pieces');
}
},
handleSuccess(response){
let notificationText = getLangText('Sign up successful');
let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email +
', ' + getLangText('please confirm') + '.');
},
getFormData(){
return {terms: this.refs.form.refs.terms.refs.input.state.value};
},
render() {
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
getLangText('Store it in a safe place') + '!';
return (
<form id="signup_modal_content" role="form" onSubmit={this.submit}>
<input className="invisible" type="email" name="fake_consignee"/>
<input className="invisible" type="password" name="fake_password"/>
<InputText
ref="email"
placeHolder={getLangText('Email')}
required="required"
type="email"
submitted={this.state.submitted}/>
<InputText
ref="password"
placeHolder={getLangText('Choose a password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<InputText
ref="password_confirm"
placeHolder={getLangText('Confirm password')}
required="required"
type="password"
submitted={this.state.submitted}/>
<div>
{getLangText('Your password must be at least 10 characters')}.
{getLangText('This password is securing your digital property like a bank account')}.
{getLangText('Store it in a safe place')}!
</div>
<InputCheckbox
ref="terms"
required="required"
label={
<div>
{getLangText('I agree to the')}&nbsp;
<a href="/terms" target="_blank"> {getLangText('Terms of Service')}</a>
</div>}/>
<InputText
ref="promo_code"
placeHolder={getLangText('Promocode (Optional)')}
required=""
type="text"
submitted={this.state.submitted}/>
<ButtonSubmitOrClose
text={getLangText('JOIN US')}
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
<Form
className="ascribe-form-bordered"
ref='form'
url={apiUrls.users_signup}
handleSuccess={this.handleSuccess}
getFormData={this.getFormData}
buttons={
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
{getLangText('Sign up to ascribe')}
</button>}
spinner={
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</span>
}>
<FormPropertyHeader>
<h3>{getLangText('Welcome to ascribe')}</h3>
</FormPropertyHeader>
<Property
name='email'
label={getLangText('Email')}>
<input
type="email"
placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
autoComplete="on"
required/>
</Property>
<Property
name='password'
label={getLangText('Password')}
tooltip={tooltipPassword}>
<input
type="password"
placeholder={getLangText('Use a combination of minimum 10 chars and numbers')}
autoComplete="on"
required/>
</Property>
<Property
name='password_confirm'
label={getLangText('Confirm Password')}
tooltip={tooltipPassword}>
<input
type="password"
placeholder={getLangText('Enter your password once again')}
autoComplete="on"
required/>
</Property>
<Property
name='promo_code'
label={getLangText('Promocode')}>
<input
type="text"
placeholder={getLangText('Enter a promocode here (Optional)')}/>
</Property>
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox/>
</Property>
</Form>
);
}
});
export default SignupForm;
export default SignupForm;

View File

@ -68,9 +68,9 @@ let CoaVerifyForm = React.createClass({
{getLangText('Verify your Certificate of Authenticity')}
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
</span>
}>
<Property
name='message'

View File

@ -3,23 +3,13 @@
import React from 'react';
import Router from 'react-router';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import UserStore from '../stores/user_store';
import UserActions from '../actions/user_actions';
import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property';
import FormPropertyHeader from './ascribe_forms/form_property_header';
import apiUrls from '../constants/api_urls';
import AppConstants from '../constants/application_constants';
import LoginForm from './ascribe_forms/form_login';
import { getLangText } from '../utils/lang_utils';
let Link = Router.Link;
let LoginContainer = React.createClass({
propTypes: {
message: React.PropTypes.string,
@ -27,8 +17,6 @@ let LoginContainer = React.createClass({
redirectOnLoginSuccess: React.PropTypes.bool
},
mixins: [Router.Navigation],
getDefaultProps() {
return {
message: getLangText('Enter') + ' ascribe',
@ -37,32 +25,13 @@ let LoginContainer = React.createClass({
};
},
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
// if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email && this.props.redirectOnLoggedIn) {
this.transitionTo('pieces');
}
},
render() {
return (
<div className="ascribe-login-wrapper">
<br/>
<LoginForm
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
message={this.props.message} />
<div className="ascribe-login-text">
{getLangText('Not an ascribe user')}&#63; <Link to="signup">{getLangText('Sign up')}...</Link><br/>
@ -74,80 +43,5 @@ let LoginContainer = React.createClass({
});
let LoginForm = React.createClass({
propTypes: {
redirectOnLoginSuccess: React.PropTypes.bool,
message: React.PropTypes.string
},
handleSuccess(){
let notification = new GlobalNotificationModel('Login successful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
// register_piece is waiting for a login success as login_container and it is wrapped
// in a slides_container component.
// The easiest way to check if the user was successfully logged in is to fetch the user
// in the user store (which is obviously only possible if the user is logged in), since
// register_piece is listening to the changes of the user_store.
UserActions.fetchCurrentUser();
/* Taken from http://stackoverflow.com/a/14916411 */
/*
We actually have to trick the Browser into showing the "save password" dialog
as Chrome expects the login page to be reloaded after the login.
Users on Stack Overflow claim this is a bug in chrome and should be fixed in the future.
Until then, we redirect the HARD way, but reloading the whole page using window.location
*/
if(this.props.redirectOnLoginSuccess) {
window.location = AppConstants.baseUrl + 'collection';
}
},
render() {
return (
<Form
className="ascribe-form-bordered"
ref="loginForm"
url={apiUrls.users_login}
handleSuccess={this.handleSuccess}
buttons={
<button
type="submit"
className="btn ascribe-btn ascribe-btn-login">
{getLangText('Enter')} ascribe
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
}>
<FormPropertyHeader>
<h3>{this.props.message}</h3>
</FormPropertyHeader>
<Property
name='email'
label={getLangText('Email')}>
<input
type="email"
placeholder={getLangText('Enter your email')}
autoComplete="on"
name="username"
required/>
</Property>
<Property
name='password'
label={getLangText('Password')}>
<input
type="password"
placeholder={getLangText('Enter your password')}
autoComplete="on"
name="password"
required/>
</Property>
</Form>
);
}
});
export default LoginContainer;

View File

@ -82,9 +82,9 @@ let PasswordRequestResetForm = React.createClass({
{getLangText('Reset your password')}
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
</span>
}>
<Property
name='email'
@ -129,9 +129,9 @@ let PasswordResetForm = React.createClass({
{getLangText('Reset your password')}
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
</span>
}>
<Property
name='password'

View File

@ -2,14 +2,10 @@
import React from 'react';
import DatePicker from 'react-datepicker/dist/react-datepicker';
import Router from 'react-router';
import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row';
import AppConstants from '../constants/application_constants';
import LicenseActions from '../actions/license_actions';
import LicenseStore from '../stores/license_store';
@ -21,17 +17,14 @@ import UserStore from '../stores/user_store';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property';
import PropertyCollapsible from './ascribe_forms/property_collapsible';
import FormPropertyHeader from './ascribe_forms/form_property_header';
import RegisterPieceForm from './ascribe_forms/form_register_piece';
//import FormPropertyHeader from './ascribe_forms/form_property_header';
import LoginContainer from './login_container';
import SlidesContainer from './ascribe_slides_container/slides_container';
import apiUrls from '../constants/api_urls';
import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader';
import { mergeOptions } from '../utils/general_utils';
import { getCookie } from '../utils/fetch_api_utils';
@ -46,8 +39,6 @@ let RegisterPiece = React.createClass( {
UserStore.getState(),
PieceListStore.getState(),
{
digitalWorkKey: null,
uploadStatus: false,
selectedLicense: 0,
isFineUploaderEditable: false
});
@ -97,32 +88,6 @@ let RegisterPiece = React.createClass( {
this.transitionTo('piece', {pieceId: response.piece.id});
},
getFormData(){
return {
digital_work_key: this.state.digitalWorkKey
};
},
submitKey(key){
this.setState({
digitalWorkKey: key
});
},
setIsUploadReady(isReady) {
this.setState({
isUploadReady: isReady
});
},
isReadyForFormSubmission(files) {
files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled');
if (files.length > 0 && files[0].status === 'upload successful') {
return true;
} else {
return false;
}
},
onLicenseChange(event){
//console.log(this.state.licenses[event.target.selectedIndex].url);
this.setState({selectedLicense: event.target.selectedIndex});
@ -170,59 +135,9 @@ let RegisterPiece = React.createClass( {
onFocus={this.changeSlide}>
<Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<Form
className="ascribe-form-bordered"
ref='form'
url={apiUrls.pieces_list}
getFormData={this.getFormData}
handleSuccess={this.handleSuccess}
buttons={<button
type="submit"
className="btn ascribe-btn ascribe-btn-login"
disabled={!this.state.isUploadReady}>
{getLangText('Register work')}
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
}>
<FormPropertyHeader>
<h3>{getLangText('Register your work')}</h3>
</FormPropertyHeader>
<Property
ignoreFocus={true}>
<FileUploader
submitKey={this.submitKey}
setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={this.isReadyForFormSubmission}
editable={this.state.isFineUploaderEditable}/>
</Property>
<Property
name='artist_name'
label={getLangText('Artist Name')}>
<input
type="text"
placeholder="(e.g. Andy Warhol)"
required/>
</Property>
<Property
name='title'
label={getLangText('Title')}>
<input
type="text"
placeholder="(e.g. 32 Campbell's Soup Cans)"
required/>
</Property>
<Property
name='date_created'
label={getLangText('Year Created')}>
<input
type="number"
placeholder="(e.g. 1962)"
min={0}
required/>
</Property>
<RegisterPieceForm
isFineUploaderEditable={this.state.isFineUploaderEditable}
handleSuccess={this.handleSuccess}>
<PropertyCollapsible
checkboxLabel={getLangText('Specify editions')}>
<span>{getLangText('Editions')}</span>
@ -232,7 +147,7 @@ let RegisterPiece = React.createClass( {
min={0}/>
</PropertyCollapsible>
{this.getLicenses()}
</Form>
</RegisterPieceForm>
</Col>
</Row>
</div>
@ -248,92 +163,4 @@ let RegisterPiece = React.createClass( {
});
let FileUploader = React.createClass({
propTypes: {
setIsUploadReady: React.PropTypes.func,
submitKey: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func,
onClick: React.PropTypes.func,
// editable is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
editable: React.PropTypes.bool
},
render() {
return (
<ReactS3FineUploader
onClick={this.props.onClick}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'digitalwork'
}}
createBlobRoutine={{
url: apiUrls.blob_digitalworks
}}
submitKey={this.props.submitKey}
validation={{
itemLimit: 100000,
sizeLimit: '25000000000'
}}
setIsUploadReady={this.props.setIsUploadReady}
isReadyForFormSubmission={this.props.isReadyForFormSubmission}
areAssetsDownloadable={false}
areAssetsEditable={this.props.editable}
signature={{
endpoint: AppConstants.serverUrl + 's3/signature/',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}
deleteFile={{
enabled: true,
method: 'DELETE',
endpoint: AppConstants.serverUrl + 's3/delete',
customHeaders: {
'X-CSRFToken': getCookie('csrftoken')
}
}}/>
);
}
});
let InputDate = React.createClass({
propTypes: {
placeholderText: React.PropTypes.string,
onChange: React.PropTypes.func
},
getInitialState() {
return {
value: null,
value_formatted: null
};
},
handleChange(date) {
this.setState({
value: date,
value_formatted: date.format('YYYY')});
let event = document.createEvent('HTMLEvents');
event.initEvent('click', false, true);
document.dispatchEvent(event);
event.target.value = date;
this.props.onChange(event);
},
render: function () {
return (
<DatePicker
key="example2"
dateFormat="YYYY"
selected={this.state.value}
onChange={this.handleChange}
onBlur={this.props.onBlur}
placeholderText={this.props.placeholderText}/>
);
}
});
export default RegisterPiece;

25
js/components/routes.js Normal file
View File

@ -0,0 +1,25 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import App from './ascribe_app';
import AppConstants from '../constants/application_constants';
let Route = Router.Route;
let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl;
function getRoutes(commonRoutes) {
return (
<Route name="app" path={baseUrl} handler={App}>
<Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />
{commonRoutes}
</Route>
);
}
export default getRoutes;

View File

@ -1,50 +1,16 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils';
import UserStore from '../stores/user_store';
import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions';
import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property';
import FormPropertyHeader from './ascribe_forms/form_property_header';
import InputCheckbox from './ascribe_forms/input_checkbox';
import apiUrls from '../constants/api_urls';
import SignupForm from './ascribe_forms/form_signup';
// import { getLangText } from '../utils/lang_utils';
let SignupContainer = React.createClass({
mixins: [Router.Navigation],
getInitialState() {
return mergeOptions({
return {
submitted: false,
message: null
}, UserStore.getState());
},
componentDidMount() {
UserStore.listen(this.onChange);
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
// if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email) {
this.transitionTo('pieces');
}
};
},
handleSuccess(message){
@ -75,93 +41,4 @@ let SignupContainer = React.createClass({
});
let SignupForm = React.createClass({
propTypes: {
handleSuccess: React.PropTypes.func
},
mixins: [Router.Navigation],
handleSuccess(response){
let notificationText = getLangText('Sign up successful');
let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification);
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email +
', ' + getLangText('please confirm') + '.');
},
getFormData(){
return {terms: this.refs.form.refs.terms.refs.input.state.value};
},
render() {
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
getLangText('Store it in a safe place') + '!';
return (
<Form
className="ascribe-form-bordered"
ref='form'
url={apiUrls.users_signup}
handleSuccess={this.handleSuccess}
getFormData={this.getFormData}
buttons={
<button type="submit" className="btn ascribe-btn ascribe-btn-login">
{getLangText('Sign up to ascribe')}
</button>}
spinner={
<button className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button>
}>
<FormPropertyHeader>
<h3>{getLangText('Welcome to ascribe')}</h3>
</FormPropertyHeader>
<Property
name='email'
label={getLangText('Email')}>
<input
type="email"
placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
autoComplete="on"
required/>
</Property>
<Property
name='password'
label={getLangText('Password')}
tooltip={tooltipPassword}>
<input
type="password"
placeholder={getLangText('Use a combination of minimum 10 chars and numbers')}
autoComplete="on"
required/>
</Property>
<Property
name='password_confirm'
label={getLangText('Confirm Password')}
tooltip={tooltipPassword}>
<input
type="password"
placeholder={getLangText('Enter your password once again')}
autoComplete="on"
required/>
</Property>
<Property
name='promo_code'
label={getLangText('Promocode')}>
<input
type="text"
placeholder={getLangText('Enter a promocode here (Optional)')}/>
</Property>
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox/>
</Property>
</Form>
);
}
});
export default SignupContainer;
export default SignupContainer;

View File

@ -0,0 +1,36 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import Hero from './components/hero';
import Header from '../../header';
// import Footer from '../../footer';
import GlobalNotification from '../../global_notification';
let RouteHandler = Router.RouteHandler;
let PrizeApp = React.createClass({
mixins: [Router.State],
render() {
let header = null;
if (this.isActive('pieces')) {
header = (
<Header />
);
}
return (
<div className="whitelabel-prize">
<Hero />
{header}
<RouteHandler />
<GlobalNotification />
<div id="modal" className="container"></div>
</div>
);
}
});
export default PrizeApp;

View File

@ -0,0 +1,18 @@
'use strict';
import React from 'react';
import constants from '../../../../constants/application_constants';
let Hero = React.createClass({
render() {
return (
<div className="hero">
<img className="logo" src={constants.whitelabel.logo} alt="Sluice Art Prize" />
<h1>Sluice Art Prize 2015</h1>
</div>
);
}
});
export default Hero;

View File

@ -0,0 +1,29 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
let Link = Router.Link;
let Landing = React.createClass({
render() {
return (
<div>
<div className="container">
<ButtonGroup className="enter" bsSize="large" vertical block>
<ButtonLink to="signup">
Signup to the prize
</ButtonLink>
Already a user? <Link to="login">log in</Link>
</ButtonGroup>
</div>
</div>
);
}
});
export default Landing;

View File

@ -0,0 +1,26 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import Landing from './components/landing';
import App from './app';
import AppConstants from '../../../constants/application_constants';
let Route = Router.Route;
let baseUrl = AppConstants.baseUrl;
function getRoutes(commonRoutes) {
return (
<Route name="app" path={baseUrl} handler={App}>
<Route name="landing" path="/" handler={Landing} />
{commonRoutes}
</Route>
);
}
export default getRoutes;

View File

@ -10,7 +10,10 @@ let constants = {
'serverUrl': window.SERVER_URL,
'baseUrl': window.BASE_URL,
'aclList': ['edit', 'consign', 'consign_request', 'unconsign', 'unconsign_request', 'transfer',
'loan', 'loan_request', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection']
'loan', 'loan_request', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection'],
// in case of whitelabel cusomization, we store stuff here
'whitelabel': {}
};
export default constants;

View File

@ -3,7 +3,9 @@
import React from 'react';
import Router from 'react-router';
import AscribeApp from './components/ascribe_app';
import getPrizeRoutes from './components/whitelabel/prize/routes';
import getDefaultRoutes from './components/routes';
import PieceList from './components/piece_list';
import PieceContainer from './components/ascribe_detail/piece_container';
import EditionContainer from './components/ascribe_detail/edition_container';
@ -15,15 +17,13 @@ import PasswordResetContainer from './components/password_reset_container';
import SettingsContainer from './components/settings_container';
import CoaVerifyContainer from './components/coa_verify_container';
import AppConstants from './constants/application_constants';
import RegisterPiece from './components/register_piece';
let Route = Router.Route;
let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl;
let routes = (
<Route name="app" path={baseUrl} handler={AscribeApp}>
const COMMON_ROUTES = (
<Route>
<Route name="signup" path="signup" handler={SignupContainer} />
<Route name="login" path="login" handler={LoginContainer} />
<Route name="pieces" path="collection" handler={PieceList} />
@ -33,10 +33,20 @@ let routes = (
<Route name="register_piece" path="register_piece" handler={RegisterPiece} />
<Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />
</Route>
);
export default routes;
function getRoutes(type) {
let routes = null;
if (type === 'prize') {
routes = getPrizeRoutes(COMMON_ROUTES);
} else {
routes = getDefaultRoutes(COMMON_ROUTES);
}
return routes;
}
export default getRoutes;

5
sass/ascribe_app.scss Normal file
View File

@ -0,0 +1,5 @@
.ascribe-default-app {
background-color: #FDFDFD;
border-radius: 0;
padding-top: 70px;
}

View File

@ -11,6 +11,7 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'ascribe_theme';
@import './ascribe-fonts/style';
@import './ascribe-fonts/ascribe-fonts';
@import 'ascribe_app';
@import 'ascribe_login';
@import 'ascribe_table';
@import 'ascribe_accordion_list';
@ -28,10 +29,11 @@ $BASE_URL: '<%= BASE_URL %>';
@import 'ascribe_slides_container';
@import 'ascribe_form';
body {
background-color: #FDFDFD;
border-radius: 0;
margin-top: 70px;
@import 'whitelabel/index';
html, body {
height: 100%;
}
html {
@ -42,6 +44,10 @@ hr {
margin-bottom: 15px;
}
#main {
height: 100%;
}
.hidden {
display: none;
}

View File

@ -0,0 +1 @@
@import 'prize/index';

View File

@ -0,0 +1 @@
@import 'landing'

View File

@ -0,0 +1,16 @@
.whitelabel-prize {
.hero {
overflow: hidden;
.logo {
float: left;
padding-right: 2em;
}
}
.enter {
}
}