1
0
mirror of https://github.com/ascribe/onion.git synced 2025-02-14 21:10:27 +01:00

Merge branch 'AD-565-add-landing-page-for-sluice'

This commit is contained in:
vrde 2015-07-13 23:58:15 +02:00
commit 3b1fdba80f
55 changed files with 1372 additions and 1081 deletions

View File

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

View File

@ -7,7 +7,8 @@ import PieceFetcher from '../fetchers/piece_fetcher';
class PieceActions { class PieceActions {
constructor() { constructor() {
this.generateActions( this.generateActions(
'updatePiece' 'updatePiece',
'updateProperty'
); );
} }

View File

@ -14,6 +14,21 @@ class PieceListActions {
} }
fetchPieceList(page, pageSize, search, orderBy, orderAsc) { fetchPieceList(page, pageSize, search, orderBy, orderAsc) {
// To prevent flickering on a pagination request,
// we overwrite the piecelist with an empty list before
// pieceListCount === -1 defines the loading state
this.actions.updatePieceList({
page,
pageSize,
search,
orderBy,
orderAsc,
'pieceList': [],
'pieceListCount': -1
});
// afterwards, we can load the list
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
PieceListFetcher PieceListFetcher
.fetch(page, pageSize, search, orderBy, orderAsc) .fetch(page, pageSize, search, orderBy, orderAsc)

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require("babel/polyfill"); require('babel/polyfill');
import React from 'react'; import React from 'react';
import Router from 'react-router'; import Router from 'react-router';
@ -8,7 +8,8 @@ import Router from 'react-router';
import fetch from 'isomorphic-fetch'; import fetch from 'isomorphic-fetch';
import ApiUrls from './constants/api_urls'; 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'; import requests from './utils/requests';
let headers = { let headers = {
@ -28,9 +29,38 @@ requests.defaults({
} }
}); });
Router.run(routes, Router.HistoryLocation, (AscribeApp) => {
React.render( class AppGateway {
<AscribeApp />,
document.getElementById('main') start() {
); console.log('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(error) {
console.log('Loading default app, error'. error);
this.load('default');
}
load(type) {
console.log('loading', type);
Router.run(getRoutes(type), Router.HistoryLocation, (App) => {
React.render(
<App />,
document.getElementById('main')
);
});
}
}
let ag = new AppGateway();
ag.start();

View File

@ -22,7 +22,7 @@ let AccordionList = React.createClass({
} else if(this.props.count === 0) { } else if(this.props.count === 0) {
return ( return (
<div> <div>
<p className="text-center">{getLangText('We could not find any works related to you%s', '...')}</p> <p className="text-center">{getLangText('We could not find any works related to you...')}</p>
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p> <p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
</div> </div>
); );

View File

@ -8,14 +8,11 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
import Tooltip from 'react-bootstrap/lib/Tooltip'; import Tooltip from 'react-bootstrap/lib/Tooltip';
import AccordionListItemEditionWidget from './accordion_list_item_edition_widget'; import AccordionListItemEditionWidget from './accordion_list_item_edition_widget';
import AccordionListItemCreateEditions from './accordion_list_item_create_editions'; import CreateEditionsForm from '../ascribe_forms/create_editions_form';
import PieceListActions from '../../actions/piece_list_actions'; import PieceListActions from '../../actions/piece_list_actions';
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link; let Link = Router.Link;
@ -35,20 +32,6 @@ let AccordionListItem = React.createClass({
}; };
}, },
componentDidUpdate() {
if(this.props.content.num_editions === 0 && typeof this.state.pollingIntervalIndex === 'undefined') {
this.startPolling();
}
},
componentWillUnmount() {
clearInterval(this.state.pollingIntervalIndex);
},
onChange(state) {
this.setState(state);
},
getGlyphicon(){ getGlyphicon(){
if (this.props.content.requestAction){ if (this.props.content.requestAction){
return ( return (
@ -66,39 +49,32 @@ let AccordionListItem = React.createClass({
}); });
}, },
handleEditionCreationSuccess(response) { handleEditionCreationSuccess() {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0}); PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0});
this.toggleCreateEditionsDialog(); this.toggleCreateEditionsDialog();
}, },
startPolling() { onPollingSuccess(pieceId, numEditions) {
// start polling until editions are defined PieceListActions.updatePropertyForPiece({
let pollingIntervalIndex = setInterval(() => { pieceId,
EditionListActions.fetchEditionList(this.props.content.id) key: 'num_editions',
.then((res) => { value: numEditions
clearInterval(this.state.pollingIntervalIndex);
PieceListActions.updatePropertyForPiece({
pieceId: this.props.content.id,
key: 'num_editions',
value: res.editions[0].num_editions
});
EditionListActions.toggleEditionList(this.props.content.id);
})
.catch(() => {
/* Ignore and keep going */
});
}, 5000);
this.setState({
pollingIntervalIndex
}); });
EditionListActions.toggleEditionList(pieceId);
},
getCreateEditionsDialog() {
if(this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog) {
return (
<div className="ascribe-accordion-list-item-table col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<CreateEditionsForm
pieceId={this.props.content.id}
handleSuccess={this.handleEditionCreationSuccess} />
</div>
);
}
}, },
render() { render() {
@ -141,7 +117,8 @@ let AccordionListItem = React.createClass({
<AccordionListItemEditionWidget <AccordionListItemEditionWidget
className="pull-right" className="pull-right"
piece={this.props.content} piece={this.props.content}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}/> toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.onPollingSuccess}/>
</div> </div>
</div> </div>
<span style={{'clear': 'both'}}></span> <span style={{'clear': 'both'}}></span>
@ -150,8 +127,9 @@ let AccordionListItem = React.createClass({
</div> </div>
</div> </div>
</div> </div>
{this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog ? <AccordionListItemCreateEditions pieceId={this.props.content.id} handleSuccess={this.handleEditionCreationSuccess}/> : null}
{this.getCreateEditionsDialog()}
{/* this.props.children is AccordionListItemTableEditions */} {/* this.props.children is AccordionListItemTableEditions */}
{this.props.children} {this.props.children}
</div> </div>

View File

@ -1,51 +0,0 @@
'use strict';
import React from 'react';
import Form from '../ascribe_forms/form';
import Property from '../ascribe_forms/property';
import apiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils';
let AccordionListItemCreateEditions = React.createClass({
propTypes: {
pieceId: React.PropTypes.number,
handleSuccess: React.PropTypes.func
},
getFormData(){
return {
piece_id: parseInt(this.props.pieceId, 10)
};
},
render() {
return (
<div className="ascribe-accordion-list-item-table col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<Form
ref='form'
url={apiUrls.editions}
getFormData={this.getFormData}
handleSuccess={this.props.handleSuccess}
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>
}>
<Property
name='num_editions'
label={getLangText('Number of editions')}>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</Property>
</Form>
</div>
);
}
});
export default AccordionListItemCreateEditions;

View File

@ -6,13 +6,16 @@ import classNames from 'classnames';
import EditionListActions from '../../actions/edition_list_actions'; import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store'; import EditionListStore from '../../stores/edition_list_store';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let AccordionListItemEditionWidget = React.createClass({ let AccordionListItemEditionWidget = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
onPollingSuccess: React.PropTypes.func
}, },
getInitialState() { getInitialState() {
@ -55,16 +58,9 @@ let AccordionListItemEditionWidget = React.createClass({
let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false; let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false;
if(isEditionListOpen) { if(isEditionListOpen) {
if(typeof this.state.editionList[pieceId] === 'undefined') { return (
return ( <span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span>
<span className="glyph-ascribe-spool-chunked spin"/> );
);
} else {
return (
<span className="glyphicon glyphicon-menu-up" aria-hidden="true" style={{top: 2}}></span>
);
}
} else { } else {
return ( return (
<span className="glyphicon glyphicon-menu-down" aria-hidden="true" style={{top: 2}}></span> <span className="glyphicon glyphicon-menu-down" aria-hidden="true" style={{top: 2}}></span>
@ -76,23 +72,16 @@ let AccordionListItemEditionWidget = React.createClass({
let piece = this.props.piece; let piece = this.props.piece;
let numEditions = piece.num_editions; let numEditions = piece.num_editions;
if(numEditions === -1) { if(numEditions <= 0) {
return ( return (
<button <CreateEditionsButton
onClick={this.props.toggleCreateEditionsDialog} label={getLangText('Create editions')}
className={classNames('btn', 'btn-default', 'btn-xs', 'ascribe-accordion-list-item-edition-widget', this.props.className)}> className="btn-xs pull-right"
+ Editions piece={piece}
</button> toggleCreateEditionsDialog={this.props.toggleCreateEditionsDialog}
onPollingSuccess={this.props.onPollingSuccess}/>
); );
} } else if(numEditions === 1) {
else if(numEditions === 0) {
return (
<button disabled className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
Creating Editions <span className="glyph-ascribe-spool-chunked spin"/>
</button>
);
}
else if(numEditions === 1) {
let editionMapping = piece && piece.first_edition ? piece.first_edition.edition_number + '/' + piece.num_editions : ''; let editionMapping = piece && piece.first_edition ? piece.first_edition.edition_number + '/' + piece.num_editions : '';
return ( return (

View File

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

View File

@ -13,25 +13,28 @@ import AppConstants from '../../constants/application_constants';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js' import { getLangText } from '../../utils/lang_utils.js';
import apiUrls from '../../constants/api_urls';
let AclButton = React.createClass({ let AclButton = React.createClass({
propTypes: { propTypes: {
action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
availableAcls: React.PropTypes.array.isRequired, availableAcls: React.PropTypes.array.isRequired,
editions: React.PropTypes.array.isRequired, pieceOrEditions: React.PropTypes.object.isRequired,
currentUser: React.PropTypes.object, currentUser: React.PropTypes.object,
handleSuccess: React.PropTypes.func.isRequired, handleSuccess: React.PropTypes.func.isRequired,
className: React.PropTypes.string className: React.PropTypes.string
}, },
isPiece(){
return !(this.props.pieceOrEditions.constructor === Array);
},
actionProperties(){ actionProperties(){
if (this.props.action === 'consign'){ if (this.props.action === 'consign'){
return { return {
title: getLangText('Consign artwork'), title: getLangText('Consign artwork'),
tooltip: getLangText('Have someone else sell the artwork'), tooltip: getLangText('Have someone else sell the artwork'),
form: <ConsignForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <ConsignForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
@ -39,14 +42,14 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Unconsign artwork'), title: getLangText('Unconsign artwork'),
tooltip: getLangText('Have the owner manage his sales again'), tooltip: getLangText('Have the owner manage his sales again'),
form: <UnConsignForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <UnConsignForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
}else if (this.props.action === 'transfer') { }else if (this.props.action === 'transfer') {
return { return {
title: getLangText('Transfer artwork'), title: getLangText('Transfer artwork'),
tooltip: getLangText('Transfer the ownership of the artwork'), tooltip: getLangText('Transfer the ownership of the artwork'),
form: <TransferForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <TransferForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
@ -54,7 +57,7 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Loan artwork'), title: getLangText('Loan artwork'),
tooltip: getLangText('Loan your artwork for a limited period of time'), tooltip: getLangText('Loan your artwork for a limited period of time'),
form: <LoanForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: <LoanForm currentUser={ this.props.currentUser } editions={ this.props.pieceOrEditions }/>,
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
@ -62,18 +65,62 @@ let AclButton = React.createClass({
return { return {
title: getLangText('Share artwork'), title: getLangText('Share artwork'),
tooltip: getLangText('Share the artwork'), tooltip: getLangText('Share the artwork'),
form: <ShareForm currentUser={ this.props.currentUser } editions={ this.props.editions }/>, form: (
<ShareForm
message={this.getShareMessage()}
id={this.getFormDataId()}
url={this.isPiece() ? apiUrls.ownership_shares_pieces : apiUrls.ownership_shares_editions }/>
),
handleSuccess: this.showNotification handleSuccess: this.showNotification
}; };
} }
}, },
showNotification(response){ showNotification(response){
this.props.handleSuccess(); this.props.handleSuccess();
let notification = new GlobalNotificationModel(response.notification, 'success'); let notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
getTitlesString(){
if (this.isPiece()){
return '\"' + this.props.pieceOrEditions.title + '\"';
}
else {
return this.props.pieceOrEditions.map(function(edition) {
return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
}).join('');
}
},
getFormDataId(){
if (this.isPiece()) {
return {piece_id: this.props.pieceOrEditions.id};
}
else {
return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){
return edition.bitcoin_id;
}).join()};
}
},
getShareMessage(){
return (
`
${getLangText('Hi')},
${getLangText('I am sharing')}:
${this.getTitlesString()} ${getLangText('with you')}.
${getLangText('Truly yours')},
${this.props.currentUser.username}
`
);
},
render() { render() {
let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1; let shouldDisplay = this.props.availableAcls[this.props.action];
let aclProps = this.actionProperties(); let aclProps = this.actionProperties();
return ( return (
<ModalWrapper <ModalWrapper

View File

@ -11,9 +11,16 @@ import DeleteButton from '../ascribe_buttons/delete_button';
let AclButtonList = React.createClass({ let AclButtonList = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
editions: React.PropTypes.array, editions: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
availableAcls: React.PropTypes.array, availableAcls: React.PropTypes.array,
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
}, },
getInitialState() { getInitialState() {
@ -39,34 +46,36 @@ let AclButtonList = React.createClass({
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="transfer" action="transfer"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess}/> handleSuccess={this.props.handleSuccess}/>
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="consign" action="consign"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="unconsign" action="unconsign"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="loan" action="loan"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<AclButton <AclButton
availableAcls={this.props.availableAcls} availableAcls={this.props.availableAcls}
action="share" action="share"
editions={this.props.editions} pieceOrEditions={this.props.editions}
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.handleSuccess} /> handleSuccess={this.props.handleSuccess} />
<DeleteButton editions={this.props.editions}/> <DeleteButton
editions={this.props.editions}/>
{this.props.children}
</div> </div>
); );
} }

View File

@ -0,0 +1,95 @@
'use strict';
import React from 'react';
import EditionListActions from '../../actions/edition_list_actions';
import EditionListStore from '../../stores/edition_list_store';
import { getAvailableAcls } from '../../utils/acl_utils';
import { getLangText } from '../../utils/lang_utils';
import classNames from 'classnames';
let CreateEditionsButton = React.createClass({
propTypes: {
label: React.PropTypes.string,
className: React.PropTypes.string,
piece: React.PropTypes.object.isRequired,
toggleCreateEditionsDialog: React.PropTypes.func.isRequired,
onPollingSuccess: React.PropTypes.func
},
getInitialState() {
return EditionListStore.getState();
},
componentDidMount() {
EditionListStore.listen(this.onChange);
},
componentWillUnmount() {
EditionListStore.unlisten(this.onChange);
clearInterval(this.state.pollingIntervalIndex);
},
onChange(state) {
this.setState(state);
},
componentDidUpdate() {
if(this.props.piece.num_editions === 0 && typeof this.state.pollingIntervalIndex === 'undefined') {
this.startPolling();
}
},
startPolling() {
// start polling until editions are defined
let pollingIntervalIndex = setInterval(() => {
EditionListActions.fetchEditionList(this.props.piece.id)
.then((res) => {
clearInterval(this.state.pollingIntervalIndex);
this.props.onPollingSuccess(this.props.piece.id, res.editions[0].num_editions);
})
.catch(() => {
/* Ignore and keep going */
});
}, 5000);
this.setState({
pollingIntervalIndex
});
},
render: function () {
let piece = this.props.piece;
let availableAcls = getAvailableAcls(piece);
if (availableAcls.editions || piece.num_editions > 0){
return null;
}
if(piece.num_editions === 0 && typeof this.state.editionList[piece.id] === 'undefined') {
return (
<button
disabled
className={classNames('btn', 'btn-default', this.props.className)}>
{getLangText('Creating editions')} <span className="glyph-ascribe-spool-chunked spin"/>
</button>
);
} else {
return (
<button
className={classNames('btn', 'btn-default', this.props.className)}
onClick={this.props.toggleCreateEditionsDialog}>
{this.props.label}
</button>
);
}
}
});
export default CreateEditionsButton;

View File

@ -41,11 +41,11 @@ let DeleteButton = React.createClass({
let btnDelete = null; let btnDelete = null;
let content = null; let content = null;
if (availableAcls.indexOf('delete') > -1) { if (availableAcls.delete) {
content = <EditionDeleteForm editions={ this.props.editions }/>; content = <EditionDeleteForm editions={ this.props.editions }/>;
btnDelete = <Button bsStyle="danger" className="btn-delete" bsSize="small">{getLangText('DELETE')}</Button>; btnDelete = <Button bsStyle="danger" className="btn-delete" bsSize="small">{getLangText('DELETE')}</Button>;
} }
else if (availableAcls.indexOf('del_from_collection') > -1){ else if (availableAcls.unshare){
content = <EditionRemoveFromCollectionForm editions={ this.props.editions }/>; content = <EditionRemoveFromCollectionForm editions={ this.props.editions }/>;
btnDelete = <Button bsStyle="danger" className="btn-delete" bsSize="small">{getLangText('REMOVE FROM COLLECTION')}</Button>; btnDelete = <Button bsStyle="danger" className="btn-delete" bsSize="small">{getLangText('REMOVE FROM COLLECTION')}</Button>;
} }

View File

@ -5,7 +5,6 @@ import Router from 'react-router';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import Button from 'react-bootstrap/lib/Button';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import UserActions from '../../actions/user_actions'; import UserActions from '../../actions/user_actions';
@ -22,7 +21,6 @@ import Property from './../ascribe_forms/property';
import EditionDetailProperty from './detail_property'; import EditionDetailProperty from './detail_property';
import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable'; import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable';
import EditionHeader from './header';
import EditionFurtherDetails from './further_details'; import EditionFurtherDetails from './further_details';
//import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata'; //import PieceExtraDataForm from './../ascribe_forms/form_piece_extradata';
@ -38,7 +36,6 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
import apiUrls from '../../constants/api_urls'; import apiUrls from '../../constants/api_urls';
import AppConstants from '../../constants/application_constants'; import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link; let Link = Router.Link;
@ -76,14 +73,19 @@ let Edition = React.createClass({
content={this.props.edition}/> content={this.props.edition}/>
</Col> </Col>
<Col md={6} className="ascribe-edition-details"> <Col md={6} className="ascribe-edition-details">
<EditionHeader content={this.props.edition}/> <div className="ascribe-detail-header">
<EditionDetailProperty label="TITLE" value={<div className="ascribe-detail-title">{this.props.edition.title}</div>} />
<EditionDetailProperty label="BY" value={this.props.edition.artist_name} />
<EditionDetailProperty label="DATE" value={ this.props.edition.date_created.slice(0, 4) } />
<hr/>
</div>
<EditionSummary <EditionSummary
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
edition={this.props.edition} /> edition={this.props.edition} />
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('Certificate of Authenticity')} title={getLangText('Certificate of Authenticity')}
show={this.props.edition.acl.indexOf('coa') > -1}> show={this.props.edition.acl.acl_coa}>
<CoaDetails <CoaDetails
edition={this.props.edition}/> edition={this.props.edition}/>
</CollapsibleParagraph> </CollapsibleParagraph>
@ -112,7 +114,7 @@ let Edition = React.createClass({
<CollapsibleParagraph <CollapsibleParagraph
title="Notes" title="Notes"
show={(this.state.currentUser.username && true || false) || show={(this.state.currentUser.username && true || false) ||
(this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note)}> (this.props.edition.acl.acl_edit || this.props.edition.public_note)}>
<EditionPersonalNote <EditionPersonalNote
currentUser={this.state.currentUser} currentUser={this.state.currentUser}
handleSuccess={this.props.loadEdition} handleSuccess={this.props.loadEdition}
@ -124,11 +126,11 @@ let Edition = React.createClass({
<CollapsibleParagraph <CollapsibleParagraph
title={getLangText('Further Details')} title={getLangText('Further Details')}
show={this.props.edition.acl.indexOf('edit') > -1 show={this.props.edition.acl.acl_edit
|| Object.keys(this.props.edition.extra_data).length > 0 || Object.keys(this.props.edition.extra_data).length > 0
|| this.props.edition.other_data !== null}> || this.props.edition.other_data !== null}>
<EditionFurtherDetails <EditionFurtherDetails
editable={this.props.edition.acl.indexOf('edit') > -1} editable={this.props.edition.acl.acl_edit}
pieceId={this.props.edition.parent} pieceId={this.props.edition.parent}
extraData={this.props.edition.extra_data} extraData={this.props.edition.extra_data}
otherData={this.props.edition.other_data} otherData={this.props.edition.other_data}
@ -168,7 +170,7 @@ let EditionSummary = React.createClass({
if (this.props.edition.status.length > 0){ if (this.props.edition.status.length > 0){
let statusStr = this.props.edition.status.join().replace(/_/, ' '); let statusStr = this.props.edition.status.join().replace(/_/, ' ');
status = <EditionDetailProperty label="STATUS" value={ statusStr }/>; status = <EditionDetailProperty label="STATUS" value={ statusStr }/>;
if (this.props.edition.pending_new_owner && this.props.edition.acl.indexOf('withdraw_transfer') > -1){ if (this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer){
status = ( status = (
<Form <Form
url={apiUrls.ownership_transfers_withdraw} url={apiUrls.ownership_transfers_withdraw}
@ -217,7 +219,6 @@ let EditionSummary = React.createClass({
<EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } /> <EditionDetailProperty label={getLangText('ID')} value={ this.props.edition.bitcoin_id } />
<EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } /> <EditionDetailProperty label={getLangText('OWNER')} value={ this.props.edition.owner } />
{this.getStatus()} {this.getStatus()}
<br/>
{this.getActions()} {this.getActions()}
<hr/> <hr/>
</div> </div>
@ -303,8 +304,8 @@ let EditionPublicEditionNote = React.createClass({
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },
render() { render() {
let isEditable = this.props.edition.acl.indexOf('edit') > -1; let isEditable = this.props.edition.acl.acl_edit;
if (this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note){ if (isEditable || this.props.edition.public_note){
return ( return (
<Form <Form
url={apiUrls.note_edition} url={apiUrls.note_edition}

View File

@ -1,26 +0,0 @@
'use strict';
import React from 'react';
import EditionDetailProperty from './detail_property';
let Header = React.createClass({
propTypes: {
content: React.PropTypes.object
},
render() {
var titleHtml = <div className="ascribe-detail-title">{this.props.content.title}</div>;
return (
<div className="ascribe-detail-header">
<EditionDetailProperty label="TITLE" value={titleHtml} />
<EditionDetailProperty label="BY" value={this.props.content.artist_name} />
<EditionDetailProperty label="DATE" value={ this.props.content.date_created.slice(0, 4) } />
<hr/>
</div>
);
}
});
export default Header;

View File

@ -7,13 +7,25 @@ import Col from 'react-bootstrap/lib/Col';
import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph';
import DetailProperty from './detail_property';
import FurtherDetails from './further_details'; import FurtherDetails from './further_details';
//import UserActions from '../../actions/user_actions';
//import UserStore from '../../stores/user_store'; import UserActions from '../../actions/user_actions';
import UserStore from '../../stores/user_store';
import PieceActions from '../../actions/piece_actions';
import MediaContainer from './media_container'; import MediaContainer from './media_container';
import Header from './header'; import EditionDetailProperty from './detail_property';
import AclButtonList from './../ascribe_buttons/acl_button_list';
import CreateEditionsForm from '../ascribe_forms/create_editions_form';
import CreateEditionsButton from '../ascribe_buttons/create_editions_button';
import { getLangText } from '../../utils/lang_utils';
import { mergeOptions } from '../../utils/general_utils';
/** /**
* This is the component that implements display-specific functionality * This is the component that implements display-specific functionality
@ -24,25 +36,62 @@ let Piece = React.createClass({
loadPiece: React.PropTypes.func loadPiece: React.PropTypes.func
}, },
//getInitialState() { getInitialState() {
// return UserStore.getState(); return mergeOptions(
//}, UserStore.getState(),
// {
//componentDidMount() { showCreateEditionsDialog: false
// UserStore.listen(this.onChange); }
// UserActions.fetchCurrentUser(); );
//}, },
//
//componentWillUnmount() { componentDidMount() {
// UserStore.unlisten(this.onChange); UserStore.listen(this.onChange);
//}, UserActions.fetchCurrentUser();
// },
//onChange(state) {
// this.setState(state); componentWillUnmount() {
//}, UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
},
toggleCreateEditionsDialog() {
this.setState({
showCreateEditionsDialog: !this.state.showCreateEditionsDialog
});
},
handleEditionCreationSuccess() {
PieceActions.updateProperty({key: 'num_editions', value: 0});
this.toggleCreateEditionsDialog();
},
getCreateEditionsDialog() {
if(this.props.piece.num_editions < 1 && this.state.showCreateEditionsDialog) {
return (
<div style={{marginTop: '1em'}}>
<CreateEditionsForm
pieceId={this.props.piece.id}
handleSuccess={this.handleEditionCreationSuccess} />
<hr/>
</div>
);
} else {
return (<hr/>);
}
},
handlePollingSuccess(pieceId, numEditions) {
PieceActions.updateProperty({
key: 'num_editions',
value: numEditions
});
},
render() { render() {
return ( return (
<Row> <Row>
<Col md={6}> <Col md={6}>
@ -50,22 +99,44 @@ let Piece = React.createClass({
content={this.props.piece}/> content={this.props.piece}/>
</Col> </Col>
<Col md={6} className="ascribe-edition-details"> <Col md={6} className="ascribe-edition-details">
<Header <div className="ascribe-detail-header">
content={this.props.piece}/> <EditionDetailProperty label="TITLE" value={<div className="ascribe-detail-title">{this.props.piece.title}</div>} />
<EditionDetailProperty label="BY" value={this.props.piece.artist_name} />
<EditionDetailProperty label="DATE" value={ this.props.piece.date_created.slice(0, 4) } />
{this.props.piece.num_editions > 0 ? <EditionDetailProperty label="NUMBER OF EDITIONS" value={ this.props.piece.num_editions } /> : null}
<hr/>
</div>
<div className="ascribe-detail-header">
<DetailProperty label={getLangText('REGISTREE')} value={ this.props.piece.user_registered } />
</div>
<AclButtonList
className="text-center ascribe-button-list"
availableAcls={this.props.piece.acl}
editions={this.props.piece}>
<CreateEditionsButton
label={getLangText('CREATE EDITIONS')}
className="btn-sm"
piece={this.props.piece}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.handlePollingSuccess}/>
</AclButtonList>
{this.getCreateEditionsDialog()}
<CollapsibleParagraph <CollapsibleParagraph
title="Further Details" title="Further Details"
show={this.props.piece.acl.indexOf('edit') > -1 show={this.props.piece.acl.acl_edit
|| Object.keys(this.props.piece.extra_data).length > 0 || Object.keys(this.props.piece.extra_data).length > 0
|| this.props.piece.other_data !== null} || this.props.piece.other_data !== null}
defaultExpanded={true}> defaultExpanded={true}>
<FurtherDetails <FurtherDetails
editable={this.props.piece.acl.indexOf('edit') > -1} editable={this.props.piece.acl.acl_edit}
pieceId={this.props.piece.id} pieceId={this.props.piece.id}
extraData={this.props.piece.extra_data} extraData={this.props.piece.extra_data}
otherData={this.props.piece.other_data} otherData={this.props.piece.other_data}
handleSuccess={this.props.loadPiece}/> handleSuccess={this.props.loadPiece}/>
</CollapsibleParagraph> </CollapsibleParagraph>
</Col> </Col>
</Row> </Row>
); );

View File

@ -0,0 +1,62 @@
'use strict';
import React from 'react';
import Form from '../ascribe_forms/form';
import Property from '../ascribe_forms/property';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import apiUrls from '../../constants/api_urls';
import { getLangText } from '../../utils/lang_utils';
let CreateEditionsForm = React.createClass({
propTypes: {
handleSuccess: React.PropTypes.func,
pieceId: React.PropTypes.number
},
getFormData(){
return {
piece_id: parseInt(this.props.pieceId, 10)
};
},
handleSuccess(response) {
let notification = new GlobalNotificationModel(response.notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification);
if(this.props.handleSuccess) {
this.props.handleSuccess(response);
}
},
render() {
return (
<Form
ref='form'
url={apiUrls.editions}
getFormData={this.getFormData}
handleSuccess={this.handleSuccess}
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>
}>
<Property
name='num_editions'
label={getLangText('Number of editions')}>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</Property>
</Form>
);
}
});
export default CreateEditionsForm;

View File

@ -1,62 +1,130 @@
'use strict'; 'use strict';
import React from 'react'; 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 FormPropertyHeader from './form_property_header';
import apiUrls from '../../constants/api_urls'; import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin'; import AppConstants from '../../constants/application_constants';
import InputText from './input_text';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; import { getLangText } from '../../utils/lang_utils';
import SignupModal from '../ascribe_modal/modal_signup';
import PasswordResetRequestModal from '../ascribe_modal/modal_password_request_reset';
import { getLangText } from '../../utils/lang_utils.js';
let LoginForm = React.createClass({ let LoginForm = React.createClass({
mixins: [FormMixin],
propTypes: {
url() { headerMessage: React.PropTypes.string,
return apiUrls.users_login; submitMessage: React.PropTypes.string,
redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool
}, },
getFormData() { mixins: [Router.Navigation],
getDefaultProps() {
return { return {
email: this.refs.email.state.value, headerMessage: 'Enter ascribe',
password: this.refs.password.state.value submitMessage: 'Log in',
redirectOnLoggedIn: true,
redirectOnLoginSuccess: true
}; };
}, },
renderForm() {
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 ( return (
<form id="login_modal_content" role="form" onSubmit={this.submit}> <Form
<input className="invisible" type="email" name="fake_consignee"/> className="ascribe-form-bordered"
<input className="invisible" type="password" name="fake_password"/> ref="loginForm"
<InputText url={apiUrls.users_login}
ref="email" handleSuccess={this.handleSuccess}
placeHolder={getLangText('Email')} buttons={
required="required" <button
type="email" type="submit"
submitted={this.state.submitted}/> className="btn ascribe-btn ascribe-btn-login">
<InputText {getLangText(this.props.submitMessage)}
ref="password" </button>}
placeHolder={getLangText('Password')} spinner={
required="required" <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
type="password" <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
submitted={this.state.submitted}/> </span>
<div> }>
{getLangText('Forgot your password')}&#63; <FormPropertyHeader>
<PasswordResetRequestModal <h3>{getLangText(this.props.headerMessage)}</h3>
button={<a className="button" href="#"> {getLangText('Reset password')}</a>}/> </FormPropertyHeader>
</div> <Property
<div> name='email'
{getLangText('Not a member yet')}&#63; label={getLangText('Email')}>
<SignupModal <input
button={<a className="button" href="#"> {getLangText('Sign up')}</a>}/> type="email"
</div> placeholder={getLangText('Enter your email')}
<ButtonSubmitOrClose autoComplete="on"
text={getLangText('LOGIN')} name="username"
onClose={this.props.onRequestHide} required/>
submitted={this.state.submitted} /> </Property>
</form> <Property
name='password'
label={getLangText('Password')}>
<input
type="password"
placeholder={getLangText('Enter your password')}
autoComplete="on"
name="password"
required/>
</Property>
</Form>
); );
} }
}); });

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputTextAreaToggable from './input_textarea_toggable';
let EditionNoteForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.note_edition;
},
getFormData() {
return {
bitcoin_id: this.getBitcoinIds().join(),
note: this.refs.personalNote.state.value
};
},
renderForm() {
return (
<form id="personal_note_content" role="form" key="personal_note_content">
<InputTextAreaToggable
ref="personalNote"
className="form-control"
defaultValue={this.props.editions[0].public_note}
rows={3}
editable={this.props.editable}
required=""
onSubmit={this.submit}
/>
</form>
);
}
});
export default EditionNoteForm;

View File

@ -1,43 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputTextAreaToggable from './input_textarea_toggable';
let PersonalNoteForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.note_notes;
},
getFormData() {
return {
bitcoin_id: this.getBitcoinIds().join(),
note: this.refs.personalNote.state.value
};
},
renderForm() {
return (
<form id="personal_note_content" role="form" key="personal_note_content">
<InputTextAreaToggable
ref="personalNote"
className="form-control"
defaultValue={this.props.editions[0].note_from_user}
rows={3}
editable={this.props.editable}
required=""
onSubmit={this.submit}
/>
</form>
);
}
});
export default PersonalNoteForm;

View File

@ -1,51 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmit from '../ascribe_buttons/button_submit';
import { getLangText } from '../../utils/lang_utils.js'
let PasswordResetForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_password_reset;
},
getFormData() {
return {
email: this.props.email,
token: this.props.token,
password: this.refs.password.state.value,
password_confirm: this.refs.password_confirm.state.value
};
},
renderForm() {
return (
<form id="reset_password_modal_content" role="form" onSubmit={this.submit}>
<div>Reset the password for {this.props.email}:</div>
<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}/>
<ButtonSubmit
text={getLangText('RESET PASSWORD')}
submitted={this.state.submitted} />
</form>
);
}
});
export default PasswordResetForm;

View File

@ -1,42 +0,0 @@
'use strict';
import React from 'react';
import apiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close';
import { getLangText } from '../../utils/lang_utils.js'
let PasswordResetRequestForm = React.createClass({
mixins: [FormMixin],
url() {
return apiUrls.users_password_reset_request;
},
getFormData() {
return {
email: this.refs.email.state.value
};
},
renderForm() {
return (
<form id="request_reset_password_modal_content" role="form" onSubmit={this.submit}>
<InputText
ref="email"
placeHolder={getLangText('Email')}
required="required"
type="email"
submitted={this.state.submitted}/>
<ButtonSubmitOrClose
text={getLangText('RESET PASSWORD')}
onClose={this.props.onRequestHide}
submitted={this.state.submitted} />
</form>
);
}
});
export default PasswordResetRequestForm;

View File

@ -0,0 +1,180 @@
'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({
propTypes: {
headerMessage: React.PropTypes.string,
submitMessage: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
isFineUploaderEditable: React.PropTypes.bool,
children: React.PropTypes.element
},
getDefaultProps() {
return {
headerMessage: getLangText('Register your work'),
submitMessage: getLangText('Register work')
};
},
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}>
{this.props.submitMessage}
</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>{this.props.headerMessage}</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

@ -24,8 +24,9 @@ let EditionRemoveFromCollectionForm = React.createClass({
<p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p> <p>{getLangText('Are you sure you would like to remove these editions from your collection')}&#63;</p>
<p>{getLangText('This is an irrevocable action%s', '.')}</p> <p>{getLangText('This is an irrevocable action%s', '.')}</p>
<div className="modal-footer"> <div className="modal-footer">
<button type="submit" className="btn btn-ascribe-inv" onClick={this.submit}>{getLangText('YES, REMOVE')}</button> <button type="submit" className="btn btn-danger btn-delete btn-sm ascribe-margin-1px" onClick={this.submit}>{getLangText('YES, REMOVE')}</button>
<button className="btn btn-ascribe" onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button> <button className="btn btn-default btn-sm ascribe-margin-1px" style={{marginLeft: '0'}}
onClick={this.props.onRequestHide}>{getLangText('CLOSE')}</button>
</div> </div>
</div> </div>
); );

View File

@ -2,59 +2,76 @@
import React from 'react'; import React from 'react';
import ApiUrls from '../../constants/api_urls';
import FormMixin from '../../mixins/form_mixin';
import InputText from './input_text'; import Form from './form';
import InputTextArea from './input_textarea'; import Property from './property';
import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; import InputTextAreaToggable from './input_textarea_toggable';
import { getLangText } from '../../utils/lang_utils.js' import Button from 'react-bootstrap/lib/Button';
import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils.js';
let ShareForm = React.createClass({ let ShareForm = React.createClass({
mixins: [FormMixin], propTypes: {
url: React.PropTypes.string,
url() { id: React.PropTypes.string,
return ApiUrls.ownership_shares; message: React.PropTypes.string,
editions: React.PropTypes.array,
currentUser: React.PropTypes.object,
onRequestHide: React.PropTypes.func,
handleSuccess: React.PropTypes.func
}, },
getFormData() { getFormData(){
return { return this.props.id;
bitcoin_id: this.getBitcoinIds().join(),
share_emails: this.refs.share_emails.state.value,
share_message: this.refs.share_message.state.value
};
}, },
renderForm() { render() {
let title = this.getTitlesString().join('');
let username = this.props.currentUser.username;
let message =
`${getLangText('Hi')},
${getLangText('I am sharing')} :
${title}${getLangText('with you')}.
${getLangText('Truly yours')},
${username}`;
return ( return (
<form id="share_modal_content" role="form" key="share_modal_content" onSubmit={this.submit}> <Form
<InputText ref='form'
ref="share_emails" url={this.props.url}
placeHolder={getLangText('Comma separated emails')} getFormData={this.getFormData}
required="required" handleSuccess={this.props.handleSuccess}
type="text" buttons={
submitted={this.state.submitted}/> <div className="modal-footer">
<InputTextArea <p className="pull-right">
ref="share_message" <Button
defaultValue={message} className="btn btn-default btn-sm ascribe-margin-1px"
required="" type="submit">SHARE</Button>
/> <Button
<ButtonSubmitOrClose className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
text={getLangText('SHARE')} style={{marginLeft: '0'}}
onClose={this.props.onRequestHide} onClick={this.props.onRequestHide}>CLOSE</Button>
submitted={this.state.submitted} /> </p>
</form> </div>}
spinner={
<div className="modal-footer">
<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_small.gif'} />
</div>}>
<Property
name='share_emails'
label={getLangText('Emails')}>
<input
type="text"
placeholder={getLangText('Comma separated emails')}
required/>
</Property>
<Property
name='share_message'
label='Personal Message'
editable={true}>
<InputTextAreaToggable
rows={1}
editable={true}
defaultValue={this.props.message}
placeholder={getLangText('Enter a message...')}
required="required"/>
</Property>
</Form>
); );
} }
}); });

View File

@ -1,80 +1,137 @@
'use strict'; 'use strict';
import React from 'react'; 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 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({ let SignupForm = React.createClass({
mixins: [FormMixin],
url() { propTypes: {
return apiUrls.users_signup; headerMessage: React.PropTypes.string,
submitMessage: React.PropTypes.string,
handleSuccess: React.PropTypes.func,
children: React.PropTypes.element
}, },
getFormData() { mixins: [Router.Navigation],
getDefaultProps() {
return { return {
email: this.refs.email.state.value, headerMessage: 'Welcome to ascribe',
password: this.refs.password.state.value, submitMessage: 'Sign up'
password_confirm: this.refs.password_confirm.state.value,
terms: this.refs.terms.state.value,
promo_code: this.refs.promo_code.state.value
}; };
}, },
renderForm() { 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 ( return (
<form id="signup_modal_content" role="form" onSubmit={this.submit}> <Form
<input className="invisible" type="email" name="fake_consignee"/> className="ascribe-form-bordered"
<input className="invisible" type="password" name="fake_password"/> ref='form'
<InputText url={apiUrls.users_signup}
ref="email" handleSuccess={this.handleSuccess}
placeHolder={getLangText('Email')} getFormData={this.getFormData}
required="required" buttons={
type="email" <button type="submit" className="btn ascribe-btn ascribe-btn-login">
submitted={this.state.submitted}/> {getLangText(this.props.submitMessage)}
<InputText </button>}
ref="password" spinner={
placeHolder={getLangText('Choose a password')} <span className="btn ascribe-btn ascribe-btn-login ascribe-btn-login-spinner">
required="required" <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
type="password" </span>
submitted={this.state.submitted}/> }>
<InputText <FormPropertyHeader>
ref="password_confirm" <h3>{getLangText(this.props.headerMessage)}</h3>
placeHolder={getLangText('Confirm password')} </FormPropertyHeader>
required="required" <Property
type="password" name='email'
submitted={this.state.submitted}/> label={getLangText('Email')}>
<div> <input
{getLangText('Your password must be at least 10 characters')}. type="email"
{getLangText('This password is securing your digital property like a bank account')}. placeholder={getLangText('(e.g. andy@warhol.co.uk)')}
{getLangText('Store it in a safe place')}! autoComplete="on"
</div> required/>
<InputCheckbox </Property>
ref="terms" <Property
required="required" name='password'
label={ label={getLangText('Password')}
<div> tooltip={tooltipPassword}>
{getLangText('I agree to the')}&nbsp; <input
<a href="/terms" target="_blank"> {getLangText('Terms of Service')}</a> type="password"
</div>}/> placeholder={getLangText('Use a combination of minimum 10 chars and numbers')}
<InputText autoComplete="on"
ref="promo_code" required/>
placeHolder={getLangText('Promocode (Optional)')} </Property>
required="" <Property
type="text" name='password_confirm'
submitted={this.state.submitted}/> label={getLangText('Confirm Password')}
<ButtonSubmitOrClose tooltip={tooltipPassword}>
text={getLangText('JOIN US')} <input
onClose={this.props.onRequestHide} type="password"
submitted={this.state.submitted} /> placeholder={getLangText('Enter your password once again')}
</form> autoComplete="on"
required/>
</Property>
{this.props.children}
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox/>
</Property>
</Form>
); );
} }
}); });
export default SignupForm;
export default SignupForm;

View File

@ -1,33 +0,0 @@
'use strict';
import React from 'react';
import ModalWrapper from './modal_wrapper';
import LoginForm from '../ascribe_forms/form_login';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'
let LoginModal = React.createClass({
handleLoginSuccess(){
this.props.handleSuccess();
let notificationText = getLangText('Login successful');
let notification = new GlobalNotificationModel(notificationText, 'success');
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
return (
<ModalWrapper
button={this.props.button}
title={getLangText('Log in to ascribe')}
handleSuccess={this.handleLoginSuccess}
tooltip={getLangText('Log in to ascribe')}>
<LoginForm />
</ModalWrapper>
);
}
});
export default LoginModal;

View File

@ -1,32 +0,0 @@
'use strict';
import React from 'react';
import ModalWrapper from './modal_wrapper';
import SignupForm from '../ascribe_forms/form_signup';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import { getLangText } from '../../utils/lang_utils.js'
let SignupModal = React.createClass({
handleSignupSuccess(response){
let notificationText = getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.';
let notification = new GlobalNotificationModel(notificationText, 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
render() {
return (
<ModalWrapper
button={this.props.button}
title={getLangText('Create an account')}
handleSuccess={this.handleSignupSuccess}
tooltip={getLangText('Sign up to ascribe')}>
<SignupForm />
</ModalWrapper>
);
}
});
export default SignupModal;

View File

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

View File

@ -3,23 +3,13 @@
import React from 'react'; import React from 'react';
import Router from 'react-router'; import Router from 'react-router';
import GlobalNotificationModel from '../models/global_notification_model'; import LoginForm from './ascribe_forms/form_login';
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 { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
let Link = Router.Link; let Link = Router.Link;
let LoginContainer = React.createClass({ let LoginContainer = React.createClass({
propTypes: { propTypes: {
message: React.PropTypes.string, message: React.PropTypes.string,
@ -27,8 +17,6 @@ let LoginContainer = React.createClass({
redirectOnLoginSuccess: React.PropTypes.bool redirectOnLoginSuccess: React.PropTypes.bool
}, },
mixins: [Router.Navigation],
getDefaultProps() { getDefaultProps() {
return { return {
message: getLangText('Enter') + ' ascribe', message: getLangText('Enter') + ' ascribe',
@ -37,32 +25,12 @@ 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() { render() {
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<br/>
<LoginForm <LoginForm
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
message={this.props.message} /> message={this.props.message} />
<div className="ascribe-login-text"> <div className="ascribe-login-text">
{getLangText('Not an ascribe user')}&#63; <Link to="signup">{getLangText('Sign up')}...</Link><br/> {getLangText('Not an ascribe user')}&#63; <Link to="signup">{getLangText('Sign up')}...</Link><br/>
@ -74,80 +42,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; export default LoginContainer;

View File

@ -39,7 +39,7 @@ let PasswordResetContainer = React.createClass({
return ( return (
<div> <div>
<div className="ascribe-login-text ascribe-login-header"> <div className="ascribe-login-text ascribe-login-header">
{getLangText('Reset your ascribe password')} {getLangText('Reset your password')}
</div> </div>
<PasswordRequestResetForm <PasswordRequestResetForm
handleRequestSuccess={this.handleRequestSuccess}/> handleRequestSuccess={this.handleRequestSuccess}/>
@ -82,9 +82,9 @@ let PasswordRequestResetForm = React.createClass({
{getLangText('Reset your password')} {getLangText('Reset your password')}
</button>} </button>}
spinner={ 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" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<Property <Property
name='email' name='email'
@ -129,9 +129,9 @@ let PasswordResetForm = React.createClass({
{getLangText('Reset your password')} {getLangText('Reset your password')}
</button>} </button>}
spinner={ 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" /> <img src="https://s3-us-west-2.amazonaws.com/ascribe0/media/thumbnails/ascribe_animated_medium.gif" />
</button> </span>
}> }>
<Property <Property
name='password' name='password'
@ -157,4 +157,4 @@ let PasswordResetForm = React.createClass({
} }
}); });
export default PasswordResetContainer; export default PasswordResetContainer;

View File

@ -20,7 +20,7 @@ import AppConstants from '../constants/application_constants';
let PieceList = React.createClass({ let PieceList = React.createClass({
propTypes: { propTypes: {
query: React.PropTypes.object redirectTo: React.PropTypes.string
}, },
mixins: [Router.Navigation, Router.State], mixins: [Router.Navigation, Router.State],
@ -30,7 +30,7 @@ let PieceList = React.createClass({
}, },
componentDidMount() { componentDidMount() {
let page = this.props.query.page || 1; let page = this.getQuery().page || 1;
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
if (this.state.pieceList.length === 0){ if (this.state.pieceList.length === 0){
PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc) PieceListActions.fetchPieceList(page, this.state.pageSize, this.state.search, this.state.orderBy, this.state.orderAsc)
@ -38,6 +38,12 @@ let PieceList = React.createClass({
} }
}, },
componentDidUpdate() {
if (this.props.redirectTo && this.state.pieceListCount === 0) {
this.transitionTo(this.props.redirectTo);
}
},
componentWillUnmount() { componentWillUnmount() {
PieceListStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange);
}, },
@ -56,6 +62,30 @@ let PieceList = React.createClass({
this.state.orderAsc); this.state.orderAsc);
}, },
getPieceListToolbar() {
if(this.state.pieceListCount > 10) {
return (
<PieceListToolbar
className="ascribe-piece-list-toolbar"
searchFor={this.searchFor} />
);
}
},
getPagination() {
let currentPage = parseInt(this.getQuery().page, 10) || 1;
let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize);
if (this.state.pieceListCount > 10) {
return (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
goToPage={this.paginationGoToPage} />
);
}
},
searchFor(searchTerm) { searchFor(searchTerm) {
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc); PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc);
this.transitionTo(this.getPathname(), {page: 1}); this.transitionTo(this.getPathname(), {page: 1});
@ -67,15 +97,11 @@ let PieceList = React.createClass({
}, },
render() { render() {
let currentPage = parseInt(this.props.query.page, 10) || 1;
let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize);
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />); let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
return ( return (
<div> <div>
<PieceListToolbar {this.getPieceListToolbar()}
className="ascribe-piece-list-toolbar"
searchFor={this.searchFor} />
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" /> <PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
<AccordionList <AccordionList
className="ascribe-accordion-list" className="ascribe-accordion-list"
@ -101,10 +127,7 @@ let PieceList = React.createClass({
); );
})} })}
</AccordionList> </AccordionList>
<Pagination {this.getPagination()}
currentPage={currentPage}
totalPages={totalPages}
goToPage={this.paginationGoToPage} />
</div> </div>
); );
} }

View File

@ -2,14 +2,10 @@
import React from 'react'; import React from 'react';
import DatePicker from 'react-datepicker/dist/react-datepicker';
import Router from 'react-router'; import Router from 'react-router';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
import AppConstants from '../constants/application_constants';
import LicenseActions from '../actions/license_actions'; import LicenseActions from '../actions/license_actions';
import LicenseStore from '../stores/license_store'; import LicenseStore from '../stores/license_store';
@ -21,33 +17,44 @@ import UserStore from '../stores/user_store';
import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions'; import GlobalNotificationActions from '../actions/global_notification_actions';
import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
import PropertyCollapsible from './ascribe_forms/property_collapsible'; 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 LoginContainer from './login_container';
import SlidesContainer from './ascribe_slides_container/slides_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 { mergeOptions } from '../utils/general_utils';
import { getCookie } from '../utils/fetch_api_utils';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
let RegisterPiece = React.createClass( { let RegisterPiece = React.createClass( {
propTypes: {
headerMessage: React.PropTypes.string,
submitMessage: React.PropTypes.string,
canSpecifyEditions: React.PropTypes.bool,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element])
},
mixins: [Router.Navigation], mixins: [Router.Navigation],
getDefaultProps() {
return {
canSpecifyEditions: true
};
},
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
LicenseStore.getState(), LicenseStore.getState(),
UserStore.getState(), UserStore.getState(),
PieceListStore.getState(), PieceListStore.getState(),
{ {
digitalWorkKey: null,
uploadStatus: false,
selectedLicense: 0, selectedLicense: 0,
isFineUploaderEditable: false isFineUploaderEditable: false
}); });
@ -97,32 +104,6 @@ let RegisterPiece = React.createClass( {
this.transitionTo('piece', {pieceId: response.piece.id}); 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){ onLicenseChange(event){
//console.log(this.state.licenses[event.target.selectedIndex].url); //console.log(this.state.licenses[event.target.selectedIndex].url);
this.setState({selectedLicense: event.target.selectedIndex}); this.setState({selectedLicense: event.target.selectedIndex});
@ -154,6 +135,21 @@ let RegisterPiece = React.createClass( {
return null; return null;
}, },
getSpecifyEditions() {
if (this.props.canSpecifyEditions) {
return (
<PropertyCollapsible
checkboxLabel={getLangText('Specify editions')}>
<span>{getLangText('Editions')}</span>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</PropertyCollapsible>
);
}
},
changeSlide() { changeSlide() {
// only transition to the login store, if user is not logged in // only transition to the login store, if user is not logged in
// ergo the currentUser object is not properly defined // ergo the currentUser object is not properly defined
@ -170,69 +166,14 @@ let RegisterPiece = React.createClass( {
onFocus={this.changeSlide}> onFocus={this.changeSlide}>
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<Form <RegisterPieceForm
className="ascribe-form-bordered" {...this.props}
ref='form' isFineUploaderEditable={this.state.isFineUploaderEditable}
url={apiUrls.pieces_list} handleSuccess={this.handleSuccess}>
getFormData={this.getFormData} {this.getSpecifyEditions()}
handleSuccess={this.handleSuccess} {this.props.children}
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>
<PropertyCollapsible
checkboxLabel={getLangText('Specify editions')}>
<span>{getLangText('Editions')}</span>
<input
type="number"
placeholder="(e.g. 32)"
min={0}/>
</PropertyCollapsible>
{this.getLicenses()} {this.getLicenses()}
</Form> </RegisterPieceForm>
</Col> </Col>
</Row> </Row>
</div> </div>
@ -248,92 +189,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; 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,18 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import SignupForm from './ascribe_forms/form_signup';
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 Property from './ascribe_forms/property';
import FormPropertyHeader from './ascribe_forms/form_property_header';
import InputCheckbox from './ascribe_forms/input_checkbox';
import { getLangText } from '../utils/lang_utils';
import apiUrls from '../constants/api_urls';
let SignupContainer = React.createClass({ let SignupContainer = React.createClass({
mixins: [Router.Navigation],
getInitialState() { getInitialState() {
return mergeOptions({ return {
submitted: false, submitted: false,
message: null 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){ handleSuccess(message){
@ -60,108 +28,26 @@ let SignupContainer = React.createClass({
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<br/> <br/>
<div className="ascribe-login-text ascribe-login-header"> <div className="ascribe-login-text ascribe-login-header">
{this.state.message} {this.state.message}
</div> </div>
</div> </div>
); );
} }
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<br/> <SignupForm handleSuccess={this.handleSuccess}>
<SignupForm handleSuccess={this.handleSuccess}/> <Property
name='promo_code'
label={getLangText('Promocode')}>
<input
type="text"
placeholder={getLangText('Enter a promocode here (Optional)')}/>
</Property>
</SignupForm>
</div> </div>
); );
} }
}); });
let SignupForm = React.createClass({ export default SignupContainer;
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;

View File

@ -0,0 +1,34 @@
'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 = null;
}
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,27 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import LoginForm from '../../../ascribe_forms/form_login';
let Link = Router.Link;
let LoginContainer = React.createClass({
render() {
return (
<div className="ascribe-login-wrapper">
<LoginForm headerMessage="Log in" />
<div className="ascribe-login-text">
I'm not a user <Link to="signup">Sign up...</Link><br/>
I forgot my password <Link to="password_reset">Rescue me...</Link>
</div>
</div>
);
}
});
export default LoginContainer;

View File

@ -0,0 +1,15 @@
'use strict';
import React from 'react';
import PieceList from '../../../piece_list';
let PrizePieceList = React.createClass({
render() {
return (
<PieceList redirectTo="register_piece" />
);
}
});
export default PrizePieceList;

View File

@ -0,0 +1,50 @@
'use strict';
import React from 'react';
import RegisterPiece from '../../../register_piece';
import Property from '../../../ascribe_forms/property';
import InputTextAreaToggable from '../../../ascribe_forms/input_textarea_toggable';
import InputCheckbox from '../../../ascribe_forms/input_checkbox';
import { getLangText } from '../../../../utils/lang_utils';
let PrizeRegisterPiece = React.createClass({
render() {
return (
<RegisterPiece
headerMessage={getLangText('Submit to the prize')}
submitMessage={getLangText('Submit')}
canSpecifyEditions={false} >
<Property
name='artist_statement'
label={getLangText('Artist statement')}
editable={true}>
<InputTextAreaToggable
rows={1}
editable={true}
placeholder={getLangText('Enter your statement')}
required="required"/>
</Property>
<Property
name='work_description'
label={getLangText('Work description')}
editable={true}>
<InputTextAreaToggable
rows={1}
editable={true}
placeholder={getLangText('Enter the description for your work')}
required="required"/>
</Property>
<Property
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox/>
</Property>
</RegisterPiece>
);
}
});
export default PrizeRegisterPiece;

View File

@ -0,0 +1,44 @@
'use strict';
import React from 'react';
import SignupForm from '../../../ascribe_forms/form_signup';
let SignupContainer = React.createClass({
getInitialState() {
return {
submitted: false,
message: null
};
},
handleSuccess(message){
this.setState({
submitted: true,
message: message
});
},
render() {
if (this.state.submitted){
return (
<div className="ascribe-login-wrapper">
<div className="ascribe-login-text ascribe-login-header">
{this.state.message}
</div>
</div>
);
}
return (
<div className="ascribe-login-wrapper">
<SignupForm
headerMessage="Sign up to the prize"
submitMessage="Sign up"
handleSuccess={this.handleSuccess} />
</div>
);
}
});
export default SignupContainer;

View File

@ -0,0 +1,38 @@
'use strict';
import React from 'react';
import Router from 'react-router';
import Landing from './components/landing';
import LoginContainer from './components/login_container';
import SignupContainer from './components/signup_container';
import PasswordResetContainer from '../../../components/password_reset_container';
import PrizeRegisterPiece from './components/register_piece';
import PrizePieceList from './components/piece_list';
import PieceContainer from '../../ascribe_detail/piece_container';
import EditionContainer from '../../ascribe_detail/edition_container';
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} />
<Route name="login" path="login" handler={LoginContainer} />
<Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
<Route name="register_piece" path="register_piece" handler={PrizeRegisterPiece} />
<Route name="pieces" path="collection" handler={PrizePieceList} />
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
</Route>
);
}
export default getRoutes;

View File

@ -12,7 +12,7 @@ let apiUrls = {
'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/',
'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_id}/',
'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/', 'edition_delete': AppConstants.apiEndpoint + 'editions/${edition_id}/',
'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/${edition_id}/', 'edition_remove_from_collection': AppConstants.apiEndpoint + 'ownership/shares/editions/${edition_id}/',
'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below 'editions': AppConstants.apiEndpoint + 'editions/', // this should be moved to the one below
'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', 'editions_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/',
'licenses': AppConstants.apiEndpoint + 'ownership/licenses/', 'licenses': AppConstants.apiEndpoint + 'ownership/licenses/',
@ -25,7 +25,8 @@ let apiUrls = {
'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/', 'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/',
'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/',
'ownership_loans_contract': AppConstants.apiEndpoint + 'ownership/loans/contract/', 'ownership_loans_contract': AppConstants.apiEndpoint + 'ownership/loans/contract/',
'ownership_shares': AppConstants.apiEndpoint + 'ownership/shares/', 'ownership_shares_editions': AppConstants.apiEndpoint + 'ownership/shares/editions/',
'ownership_shares_pieces': AppConstants.apiEndpoint + 'ownership/shares/pieces/',
'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/', 'ownership_transfers': AppConstants.apiEndpoint + 'ownership/transfers/',
'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/', 'ownership_transfers_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/',
'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/',

View File

@ -10,7 +10,10 @@ let constants = {
'serverUrl': window.SERVER_URL, 'serverUrl': window.SERVER_URL,
'baseUrl': window.BASE_URL, 'baseUrl': window.BASE_URL,
'aclList': ['edit', 'consign', 'consign_request', 'unconsign', 'unconsign_request', 'transfer', '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; export default constants;

View File

@ -210,6 +210,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service', 'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read', 'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.', 'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.',
'REGISTREE': 'REGISTREE',
}, },
'de': { 'de': {
'ID': 'ID', 'ID': 'ID',
@ -420,6 +421,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service', 'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read', 'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.', 'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.',
'REGISTREE': 'REGISTREE',
}, },
'fr': { 'fr': {
'ID': 'ID', 'ID': 'ID',
@ -630,6 +632,7 @@ const languages = {
'I agree to the Terms of Service': 'I agree to the Terms of Service', 'I agree to the Terms of Service': 'I agree to the Terms of Service',
'read': 'read', 'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'Si votre adresse électronique existe dans notre base de données, vous recevrez un lien de récupération de mot de passe dans quelques minutes.', 'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'Si votre adresse électronique existe dans notre base de données, vous recevrez un lien de récupération de mot de passe dans quelques minutes.',
'REGISTREE': 'REGISTREE',
} }
}; };

View File

@ -3,7 +3,9 @@
import React from 'react'; import React from 'react';
import Router from 'react-router'; 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 PieceList from './components/piece_list';
import PieceContainer from './components/ascribe_detail/piece_container'; import PieceContainer from './components/ascribe_detail/piece_container';
import EditionContainer from './components/ascribe_detail/edition_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 SettingsContainer from './components/settings_container';
import CoaVerifyContainer from './components/coa_verify_container'; import CoaVerifyContainer from './components/coa_verify_container';
import AppConstants from './constants/application_constants';
import RegisterPiece from './components/register_piece'; import RegisterPiece from './components/register_piece';
let Route = Router.Route; 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="signup" path="signup" handler={SignupContainer} />
<Route name="login" path="login" handler={LoginContainer} /> <Route name="login" path="login" handler={LoginContainer} />
<Route name="pieces" path="collection" handler={PieceList} /> <Route name="pieces" path="collection" handler={PieceList} />
@ -33,10 +33,21 @@ let routes = (
<Route name="register_piece" path="register_piece" handler={RegisterPiece} /> <Route name="register_piece" path="register_piece" handler={RegisterPiece} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route name="settings" path="settings" handler={SettingsContainer} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> <Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
<Redirect from={baseUrl} to="login" />
<Redirect from={baseUrl + '/'} to="login" />
</Route> </Route>
); );
export default routes;
function getRoutes(type) {
let routes = null;
console.log(type)
if (type === 'prize') {
routes = getPrizeRoutes(COMMON_ROUTES);
} else {
routes = getDefaultRoutes(COMMON_ROUTES);
}
return routes;
}
export default getRoutes;

View File

@ -19,6 +19,7 @@ class PieceListStore {
* the number of items the resource actually - without pagination - provides. * the number of items the resource actually - without pagination - provides.
*/ */
this.pieceList = []; this.pieceList = [];
// -1 specifies that the store is currently loading
this.pieceListCount = -1; this.pieceListCount = -1;
this.page = 1; this.page = 1;
this.pageSize = 10; this.pageSize = 10;

View File

@ -13,6 +13,14 @@ class PieceStore {
onUpdatePiece(piece) { onUpdatePiece(piece) {
this.piece = piece; this.piece = piece;
} }
onUpdateProperty({key, value}) {
if(this.piece && key in this.piece) {
this.piece[key] = value;
} else {
throw new Error('There is no piece defined in PieceStore or the piece object does not have the property you\'re looking for.');
}
}
} }
export default alt.createStore(PieceStore, 'PieceStore'); export default alt.createStore(PieceStore, 'PieceStore');

View File

@ -97,7 +97,7 @@ $ascribe-accordion-list-font: 'Source Sans Pro';
} }
} }
border-left: 3px solid rgba(0,0,0,0); border-left: 3px solid rgba(0,0,0,0);
border-top: 1px solid rgba(0,0,0,.1); //border-top: 1px solid rgba(0,0,0,.1);
border-bottom: 1px solid rgba(0,0,0,.05); border-bottom: 1px solid rgba(0,0,0,.05);
} }
tbody { tbody {

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