diff --git a/index.html b/index.html index af9deda9..d5fe491e 100644 --- a/index.html +++ b/index.html @@ -18,7 +18,7 @@ -
+
diff --git a/js/actions/piece_actions.js b/js/actions/piece_actions.js index cd78569d..d52b8d43 100644 --- a/js/actions/piece_actions.js +++ b/js/actions/piece_actions.js @@ -7,7 +7,8 @@ import PieceFetcher from '../fetchers/piece_fetcher'; class PieceActions { constructor() { this.generateActions( - 'updatePiece' + 'updatePiece', + 'updateProperty' ); } diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index ec7ad7a7..3c5b44d2 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -14,6 +14,21 @@ class PieceListActions { } 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) => { PieceListFetcher .fetch(page, pageSize, search, orderBy, orderAsc) diff --git a/js/app.js b/js/app.js index a54f01d9..94b733cd 100644 --- a/js/app.js +++ b/js/app.js @@ -1,6 +1,6 @@ 'use strict'; -require("babel/polyfill"); +require('babel/polyfill'); import React from 'react'; import Router from 'react-router'; @@ -8,7 +8,8 @@ import Router from 'react-router'; import fetch from 'isomorphic-fetch'; import ApiUrls from './constants/api_urls'; -import routes from './routes'; +import constants from './constants/application_constants'; +import getRoutes from './routes'; import requests from './utils/requests'; let headers = { @@ -28,9 +29,38 @@ requests.defaults({ } }); -Router.run(routes, Router.HistoryLocation, (AscribeApp) => { - React.render( - , - document.getElementById('main') - ); -}); + +class AppGateway { + + 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( + , + document.getElementById('main') + ); + }); + } +} + +let ag = new AppGateway(); +ag.start(); diff --git a/js/components/ascribe_accordion_list/accordion_list.js b/js/components/ascribe_accordion_list/accordion_list.js index a1a1c88a..85084b5f 100644 --- a/js/components/ascribe_accordion_list/accordion_list.js +++ b/js/components/ascribe_accordion_list/accordion_list.js @@ -22,7 +22,7 @@ let AccordionList = React.createClass({ } else if(this.props.count === 0) { return (
-

{getLangText('We could not find any works related to you%s', '...')}

+

{getLangText('We could not find any works related to you...')}

{getLangText('To register one, click')} {getLangText('here')}!

); diff --git a/js/components/ascribe_accordion_list/accordion_list_item.js b/js/components/ascribe_accordion_list/accordion_list_item.js index 4d057c44..0bab42d7 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item.js +++ b/js/components/ascribe_accordion_list/accordion_list_item.js @@ -8,14 +8,11 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import Tooltip from 'react-bootstrap/lib/Tooltip'; 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 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'; 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(){ if (this.props.content.requestAction){ return ( @@ -66,39 +49,32 @@ let AccordionListItem = React.createClass({ }); }, - handleEditionCreationSuccess(response) { - let notification = new GlobalNotificationModel(response.notification, 'success', 10000); - GlobalNotificationActions.appendGlobalNotification(notification); + handleEditionCreationSuccess() { PieceListActions.updatePropertyForPiece({pieceId: this.props.content.id, key: 'num_editions', value: 0}); this.toggleCreateEditionsDialog(); }, - startPolling() { - // start polling until editions are defined - let pollingIntervalIndex = setInterval(() => { - EditionListActions.fetchEditionList(this.props.content.id) - .then((res) => { - - 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 + onPollingSuccess(pieceId, numEditions) { + PieceListActions.updatePropertyForPiece({ + pieceId, + key: 'num_editions', + value: numEditions }); + + EditionListActions.toggleEditionList(pieceId); + }, + + getCreateEditionsDialog() { + if(this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog) { + return ( +
+ +
+ ); + } }, render() { @@ -141,7 +117,8 @@ let AccordionListItem = React.createClass({ + toggleCreateEditionsDialog={this.toggleCreateEditionsDialog} + onPollingSuccess={this.onPollingSuccess}/> @@ -150,8 +127,9 @@ let AccordionListItem = React.createClass({ - {this.props.content.num_editions < 1 && this.state.showCreateEditionsDialog ? : null} + {this.getCreateEditionsDialog()} + {/* this.props.children is AccordionListItemTableEditions */} {this.props.children} diff --git a/js/components/ascribe_accordion_list/accordion_list_item_create_editions.js b/js/components/ascribe_accordion_list/accordion_list_item_create_editions.js deleted file mode 100644 index 30a6efbc..00000000 --- a/js/components/ascribe_accordion_list/accordion_list_item_create_editions.js +++ /dev/null @@ -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 ( -
-
- - - }> - - - -
-
- ); - } -}); - -export default AccordionListItemCreateEditions; \ No newline at end of file diff --git a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js index ba4ece2a..02328382 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js @@ -6,13 +6,16 @@ import classNames from 'classnames'; import EditionListActions from '../../actions/edition_list_actions'; import EditionListStore from '../../stores/edition_list_store'; +import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; + import { getLangText } from '../../utils/lang_utils'; let AccordionListItemEditionWidget = React.createClass({ propTypes: { className: React.PropTypes.string, piece: React.PropTypes.object.isRequired, - toggleCreateEditionsDialog: React.PropTypes.func.isRequired + toggleCreateEditionsDialog: React.PropTypes.func.isRequired, + onPollingSuccess: React.PropTypes.func }, getInitialState() { @@ -55,16 +58,9 @@ let AccordionListItemEditionWidget = React.createClass({ let isEditionListOpen = this.state.isEditionListOpenForPieceId[pieceId] ? this.state.isEditionListOpenForPieceId[pieceId].show : false; if(isEditionListOpen) { - if(typeof this.state.editionList[pieceId] === 'undefined') { - return ( - - ); - } else { - return ( - - ); - } - + return ( + + ); } else { return ( @@ -76,23 +72,16 @@ let AccordionListItemEditionWidget = React.createClass({ let piece = this.props.piece; let numEditions = piece.num_editions; - if(numEditions === -1) { + if(numEditions <= 0) { return ( - + ); - } - else if(numEditions === 0) { - return ( - - ); - } - else if(numEditions === 1) { + } else if(numEditions === 1) { let editionMapping = piece && piece.first_edition ? piece.first_edition.edition_number + '/' + piece.num_editions : ''; return ( diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 9000094f..b4a894a3 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -6,14 +6,14 @@ import Header from '../components/header'; import Footer from '../components/footer'; import GlobalNotification from './global_notification'; -let Link = Router.Link; +// let Link = Router.Link; let RouteHandler = Router.RouteHandler; let AscribeApp = React.createClass({ render() { return ( -
+
diff --git a/js/components/ascribe_buttons/acl_button.js b/js/components/ascribe_buttons/acl_button.js index c1a63265..f5067e1f 100644 --- a/js/components/ascribe_buttons/acl_button.js +++ b/js/components/ascribe_buttons/acl_button.js @@ -13,25 +13,28 @@ import AppConstants from '../../constants/application_constants'; import GlobalNotificationModel from '../../models/global_notification_model'; 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({ propTypes: { action: React.PropTypes.oneOf(AppConstants.aclList).isRequired, availableAcls: React.PropTypes.array.isRequired, - editions: React.PropTypes.array.isRequired, + pieceOrEditions: React.PropTypes.object.isRequired, currentUser: React.PropTypes.object, handleSuccess: React.PropTypes.func.isRequired, className: React.PropTypes.string }, + isPiece(){ + return !(this.props.pieceOrEditions.constructor === Array); + }, actionProperties(){ if (this.props.action === 'consign'){ return { title: getLangText('Consign artwork'), tooltip: getLangText('Have someone else sell the artwork'), - form: , + form: , handleSuccess: this.showNotification }; } @@ -39,14 +42,14 @@ let AclButton = React.createClass({ return { title: getLangText('Unconsign artwork'), tooltip: getLangText('Have the owner manage his sales again'), - form: , + form: , handleSuccess: this.showNotification }; }else if (this.props.action === 'transfer') { return { title: getLangText('Transfer artwork'), tooltip: getLangText('Transfer the ownership of the artwork'), - form: , + form: , handleSuccess: this.showNotification }; } @@ -54,7 +57,7 @@ let AclButton = React.createClass({ return { title: getLangText('Loan artwork'), tooltip: getLangText('Loan your artwork for a limited period of time'), - form: , + form: , handleSuccess: this.showNotification }; } @@ -62,18 +65,62 @@ let AclButton = React.createClass({ return { title: getLangText('Share artwork'), tooltip: getLangText('Share the artwork'), - form: , + form: ( + + ), handleSuccess: this.showNotification }; } }, + showNotification(response){ this.props.handleSuccess(); let notification = new GlobalNotificationModel(response.notification, 'success'); 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() { - let shouldDisplay = this.props.availableAcls.indexOf(this.props.action) > -1; + let shouldDisplay = this.props.availableAcls[this.props.action]; let aclProps = this.actionProperties(); return ( - + + {this.props.children}
); } diff --git a/js/components/ascribe_buttons/create_editions_button.js b/js/components/ascribe_buttons/create_editions_button.js new file mode 100644 index 00000000..ec341473 --- /dev/null +++ b/js/components/ascribe_buttons/create_editions_button.js @@ -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 ( + + ); + } else { + return ( + + ); + } + } +}); + +export default CreateEditionsButton; + diff --git a/js/components/ascribe_buttons/delete_button.js b/js/components/ascribe_buttons/delete_button.js index 0c9ef154..2f46b0ea 100644 --- a/js/components/ascribe_buttons/delete_button.js +++ b/js/components/ascribe_buttons/delete_button.js @@ -41,11 +41,11 @@ let DeleteButton = React.createClass({ let btnDelete = null; let content = null; - if (availableAcls.indexOf('delete') > -1) { + if (availableAcls.delete) { content = ; btnDelete = ; } - else if (availableAcls.indexOf('del_from_collection') > -1){ + else if (availableAcls.unshare){ content = ; btnDelete = ; } diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index adcf2483..0edcf4b8 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -5,7 +5,6 @@ import Router from 'react-router'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; -import Button from 'react-bootstrap/lib/Button'; import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import UserActions from '../../actions/user_actions'; @@ -22,7 +21,6 @@ import Property from './../ascribe_forms/property'; import EditionDetailProperty from './detail_property'; import InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable'; -import EditionHeader from './header'; import EditionFurtherDetails from './further_details'; //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 AppConstants from '../../constants/application_constants'; -import { getCookie } from '../../utils/fetch_api_utils'; import { getLangText } from '../../utils/lang_utils'; let Link = Router.Link; @@ -76,14 +73,19 @@ let Edition = React.createClass({ content={this.props.edition}/> - +
+ {this.props.edition.title}
} /> + + +
+
-1}> + show={this.props.edition.acl.acl_coa}> @@ -112,7 +114,7 @@ let Edition = React.createClass({ -1 || this.props.edition.public_note)}> + (this.props.edition.acl.acl_edit || this.props.edition.public_note)}> -1 + show={this.props.edition.acl.acl_edit || Object.keys(this.props.edition.extra_data).length > 0 || this.props.edition.other_data !== null}> -1} + editable={this.props.edition.acl.acl_edit} pieceId={this.props.edition.parent} extraData={this.props.edition.extra_data} otherData={this.props.edition.other_data} @@ -168,7 +170,7 @@ let EditionSummary = React.createClass({ if (this.props.edition.status.length > 0){ let statusStr = this.props.edition.status.join().replace(/_/, ' '); status = ; - 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 = (
{this.getStatus()} -
{this.getActions()}
@@ -303,8 +304,8 @@ let EditionPublicEditionNote = React.createClass({ GlobalNotificationActions.appendGlobalNotification(notification); }, render() { - let isEditable = this.props.edition.acl.indexOf('edit') > -1; - if (this.props.edition.acl.indexOf('edit') > -1 || this.props.edition.public_note){ + let isEditable = this.props.edition.acl.acl_edit; + if (isEditable || this.props.edition.public_note){ return ( {this.props.content.title}; - return ( -
- - - -
-
- ); - } -}); - -export default Header; \ No newline at end of file diff --git a/js/components/ascribe_detail/piece.js b/js/components/ascribe_detail/piece.js index 14ab1de5..9a410d29 100644 --- a/js/components/ascribe_detail/piece.js +++ b/js/components/ascribe_detail/piece.js @@ -7,13 +7,25 @@ import Col from 'react-bootstrap/lib/Col'; import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; +import DetailProperty from './detail_property'; + 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 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 @@ -24,25 +36,62 @@ let Piece = React.createClass({ loadPiece: React.PropTypes.func }, - //getInitialState() { - // return UserStore.getState(); - //}, - // - //componentDidMount() { - // UserStore.listen(this.onChange); - // UserActions.fetchCurrentUser(); - //}, - // - //componentWillUnmount() { - // UserStore.unlisten(this.onChange); - //}, - // - //onChange(state) { - // this.setState(state); - //}, + getInitialState() { + return mergeOptions( + UserStore.getState(), + { + showCreateEditionsDialog: false + } + ); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + 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 ( +
+ +
+
+ ); + } else { + return (
); + } + }, + + handlePollingSuccess(pieceId, numEditions) { + PieceActions.updateProperty({ + key: 'num_editions', + value: numEditions + }); + }, render() { - return ( @@ -50,22 +99,44 @@ let Piece = React.createClass({ content={this.props.piece}/> -
+
+ {this.props.piece.title}
} /> + + + {this.props.piece.num_editions > 0 ? : null} +
+ +
+ +
+ + + + + + {this.getCreateEditionsDialog()} + -1 + show={this.props.piece.acl.acl_edit || Object.keys(this.props.piece.extra_data).length > 0 || this.props.piece.other_data !== null} defaultExpanded={true}> -1} + editable={this.props.piece.acl.acl_edit} pieceId={this.props.piece.id} extraData={this.props.piece.extra_data} otherData={this.props.piece.other_data} handleSuccess={this.props.loadPiece}/> - ); diff --git a/js/components/ascribe_forms/create_editions_form.js b/js/components/ascribe_forms/create_editions_form.js new file mode 100644 index 00000000..08195756 --- /dev/null +++ b/js/components/ascribe_forms/create_editions_form.js @@ -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 ( + + + + }> + + + + + ); + } +}); + +export default CreateEditionsForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_login.js b/js/components/ascribe_forms/form_login.js index fc44b08b..408aaf73 100644 --- a/js/components/ascribe_forms/form_login.js +++ b/js/components/ascribe_forms/form_login.js @@ -1,62 +1,130 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +import UserStore from '../../stores/user_store'; +import UserActions from '../../actions/user_actions'; + +import Form from './form'; +import Property from './property'; +import FormPropertyHeader from './form_property_header'; 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 AppConstants from '../../constants/application_constants'; + +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({ - mixins: [FormMixin], - - url() { - return apiUrls.users_login; + propTypes: { + headerMessage: React.PropTypes.string, + submitMessage: React.PropTypes.string, + redirectOnLoggedIn: React.PropTypes.bool, + redirectOnLoginSuccess: React.PropTypes.bool }, - - getFormData() { + + mixins: [Router.Navigation], + + getDefaultProps() { return { - email: this.refs.email.state.value, - password: this.refs.password.state.value + headerMessage: 'Enter ascribe', + 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 ( -
- - - - -
- {getLangText('Forgot your password')}? - {getLangText('Reset password')}}/> -
-
- {getLangText('Not a member yet')}? - {getLangText('Sign up')}}/> -
- - +
+ {getLangText(this.props.submitMessage)} + } + spinner={ + + + + }> + +

{getLangText(this.props.headerMessage)}

+
+ + + + + + +
); } }); diff --git a/js/components/ascribe_forms/form_note_edition.js b/js/components/ascribe_forms/form_note_edition.js deleted file mode 100644 index ca1af7ed..00000000 --- a/js/components/ascribe_forms/form_note_edition.js +++ /dev/null @@ -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 ( -
- - - ); - } -}); - -export default EditionNoteForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_note_personal.js b/js/components/ascribe_forms/form_note_personal.js deleted file mode 100644 index 7f3c4f2b..00000000 --- a/js/components/ascribe_forms/form_note_personal.js +++ /dev/null @@ -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 ( -
- - - ); - } -}); - -export default PersonalNoteForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_password_reset.js b/js/components/ascribe_forms/form_password_reset.js deleted file mode 100644 index e42689a4..00000000 --- a/js/components/ascribe_forms/form_password_reset.js +++ /dev/null @@ -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 ( -
-
Reset the password for {this.props.email}:
- - - - - ); - } -}); - -export default PasswordResetForm; \ No newline at end of file diff --git a/js/components/ascribe_forms/form_password_reset_request.js b/js/components/ascribe_forms/form_password_reset_request.js deleted file mode 100644 index 7e5ac62b..00000000 --- a/js/components/ascribe_forms/form_password_reset_request.js +++ /dev/null @@ -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 ( -
- - - - ); - } -}); - -export default PasswordResetRequestForm; diff --git a/js/components/ascribe_forms/form_register_piece.js b/js/components/ascribe_forms/form_register_piece.js new file mode 100644 index 00000000..d3748fc0 --- /dev/null +++ b/js/components/ascribe_forms/form_register_piece.js @@ -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 ( +
+ {this.props.submitMessage} + } + spinner={ + + + + }> + +

{this.props.headerMessage}

+
+ + + + + + + + + + + + + {this.props.children} +
+ ); + } +}); + +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 ( + + ); + } +}); + +export default RegisterPieceForm; diff --git a/js/components/ascribe_forms/form_remove_editions_from_collection.js b/js/components/ascribe_forms/form_remove_editions_from_collection.js index 71607b3d..0ba13975 100644 --- a/js/components/ascribe_forms/form_remove_editions_from_collection.js +++ b/js/components/ascribe_forms/form_remove_editions_from_collection.js @@ -24,8 +24,9 @@ let EditionRemoveFromCollectionForm = React.createClass({

{getLangText('Are you sure you would like to remove these editions from your collection')}?

{getLangText('This is an irrevocable action%s', '.')}

- - + +
); diff --git a/js/components/ascribe_forms/form_share_email.js b/js/components/ascribe_forms/form_share_email.js index e10492d4..cfdce452 100644 --- a/js/components/ascribe_forms/form_share_email.js +++ b/js/components/ascribe_forms/form_share_email.js @@ -2,59 +2,76 @@ import React from 'react'; -import ApiUrls from '../../constants/api_urls'; -import FormMixin from '../../mixins/form_mixin'; -import InputText from './input_text'; -import InputTextArea from './input_textarea'; -import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; -import { getLangText } from '../../utils/lang_utils.js' + + +import Form from './form'; +import Property from './property'; +import InputTextAreaToggable from './input_textarea_toggable'; +import Button from 'react-bootstrap/lib/Button'; + +import AppConstants from '../../constants/application_constants'; +import { getLangText } from '../../utils/lang_utils.js'; let ShareForm = React.createClass({ - mixins: [FormMixin], - - url() { - return ApiUrls.ownership_shares; + propTypes: { + url: React.PropTypes.string, + id: React.PropTypes.string, + message: React.PropTypes.string, + editions: React.PropTypes.array, + currentUser: React.PropTypes.object, + onRequestHide: React.PropTypes.func, + handleSuccess: React.PropTypes.func }, - getFormData() { - return { - bitcoin_id: this.getBitcoinIds().join(), - share_emails: this.refs.share_emails.state.value, - share_message: this.refs.share_message.state.value - }; + getFormData(){ + return this.props.id; }, - renderForm() { - 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}`; + render() { return ( -
- - - - +
+

+ + +

+ } + spinner={ +
+ +
}> + + + + + + +
); } }); diff --git a/js/components/ascribe_forms/form_signup.js b/js/components/ascribe_forms/form_signup.js index 9fa96d6d..3a5ce94e 100644 --- a/js/components/ascribe_forms/form_signup.js +++ b/js/components/ascribe_forms/form_signup.js @@ -1,80 +1,137 @@ 'use strict'; import React from 'react'; +import Router from 'react-router'; + +import { getLangText } from '../../utils/lang_utils'; + +import UserStore from '../../stores/user_store'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +import Form from './form'; +import Property from './property'; +import FormPropertyHeader from './form_property_header'; +import InputCheckbox from './input_checkbox'; import apiUrls from '../../constants/api_urls'; -import FormMixin from '../../mixins/form_mixin'; -import InputText from './input_text'; -import InputCheckbox from './input_checkbox'; -import ButtonSubmitOrClose from '../ascribe_buttons/button_submit_close'; -import { getLangText } from '../../utils/lang_utils.js' + let SignupForm = React.createClass({ - mixins: [FormMixin], - url() { - return apiUrls.users_signup; + propTypes: { + headerMessage: React.PropTypes.string, + submitMessage: React.PropTypes.string, + handleSuccess: React.PropTypes.func, + children: React.PropTypes.element }, - - getFormData() { + + mixins: [Router.Navigation], + + getDefaultProps() { return { - email: this.refs.email.state.value, - password: this.refs.password.state.value, - password_confirm: this.refs.password_confirm.state.value, - terms: this.refs.terms.state.value, - promo_code: this.refs.promo_code.state.value + headerMessage: 'Welcome to ascribe', + submitMessage: 'Sign up' }; }, - 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 ( -
- - - - - -
- {getLangText('Your password must be at least 10 characters')}. - {getLangText('This password is securing your digital property like a bank account')}. - {getLangText('Store it in a safe place')}! -
- - {getLangText('I agree to the')}  - {getLangText('Terms of Service')} - }/> - - - +
+ {getLangText(this.props.submitMessage)} + } + spinner={ + + + + }> + +

{getLangText(this.props.headerMessage)}

+
+ + + + + + + + + + {this.props.children} + + + +
); } }); -export default SignupForm; \ No newline at end of file + +export default SignupForm; diff --git a/js/components/ascribe_modal/modal_login.js b/js/components/ascribe_modal/modal_login.js deleted file mode 100644 index 70fe9ece..00000000 --- a/js/components/ascribe_modal/modal_login.js +++ /dev/null @@ -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 ( - - - - ); - } -}); - -export default LoginModal; diff --git a/js/components/ascribe_modal/modal_signup.js b/js/components/ascribe_modal/modal_signup.js deleted file mode 100644 index 49f3cde5..00000000 --- a/js/components/ascribe_modal/modal_signup.js +++ /dev/null @@ -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 ( - - - - ); - } -}); - -export default SignupModal; diff --git a/js/components/coa_verify_container.js b/js/components/coa_verify_container.js index 6afbdfa5..8c2f2e77 100644 --- a/js/components/coa_verify_container.js +++ b/js/components/coa_verify_container.js @@ -68,9 +68,9 @@ let CoaVerifyForm = React.createClass({ {getLangText('Verify your Certificate of Authenticity')} } spinner={ - + }> -
{getLangText('Not an ascribe user')}? {getLangText('Sign up')}...
@@ -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 ( -
- {getLangText('Enter')} ascribe - } - spinner={ - - }> - -

{this.props.message}

-
- - - - - - -
- ); - } -}); - export default LoginContainer; diff --git a/js/components/password_reset_container.js b/js/components/password_reset_container.js index 5ab258b0..478b4252 100644 --- a/js/components/password_reset_container.js +++ b/js/components/password_reset_container.js @@ -39,7 +39,7 @@ let PasswordResetContainer = React.createClass({ return (
- {getLangText('Reset your ascribe password')} + {getLangText('Reset your password')}
@@ -82,9 +82,9 @@ let PasswordRequestResetForm = React.createClass({ {getLangText('Reset your password')} } spinner={ - + }> } spinner={ - + }> 10) { + return ( + + ); + } + }, + + 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 ( + + ); + } + }, + searchFor(searchTerm) { PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, this.state.orderAsc); this.transitionTo(this.getPathname(), {page: 1}); @@ -67,15 +97,11 @@ let PieceList = React.createClass({ }, render() { - let currentPage = parseInt(this.props.query.page, 10) || 1; - let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize); let loadingElement = (); return (
- + {this.getPieceListToolbar()} - + {this.getPagination()}
); } diff --git a/js/components/register_piece.js b/js/components/register_piece.js index f0dd1e02..d581f951 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -2,14 +2,10 @@ import React from 'react'; -import DatePicker from 'react-datepicker/dist/react-datepicker'; - import Router from 'react-router'; import Col from 'react-bootstrap/lib/Col'; import Row from 'react-bootstrap/lib/Row'; -import AppConstants from '../constants/application_constants'; - import LicenseActions from '../actions/license_actions'; import LicenseStore from '../stores/license_store'; @@ -21,33 +17,44 @@ import UserStore from '../stores/user_store'; import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationActions from '../actions/global_notification_actions'; -import Form from './ascribe_forms/form'; import Property from './ascribe_forms/property'; import PropertyCollapsible from './ascribe_forms/property_collapsible'; -import FormPropertyHeader from './ascribe_forms/form_property_header'; +import RegisterPieceForm from './ascribe_forms/form_register_piece'; +//import FormPropertyHeader from './ascribe_forms/form_property_header'; import LoginContainer from './login_container'; import SlidesContainer from './ascribe_slides_container/slides_container'; -import apiUrls from '../constants/api_urls'; - -import ReactS3FineUploader from './ascribe_uploader/react_s3_fine_uploader'; import { mergeOptions } from '../utils/general_utils'; -import { getCookie } from '../utils/fetch_api_utils'; import { getLangText } from '../utils/lang_utils'; + 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], + getDefaultProps() { + return { + canSpecifyEditions: true + }; + }, + getInitialState(){ return mergeOptions( LicenseStore.getState(), UserStore.getState(), PieceListStore.getState(), { - digitalWorkKey: null, - uploadStatus: false, selectedLicense: 0, isFineUploaderEditable: false }); @@ -97,32 +104,6 @@ let RegisterPiece = React.createClass( { this.transitionTo('piece', {pieceId: response.piece.id}); }, - getFormData(){ - return { - digital_work_key: this.state.digitalWorkKey - }; - }, - - submitKey(key){ - this.setState({ - digitalWorkKey: key - }); - }, - - setIsUploadReady(isReady) { - this.setState({ - isUploadReady: isReady - }); - }, - - isReadyForFormSubmission(files) { - files = files.filter((file) => file.status !== 'deleted' && file.status !== 'canceled'); - if (files.length > 0 && files[0].status === 'upload successful') { - return true; - } else { - return false; - } - }, onLicenseChange(event){ //console.log(this.state.licenses[event.target.selectedIndex].url); this.setState({selectedLicense: event.target.selectedIndex}); @@ -154,6 +135,21 @@ let RegisterPiece = React.createClass( { return null; }, + getSpecifyEditions() { + if (this.props.canSpecifyEditions) { + return ( + + {getLangText('Editions')} + + + ); + } + }, + changeSlide() { // only transition to the login store, if user is not logged in // ergo the currentUser object is not properly defined @@ -170,69 +166,14 @@ let RegisterPiece = React.createClass( { onFocus={this.changeSlide}> -
- {getLangText('Register work')} - } - spinner={ - - }> - -

{getLangText('Register your work')}

-
- - - - - - - - - - - - - - {getLangText('Editions')} - - + + {this.getSpecifyEditions()} + {this.props.children} {this.getLicenses()} - +
@@ -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 ( - - ); - } -}); - - -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 ( - - ); - } -}); - export default RegisterPiece; diff --git a/js/components/routes.js b/js/components/routes.js new file mode 100644 index 00000000..8b230a5a --- /dev/null +++ b/js/components/routes.js @@ -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 ( + + + + {commonRoutes} + + ); +} + + +export default getRoutes; diff --git a/js/components/signup_container.js b/js/components/signup_container.js index 654b9c03..28ba87df 100644 --- a/js/components/signup_container.js +++ b/js/components/signup_container.js @@ -1,50 +1,18 @@ 'use strict'; import React from 'react'; -import Router from 'react-router'; - -import { mergeOptions } from '../utils/general_utils'; -import { getLangText } from '../utils/lang_utils'; - -import UserStore from '../stores/user_store'; - -import GlobalNotificationModel from '../models/global_notification_model'; -import GlobalNotificationActions from '../actions/global_notification_actions'; - -import Form from './ascribe_forms/form'; +import SignupForm from './ascribe_forms/form_signup'; import Property from './ascribe_forms/property'; -import FormPropertyHeader from './ascribe_forms/form_property_header'; -import InputCheckbox from './ascribe_forms/input_checkbox'; - -import apiUrls from '../constants/api_urls'; +import { getLangText } from '../utils/lang_utils'; let SignupContainer = React.createClass({ - mixins: [Router.Navigation], - getInitialState() { - return mergeOptions({ + return { submitted: false, message: null - }, UserStore.getState()); - }, - - componentDidMount() { - UserStore.listen(this.onChange); - }, - - componentWillUnmount() { - UserStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - - // if user is already logged in, redirect him to piece list - if(this.state.currentUser && this.state.currentUser.email) { - this.transitionTo('pieces'); - } + }; }, handleSuccess(message){ @@ -60,108 +28,26 @@ let SignupContainer = React.createClass({

- {this.state.message} + {this.state.message}
); } return (
-
- + + + + +
); } }); -let SignupForm = React.createClass({ - propTypes: { - handleSuccess: React.PropTypes.func - }, - - mixins: [Router.Navigation], - - handleSuccess(response){ - - let notificationText = getLangText('Sign up successful'); - let notification = new GlobalNotificationModel(notificationText, 'success', 50000); - GlobalNotificationActions.appendGlobalNotification(notification); - this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + - ', ' + getLangText('please confirm') + '.'); - - }, - getFormData(){ - return {terms: this.refs.form.refs.terms.refs.input.state.value}; - }, - render() { - let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' + - getLangText('This password is securing your digital property like a bank account') + '.\n ' + - getLangText('Store it in a safe place') + '!'; - return ( -
- {getLangText('Sign up to ascribe')} - } - spinner={ - - }> - -

{getLangText('Welcome to ascribe')}

-
- - - - - - - - - - - - - - - -
- ); - } -}); - -export default SignupContainer; \ No newline at end of file +export default SignupContainer; diff --git a/js/components/whitelabel/prize/app.js b/js/components/whitelabel/prize/app.js new file mode 100644 index 00000000..0828faa4 --- /dev/null +++ b/js/components/whitelabel/prize/app.js @@ -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 ( +
+ + {header} + + + +
+ ); + } +}); + +export default PrizeApp; diff --git a/js/components/whitelabel/prize/components/hero.js b/js/components/whitelabel/prize/components/hero.js new file mode 100644 index 00000000..101d599a --- /dev/null +++ b/js/components/whitelabel/prize/components/hero.js @@ -0,0 +1,18 @@ +'use strict'; + +import React from 'react'; +import constants from '../../../../constants/application_constants'; + + +let Hero = React.createClass({ + render() { + return ( +
+ Sluice Art Prize +

Sluice Art Prize 2015

+
+ ); + } +}); + +export default Hero; diff --git a/js/components/whitelabel/prize/components/landing.js b/js/components/whitelabel/prize/components/landing.js new file mode 100644 index 00000000..b7f1fa95 --- /dev/null +++ b/js/components/whitelabel/prize/components/landing.js @@ -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 ( +
+
+ + + Signup to the prize + + + Already a user? log in + +
+
+ ); + } +}); + +export default Landing; diff --git a/js/components/whitelabel/prize/components/login_container.js b/js/components/whitelabel/prize/components/login_container.js new file mode 100644 index 00000000..cab80774 --- /dev/null +++ b/js/components/whitelabel/prize/components/login_container.js @@ -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 ( +
+ +
+ I'm not a user Sign up...
+ I forgot my password Rescue me... +
+
+ ); + } +}); + + + +export default LoginContainer; diff --git a/js/components/whitelabel/prize/components/piece_list.js b/js/components/whitelabel/prize/components/piece_list.js new file mode 100644 index 00000000..8b5a7a34 --- /dev/null +++ b/js/components/whitelabel/prize/components/piece_list.js @@ -0,0 +1,15 @@ +'use strict'; + +import React from 'react'; +import PieceList from '../../../piece_list'; + + +let PrizePieceList = React.createClass({ + render() { + return ( + + ); + } +}); + +export default PrizePieceList; diff --git a/js/components/whitelabel/prize/components/register_piece.js b/js/components/whitelabel/prize/components/register_piece.js new file mode 100644 index 00000000..a1009b4b --- /dev/null +++ b/js/components/whitelabel/prize/components/register_piece.js @@ -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 ( + + + + + + + + + + + + ); + } +}); + +export default PrizeRegisterPiece; diff --git a/js/components/whitelabel/prize/components/signup_container.js b/js/components/whitelabel/prize/components/signup_container.js new file mode 100644 index 00000000..4950dac7 --- /dev/null +++ b/js/components/whitelabel/prize/components/signup_container.js @@ -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 ( +
+
+ {this.state.message} +
+
+ ); + } + return ( +
+ +
+ ); + } +}); + + +export default SignupContainer; diff --git a/js/components/whitelabel/prize/routes.js b/js/components/whitelabel/prize/routes.js new file mode 100644 index 00000000..fd4c6edb --- /dev/null +++ b/js/components/whitelabel/prize/routes.js @@ -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 ( + + + + + + + + + + + ); +} + + +export default getRoutes; diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index 040d37ba..6d3ce7c8 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -12,7 +12,7 @@ let apiUrls = { 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', 'edition': AppConstants.apiEndpoint + 'editions/${bitcoin_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_list': AppConstants.apiEndpoint + 'pieces/${piece_id}/editions/', 'licenses': AppConstants.apiEndpoint + 'ownership/licenses/', @@ -25,7 +25,8 @@ let apiUrls = { 'ownership_loans_confirm': AppConstants.apiEndpoint + 'ownership/loans/confirm/', 'ownership_loans_deny': AppConstants.apiEndpoint + 'ownership/loans/deny/', '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_withdraw': AppConstants.apiEndpoint + 'ownership/transfers/withdraw/', 'ownership_unconsigns': AppConstants.apiEndpoint + 'ownership/unconsigns/', diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 0c7a5893..61955469 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -10,7 +10,10 @@ let constants = { 'serverUrl': window.SERVER_URL, 'baseUrl': window.BASE_URL, 'aclList': ['edit', 'consign', 'consign_request', 'unconsign', 'unconsign_request', 'transfer', - 'loan', 'loan_request', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection'] + 'loan', 'loan_request', 'share', 'download', 'view', 'delete', 'del_from_collection', 'add_to_collection'], + + // in case of whitelabel cusomization, we store stuff here + 'whitelabel': {} }; export default constants; diff --git a/js/constants/languages.js b/js/constants/languages.js index c21bd0ad..d718fa69 100644 --- a/js/constants/languages.js +++ b/js/constants/languages.js @@ -210,6 +210,7 @@ const languages = { 'I agree to the Terms of Service': 'I agree to the Terms of Service', '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.', + 'REGISTREE': 'REGISTREE', }, 'de': { 'ID': 'ID', @@ -420,6 +421,7 @@ const languages = { 'I agree to the Terms of Service': 'I agree to the Terms of Service', '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.', + 'REGISTREE': 'REGISTREE', }, 'fr': { 'ID': 'ID', @@ -630,6 +632,7 @@ const languages = { 'I agree to the Terms of Service': 'I agree to the Terms of Service', '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.', + 'REGISTREE': 'REGISTREE', } }; diff --git a/js/routes.js b/js/routes.js index db8ed171..669a0fc7 100644 --- a/js/routes.js +++ b/js/routes.js @@ -3,7 +3,9 @@ import React from 'react'; import Router from 'react-router'; -import AscribeApp from './components/ascribe_app'; +import getPrizeRoutes from './components/whitelabel/prize/routes'; +import getDefaultRoutes from './components/routes'; + import PieceList from './components/piece_list'; import PieceContainer from './components/ascribe_detail/piece_container'; import EditionContainer from './components/ascribe_detail/edition_container'; @@ -15,15 +17,13 @@ import PasswordResetContainer from './components/password_reset_container'; import SettingsContainer from './components/settings_container'; import CoaVerifyContainer from './components/coa_verify_container'; -import AppConstants from './constants/application_constants'; import RegisterPiece from './components/register_piece'; let Route = Router.Route; -let Redirect = Router.Redirect; -let baseUrl = AppConstants.baseUrl; -let routes = ( - + +const COMMON_ROUTES = ( + @@ -33,10 +33,21 @@ let routes = ( - - - ); -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; diff --git a/js/stores/piece_list_store.js b/js/stores/piece_list_store.js index 63a109ca..22d8d5d6 100644 --- a/js/stores/piece_list_store.js +++ b/js/stores/piece_list_store.js @@ -19,6 +19,7 @@ class PieceListStore { * the number of items the resource actually - without pagination - provides. */ this.pieceList = []; + // -1 specifies that the store is currently loading this.pieceListCount = -1; this.page = 1; this.pageSize = 10; diff --git a/js/stores/piece_store.js b/js/stores/piece_store.js index 5d2651ad..0bebab10 100644 --- a/js/stores/piece_store.js +++ b/js/stores/piece_store.js @@ -13,6 +13,14 @@ class PieceStore { onUpdatePiece(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'); diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss index 5c67975e..1b682735 100644 --- a/sass/ascribe_accordion_list.scss +++ b/sass/ascribe_accordion_list.scss @@ -97,7 +97,7 @@ $ascribe-accordion-list-font: 'Source Sans Pro'; } } 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); } tbody { diff --git a/sass/ascribe_app.scss b/sass/ascribe_app.scss new file mode 100644 index 00000000..ed811263 --- /dev/null +++ b/sass/ascribe_app.scss @@ -0,0 +1,5 @@ +.ascribe-default-app { + background-color: #FDFDFD; + border-radius: 0; + padding-top: 70px; +} diff --git a/sass/main.scss b/sass/main.scss index be144320..073c8937 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -11,6 +11,7 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_theme'; @import './ascribe-fonts/style'; @import './ascribe-fonts/ascribe-fonts'; +@import 'ascribe_app'; @import 'ascribe_login'; @import 'ascribe_table'; @import 'ascribe_accordion_list'; @@ -28,10 +29,11 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_slides_container'; @import 'ascribe_form'; -body { - background-color: #FDFDFD; - border-radius: 0; - margin-top: 70px; +@import 'whitelabel/index'; + + +html, body { + height: 100%; } html { @@ -42,6 +44,10 @@ hr { margin-bottom: 15px; } +#main { + height: 100%; +} + .hidden { display: none; } diff --git a/sass/whitelabel/index.scss b/sass/whitelabel/index.scss new file mode 100644 index 00000000..54b9f41e --- /dev/null +++ b/sass/whitelabel/index.scss @@ -0,0 +1 @@ +@import 'prize/index'; diff --git a/sass/whitelabel/prize/index.scss b/sass/whitelabel/prize/index.scss new file mode 100644 index 00000000..203c0985 --- /dev/null +++ b/sass/whitelabel/prize/index.scss @@ -0,0 +1 @@ +@import 'landing' diff --git a/sass/whitelabel/prize/landing.scss b/sass/whitelabel/prize/landing.scss new file mode 100644 index 00000000..968fee29 --- /dev/null +++ b/sass/whitelabel/prize/landing.scss @@ -0,0 +1,16 @@ +.whitelabel-prize { + .hero { + overflow: hidden; + + .logo { + float: left; + padding-right: 2em; + } + } + + .enter { + + } +} + +