diff --git a/js/actions/event_actions.js b/js/actions/event_actions.js index 6d8ee12f..24889f4e 100644 --- a/js/actions/event_actions.js +++ b/js/actions/event_actions.js @@ -8,9 +8,8 @@ class EventActions { this.generateActions( 'applicationWillBoot', 'applicationDidBoot', - 'profileDidLoad', - //'userDidLogin', - //'userDidLogout', + 'userDidAuthenticate', + 'userDidLogout', 'routeDidChange' ); } diff --git a/js/actions/user_actions.js b/js/actions/user_actions.js index a661b8de..9d59044f 100644 --- a/js/actions/user_actions.js +++ b/js/actions/user_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import { altUser } from '../alt'; +import { alt } from '../alt'; class UserActions { @@ -15,4 +15,4 @@ class UserActions { } } -export default altUser.createActions(UserActions); +export default alt.createActions(UserActions); diff --git a/js/alt.js b/js/alt.js index 141248c1..3e1d3fae 100644 --- a/js/alt.js +++ b/js/alt.js @@ -4,5 +4,4 @@ import Alt from 'alt'; export let alt = new Alt(); export let altThirdParty = new Alt(); -export let altUser = new Alt(); export let altWhitelabel = new Alt(); diff --git a/js/components/app_base.js b/js/components/app_base.js index c188e628..3d14fae5 100644 --- a/js/components/app_base.js +++ b/js/components/app_base.js @@ -4,10 +4,18 @@ import React from 'react'; import classNames from 'classnames'; import { History } from 'react-router'; +import UserActions from '../actions/user_actions'; +import UserStore from '../stores/user_store'; + +import WhitelabelActions from '../actions/whitelabel_actions'; +import WhitelabelStore from '../stores/whitelabel_store'; + import GlobalNotification from './global_notification'; import AppConstants from '../constants/application_constants'; +import { mergeOptions } from '../utils/general_utils'; + export default function AppBase(App) { return React.createClass({ @@ -20,9 +28,22 @@ export default function AppBase(App) { routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, + getInitialState() { + return mergeOptions( + UserStore.getState(), + WhitelabelStore.getState() + ); + }, + mixins: [History], componentDidMount() { + UserStore.listen(this.onChange); + WhitelabelStore.listen(this.onChange); + + UserActions.fetchCurrentUser(); + WhitelabelActions.fetchWhitelabel(); + this.history.locationQueue.push(this.props.location); }, @@ -36,8 +57,18 @@ export default function AppBase(App) { } }, + componentWillUnmount() { + UserStore.unlisten(this.onChange); + WhitelabelActions.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + render() { const { routes } = this.props; + const { currentUser, whitelabel } = this.state; // The second element of the routes prop given to us by react-router is always the // active second-level component object (ie. after App). @@ -47,7 +78,9 @@ export default function AppBase(App) {
+ activeRoute={activeRoute} + currentUser={currentUser} + whitelabel={whitelabel} /> diff --git a/js/components/app_route_wrapper.js b/js/components/app_route_wrapper.js new file mode 100644 index 00000000..d680faeb --- /dev/null +++ b/js/components/app_route_wrapper.js @@ -0,0 +1,34 @@ +'use strict'; + +import React from 'react'; + +import { omitFromObject } from '../utils/general_utils'; + +const AppRouteWrapper = React.createClass({ + propTypes: { + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]).isRequired + }, + + render() { + const propsToPropagate = omitFromObject(this.props, ['children']); + + let childrenWithProps = this.props.children; + // If there are more props given, propagate them into the child routes by cloning the routes + if (Object.keys(propsToPropagate).length) { + childrenWithProps = React.Children.map(this.props.children, (child) => { + return React.cloneElement(child, propsToPropagate); + }); + } + + return ( +
+ {childrenWithProps} +
+ ); + } +}); + +export default AppRouteWrapper; diff --git a/js/components/ascribe_accordion_list/accordion_list.js b/js/components/ascribe_accordion_list/accordion_list.js index 1046ab7f..1ce113cb 100644 --- a/js/components/ascribe_accordion_list/accordion_list.js +++ b/js/components/ascribe_accordion_list/accordion_list.js @@ -1,14 +1,17 @@ 'use strict'; import React from 'react'; +import { Link } from 'react-router'; + import { getLangText } from '../../utils/lang_utils'; let AccordionList = React.createClass({ propTypes: { - className: React.PropTypes.string, children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired, - loadingElement: React.PropTypes.element, + loadingElement: React.PropTypes.element.isRequired, + + className: React.PropTypes.string, count: React.PropTypes.number, itemList: React.PropTypes.arrayOf(React.PropTypes.object), search: React.PropTypes.string, @@ -22,7 +25,7 @@ let AccordionList = React.createClass({ render() { const { search } = this.props; - if(this.props.itemList && this.props.itemList.length > 0) { + if (this.props.itemList && this.props.itemList.length > 0) { return (
{this.props.children} @@ -36,7 +39,7 @@ let AccordionList = React.createClass({

{getLangText('To register one, click')}  - {getLangText('here')}! + {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 38cb77b1..4cc99de0 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item.js +++ b/js/components/ascribe_accordion_list/accordion_list_item.js @@ -21,16 +21,16 @@ let AccordionListItem = React.createClass({ }, render() { - const { linkData, - className, - thumbnail, - heading, - subheading, - subsubheading, - buttons, - badge, - children } = this.props; - + const { + linkData, + className, + thumbnail, + heading, + subheading, + subsubheading, + buttons, + badge, + children } = this.props; return (
diff --git a/js/components/ascribe_accordion_list/accordion_list_item_piece.js b/js/components/ascribe_accordion_list/accordion_list_item_piece.js index 9f876388..3a21c2ea 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_piece.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js @@ -34,11 +34,10 @@ let AccordionListItemPiece = React.createClass({ }, getLinkData() { - let { piece } = this.props; + const { piece } = this.props; - if(piece && piece.first_edition) { + if (piece && piece.first_edition) { return `/editions/${piece.first_edition.bitcoin_id}`; - } else { return `/pieces/${piece.id}`; } diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js index f6712d37..e752451e 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js @@ -7,20 +7,19 @@ import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import Tooltip from 'react-bootstrap/lib/Tooltip'; -import AccordionListItemPiece from './accordion_list_item_piece'; -import AccordionListItemEditionWidget from './accordion_list_item_edition_widget'; -import CreateEditionsForm from '../ascribe_forms/create_editions_form'; - -import PieceListActions from '../../actions/piece_list_actions'; -import PieceListStore from '../../stores/piece_list_store'; - -import WhitelabelStore from '../../stores/whitelabel_store'; - import EditionListActions from '../../actions/edition_list_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; +import PieceListActions from '../../actions/piece_list_actions'; +import PieceListStore from '../../stores/piece_list_store'; + +import AccordionListItemPiece from './accordion_list_item_piece'; +import AccordionListItemEditionWidget from './accordion_list_item_edition_widget'; +import CreateEditionsForm from '../ascribe_forms/create_editions_form'; + + import AclProxy from '../acl_proxy'; import { getLangText } from '../../utils/lang_utils'; @@ -29,50 +28,51 @@ import { mergeOptions } from '../../utils/general_utils'; let AccordionListItemWallet = React.createClass({ propTypes: { - className: React.PropTypes.string, - content: React.PropTypes.object, - thumbnailPlaceholder: React.PropTypes.func, + content: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object.isRequired, + children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element - ]) + ]), + className: React.PropTypes.string, + thumbnailPlaceholder: React.PropTypes.func }, getInitialState() { return mergeOptions( + PieceListStore.getState(), { showCreateEditionsDialog: false - }, - PieceListStore.getState(), - WhitelabelStore.getState() + } ); }, componentDidMount() { PieceListStore.listen(this.onChange); - WhitelabelStore.listen(this.onChange); }, componentWillUnmount() { PieceListStore.unlisten(this.onChange); - WhitelabelStore.unlisten(this.onChange); }, onChange(state) { this.setState(state); }, - getGlyphicon(){ - if ((this.props.content.notifications && this.props.content.notifications.length > 0)){ + getGlyphicon() { + if (this.props.content.notifications && this.props.content.notifications.length) { return ( {getLangText('You have actions pending')}}> - - ); + + + ); + } else { + return null; } - return null; }, toggleCreateEditionsDialog() { @@ -93,7 +93,7 @@ let AccordionListItemWallet = React.createClass({ PieceListActions.fetchPieceList({ page, pageSize, search, orderBy, orderAsc, filterBy }); EditionListActions.toggleEditionList(pieceId); - const notification = new GlobalNotificationModel('Editions successfully created', 'success', 10000); + const notification = new GlobalNotificationModel(getLangText('Editions successfully created'), 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); }, @@ -111,13 +111,15 @@ let AccordionListItemWallet = React.createClass({ }, getLicences() { + const { content, whitelabel } = this.props; + // convert this to acl_view_licences later - if (this.state.whitelabel && this.state.whitelabel.name === 'Creative Commons France') { + if (whitelabel.name === 'Creative Commons France') { return ( , - - {getLangText('%s license', this.props.content.license_type.code)} + + {getLangText('%s license', content.license_type.code)} ); diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 737a35f5..87ab1daf 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -3,6 +3,7 @@ import React from 'react'; import AppBase from './app_base'; +import AppRouteWrapper from './app_route_wrapper'; import Footer from './footer'; import Header from './header'; @@ -11,19 +12,28 @@ let AscribeApp = React.createClass({ propTypes: { activeRoute: React.PropTypes.object.isRequired, children: React.PropTypes.element.isRequired, - routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + + // Provided from AppBase + currentUser: React.PropTypes.object, + whitelabel: React.PropTypes.object }, render() { - const { activeRoute, children, routes } = this.props; + const { activeRoute, children, currentUser, routes, whitelabel } = this.props; return ( -
-
-
+
+
+ {/* Routes are injected here */} {children} -
+
); diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index 35e42c20..d059e0f2 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -2,9 +2,6 @@ import React from 'react/addons'; -import UserActions from '../../actions/user_actions'; -import UserStore from '../../stores/user_store'; - import ConsignButton from './acls/consign_button'; import EmailButton from './acls/email_button'; import LoanButton from './acls/loan_button'; @@ -12,50 +9,44 @@ import LoanRequestButton from './acls/loan_request_button'; import TransferButton from './acls/transfer_button'; import UnconsignButton from './acls/unconsign_button'; -import { mergeOptions } from '../../utils/general_utils'; +import { selectFromObject } from '../../utils/general_utils'; let AclButtonList = React.createClass({ propTypes: { - className: React.PropTypes.string, + availableAcls: React.PropTypes.object.isRequired, + currentUser: React.PropTypes.object.isRequired, + handleSuccess: React.PropTypes.func.isRequired, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array ]).isRequired, - availableAcls: React.PropTypes.object.isRequired, + buttonsStyle: React.PropTypes.object, - handleSuccess: React.PropTypes.func.isRequired, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element - ]) + ]), + className: React.PropTypes.string }, getInitialState() { - return mergeOptions( - UserStore.getState(), - { - buttonListSize: 0 - } - ); + return { + buttonListSize: 0 + } }, componentDidMount() { - UserStore.listen(this.onChange); - UserActions.fetchCurrentUser.defer(); - window.addEventListener('resize', this.handleResize); window.dispatchEvent(new Event('resize')); }, componentDidUpdate(prevProps) { - if(prevProps.availableAcls && prevProps.availableAcls !== this.props.availableAcls) { + if (prevProps.availableAcls && prevProps.availableAcls !== this.props.availableAcls) { window.dispatchEvent(new Event('resize')); } }, componentWillUnmount() { - UserStore.unlisten(this.onChange); - window.removeEventListener('resize', this.handleResize); }, @@ -65,10 +56,6 @@ let AclButtonList = React.createClass({ }); }, - onChange(state) { - this.setState(state); - }, - renderChildren() { const { children } = this.props; const { buttonListSize } = this.state; @@ -79,42 +66,29 @@ let AclButtonList = React.createClass({ }, render() { - const { className, - buttonsStyle, - availableAcls, - pieceOrEditions, - handleSuccess } = this.props; + const { + availableAcls, + buttonsStyle, + className, + currentUser, + handleSuccess, + pieceOrEditions } = this.props; - const { currentUser } = this.state; + const buttonProps = selectFromObject(this.props, [ + 'availableAcls', + 'currentUser', + 'handleSuccess', + 'pieceOrEditions' + ]); return (
- - - - - + + + + + {this.renderChildren()}
diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js index 2525c52a..d40a779a 100644 --- a/js/components/ascribe_buttons/acls/acl_button.js +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -24,23 +24,20 @@ export default function AclButton({ action, displayName, title, tooltip }) { propTypes: { availableAcls: React.PropTypes.object.isRequired, - buttonAcceptName: React.PropTypes.string, - buttonAcceptClassName: React.PropTypes.string, - currentUser: React.PropTypes.object, - email: React.PropTypes.string, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array ]).isRequired, - handleSuccess: React.PropTypes.func.isRequired, - className: React.PropTypes.string + + buttonAcceptName: React.PropTypes.string, + buttonAcceptClassName: React.PropTypes.string, + currentUser: React.PropTypes.object, + email: React.PropTypes.string, + handleSuccess: React.PropTypes.func }, sanitizeAction() { - if (this.props.buttonAcceptName) { - return this.props.buttonAcceptName; - } - return AclInformationText.titles[action]; + return this.props.buttonAcceptName || AclInformationText.titles[action]; }, render() { diff --git a/js/components/ascribe_buttons/unconsign_request_button.js b/js/components/ascribe_buttons/unconsign_request_button.js index e5e1c661..c324ff28 100644 --- a/js/components/ascribe_buttons/unconsign_request_button.js +++ b/js/components/ascribe_buttons/unconsign_request_button.js @@ -15,10 +15,12 @@ let UnConsignRequestButton = React.createClass({ propTypes: { currentUser: React.PropTypes.object.isRequired, edition: React.PropTypes.object.isRequired, - handleSuccess: React.PropTypes.func.isRequired + + handleSuccess: React.PropTypes.func }, render: function () { + const { currentUser, edition, handleSuccess } = this.props; return ( } - handleSuccess={this.props.handleSuccess} + handleSuccess={handleSuccess} title='Request to Un-Consign'> +${currentUser.username}` + } /> ); } diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index df9d41a0..f2110e10 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -8,23 +8,23 @@ import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; import Glyphicon from 'react-bootstrap/lib/Glyphicon'; -import HistoryIterator from './history_iterator'; +import EditionActions from '../../actions/edition_actions'; -import MediaContainer from './media_container'; - -import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; - -import Form from './../ascribe_forms/form'; -import Property from './../ascribe_forms/property'; import DetailProperty from './detail_property'; -import LicenseDetail from './license_detail'; -import FurtherDetails from './further_details'; - import EditionActionPanel from './edition_action_panel'; -import AclProxy from '../acl_proxy'; - +import FurtherDetails from './further_details'; +import HistoryIterator from './history_iterator'; +import LicenseDetail from './license_detail'; +import MediaContainer from './media_container'; import Note from './note'; +import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph'; + +import Form from '../ascribe_forms/form'; +import Property from '../ascribe_forms/property'; + +import AclProxy from '../acl_proxy'; + import ApiUrls from '../../constants/api_urls'; import AscribeSpinner from '../ascribe_spinner'; @@ -36,11 +36,13 @@ import { getLangText } from '../../utils/lang_utils'; */ let Edition = React.createClass({ propTypes: { + currentUser: React.PropTypes.object.isRequired, + edition: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object.isRequired, + actionPanelButtonListType: React.PropTypes.func, - furtherDetailsType: React.PropTypes.func, - edition: React.PropTypes.object, coaError: React.PropTypes.object, - currentUser: React.PropTypes.object, + furtherDetailsType: React.PropTypes.func, loadEdition: React.PropTypes.func }, @@ -57,56 +59,56 @@ let Edition = React.createClass({ currentUser, edition, furtherDetailsType: FurtherDetailsType, - loadEdition } = this.props; + loadEdition, + whitelabel } = this.props; return ( + currentUser={currentUser} + refreshObject={loadEdition} />
-
+

{edition.title}

-
+
+ handleSuccess={loadEdition} + whitelabel={whitelabel} /> + editionId={edition.bitcoin_id} /> 0}> - + show={edition.ownership_history && edition.ownership_history.length}> + 0}> - + 0}> - + + currentUser={currentUser} /> {return {'bitcoin_id': edition.bitcoin_id}; }} label={getLangText('Personal note (public)')} @@ -130,13 +132,11 @@ let Edition = React.createClass({ show={!!edition.public_note || !!edition.acl.acl_edit} successMessage={getLangText('Public edition note saved')} url={ApiUrls.note_public_edition} - currentUser={currentUser}/> + currentUser={currentUser} /> 0 || - edition.other_data.length > 0}> + show={edition.acl.acl_edit || Object.keys(edition.extra_data).length || edition.other_data.length}> - - + +
@@ -158,60 +156,56 @@ let Edition = React.createClass({ let EditionSummary = React.createClass({ propTypes: { + currentUser: React.PropTypes.object.isRequired, + edition: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object.isRequired, + actionPanelButtonListType: React.PropTypes.func, - edition: React.PropTypes.object, - currentUser: React.PropTypes.object, handleSuccess: React.PropTypes.func }, - handleSuccess() { - this.props.handleSuccess(); - }, + getStatus() { + const { status } = this.props.edition; - getStatus(){ - let status = null; - if (this.props.edition.status.length > 0){ - let statusStr = this.props.edition.status.join(', ').replace(/_/g, ' '); - status = ; - if (this.props.edition.pending_new_owner && this.props.edition.acl.acl_withdraw_transfer){ - status = ( - - ); - } - } - return status; + return status.length ? ( + + ) : null; }, render() { - let { actionPanelButtonListType, edition, currentUser } = this.props; + const { actionPanelButtonListType, currentUser, edition, handleSuccess, whitelabel } = this.props; + return (
+ value={edition.edition_number + ' ' + getLangText('of') + ' ' + edition.num_editions} /> - + value={edition.owner} /> + {this.getStatus()} {/* `acl_view` is always available in `edition.acl`, therefore if it has no more than 1 key, we're hiding the `DetailProperty` actions as otherwise `AclInformation` would show up */} - 1}> + 1}> + edition={edition} + handleSuccess={handleSuccess} + whitelabel={whitelabel} />
@@ -360,4 +354,5 @@ let SpoolDetails = React.createClass({ } }); + export default Edition; diff --git a/js/components/ascribe_detail/edition_action_panel.js b/js/components/ascribe_detail/edition_action_panel.js index 71bf38fe..dd5c117b 100644 --- a/js/components/ascribe_detail/edition_action_panel.js +++ b/js/components/ascribe_detail/edition_action_panel.js @@ -36,9 +36,11 @@ import { getLangText } from '../../utils/lang_utils'; */ let EditionActionPanel = React.createClass({ propTypes: { + currentUser: React.PropTypes.object.isRequired, + edition: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object.isRequired, + actionPanelButtonListType: React.PropTypes.func, - edition: React.PropTypes.object, - currentUser: React.PropTypes.object, handleSuccess: React.PropTypes.func }, @@ -87,39 +89,42 @@ let EditionActionPanel = React.createClass({ handleSuccess(response) { this.refreshCollection(); - this.props.handleSuccess(); - if (response){ - let notification = new GlobalNotificationModel(response.notification, 'success'); + + if (response) { + const notification = new GlobalNotificationModel(response.notification, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); } + + if (typeof this.props.handleSuccess === 'function') { + this.props.handleSuccess(); + } }, render() { const { actionPanelButtonListType: ActionPanelButtonListType, + currentUser, edition, - currentUser } = this.props; + whitelabel } = this.props; - if (edition && - edition.notifications && - edition.notifications.length > 0){ + if (edition.notifications && edition.notifications.length) { return ( ); - } - - else { + notifications={edition.notifications} + pieceOrEditions={[edition]} + handleSuccess={this.handleSuccess} />); + } else { return ( + whitelabel={whitelabel}> diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index d0adadf0..77ebed48 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -9,16 +9,12 @@ import { ResourceNotFoundError } from '../../models/errors'; import EditionActions from '../../actions/edition_actions'; import EditionStore from '../../stores/edition_store'; -import UserActions from '../../actions/user_actions'; -import UserStore from '../../stores/user_store'; - import Edition from './edition'; import AscribeSpinner from '../ascribe_spinner'; import { getLangText } from '../../utils/lang_utils'; import { setDocumentTitle } from '../../utils/dom_utils'; -import { mergeOptions } from '../../utils/general_utils'; /** @@ -28,24 +24,26 @@ let EditionContainer = React.createClass({ propTypes: { actionPanelButtonListType: React.PropTypes.func, furtherDetailsType: React.PropTypes.func, + + // Provided from AscribeApp + currentUser: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object.isRequired, + + // Provided from router + location: React.PropTypes.object, params: React.PropTypes.object }, mixins: [History, ReactError], getInitialState() { - return mergeOptions( - EditionStore.getInitialState(), - UserStore.getState() - ); + return EditionStore.getInitialState(); }, componentDidMount() { EditionStore.listen(this.onChange); - UserStore.listen(this.onChange); this.loadEdition(); - UserActions.fetchCurrentUser(); }, // This is done to update the container when the user clicks on the prev or next @@ -68,19 +66,10 @@ let EditionContainer = React.createClass({ componentWillUnmount() { window.clearInterval(this.state.timerId); EditionStore.unlisten(this.onChange); - UserStore.unlisten(this.onChange); }, onChange(state) { this.setState(state); - - if(state && state.edition && state.edition.digital_work) { - let isEncoding = state.edition.digital_work.isEncoding; - if (state.edition.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { - let timerId = window.setInterval(() => EditionActions.fetchEdition(this.props.params.editionId), 10000); - this.setState({timerId: timerId}); - } - } }, loadEdition(editionId = this.props.params.editionId) { @@ -88,8 +77,8 @@ let EditionContainer = React.createClass({ }, render() { - const { edition, currentUser, coaMeta } = this.state; - const { actionPanelButtonListType, furtherDetailsType } = this.props; + const { actionPanelButtonListType, currentUser, furtherDetailsType, whitelabel } = this.props; + const { edition, coaMeta } = this.state; if (edition.id) { setDocumentTitle(`${edition.artist_name}, ${edition.title}`); @@ -97,11 +86,12 @@ let EditionContainer = React.createClass({ return ( + edition={edition} + furtherDetailsType={furtherDetailsType} + loadEdition={this.loadEdition} + whitelabel={whitelabel} /> ); } else { return ( diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index 00ca9164..1e9ba0a1 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -22,12 +22,14 @@ const EMBED_IFRAME_HEIGHT = { video: 315, audio: 62 }; +const ENCODE_UPDATE_TIME = 5000; let MediaContainer = React.createClass({ propTypes: { - content: React.PropTypes.object, - currentUser: React.PropTypes.object, - refreshObject: React.PropTypes.func + content: React.PropTypes.object.isRequired, + refreshObject: React.PropTypes.func.isRequired, + + currentUser: React.PropTypes.object }, getInitialState() { @@ -37,14 +39,16 @@ let MediaContainer = React.createClass({ }, componentDidMount() { - if (!this.props.content.digital_work) { - return; - } + const { content: { digital_work: digitalWork }, refreshObject } = this.props; - const isEncoding = this.props.content.digital_work.isEncoding; - if (this.props.content.digital_work.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { - let timerId = window.setInterval(this.props.refreshObject, 10000); - this.setState({timerId: timerId}); + if (digitalWork) { + const isEncoding = digitalWork.isEncoding; + + if (digitalWork.mime === 'video' && typeof isEncoding === 'number' && isEncoding !== 100 && !this.state.timerId) { + this.setState({ + timerId: window.setInterval(refreshObject, ENCODE_UPDATE_TIME) + }); + } } }, @@ -105,7 +109,7 @@ let MediaContainer = React.createClass({ {''} - }/> + } /> ); } return ( @@ -136,7 +140,7 @@ let MediaContainer = React.createClass({ If it turns out that `fileExtension` is an empty string, we're just using the label 'file'. */} - {getLangText('Download')} .{fileExtension || 'file'} + {getLangText('Download')} .{fileExtension || 'file'} {embed} diff --git a/js/components/ascribe_detail/note.js b/js/components/ascribe_detail/note.js index c739b937..693a0400 100644 --- a/js/components/ascribe_detail/note.js +++ b/js/components/ascribe_detail/note.js @@ -2,64 +2,68 @@ import React from 'react'; +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 InputTextAreaToggable from './../ascribe_forms/input_textarea_toggable'; -import GlobalNotificationModel from '../../models/global_notification_model'; -import GlobalNotificationActions from '../../actions/global_notification_actions'; - import { getLangText } from '../../utils/lang_utils'; let Note = React.createClass({ propTypes: { - url: React.PropTypes.string, - id: React.PropTypes.func, - label: React.PropTypes.string, - currentUser: React.PropTypes.object, + currentUser: React.PropTypes.object.isRequired, + id: React.PropTypes.func.isRequired, + url: React.PropTypes.string.isRequired, + defaultValue: React.PropTypes.string, editable: React.PropTypes.bool, - show: React.PropTypes.bool, + label: React.PropTypes.string, placeholder: React.PropTypes.string, + show: React.PropTypes.bool, successMessage: React.PropTypes.string }, getDefaultProps() { return { editable: true, - show: true, placeholder: getLangText('Enter a note'), + show: true, successMessage: getLangText('Note saved') }; }, - showNotification(){ - let notification = new GlobalNotificationModel(this.props.successMessage, 'success'); + showNotification() { + const notification = new GlobalNotificationModel(this.props.successMessage, 'success'); GlobalNotificationActions.appendGlobalNotification(notification); }, render() { - if ((!!this.props.currentUser.username && this.props.editable || !this.props.editable ) && this.props.show) { + const { currentUser, defaultValue, editable, id, label, placeholder, show, url } = this.props; + + if ((!!currentUser.username && editable || !editable ) && show) { return (
+ disabled={!editable}> + label={label}> + defaultValue={defaultValue} + placeholder={placeholder} />
); + } else { + return null; } - return null; } }); -export default Note; \ No newline at end of file +export default Note; diff --git a/js/components/ascribe_detail/piece.js b/js/components/ascribe_detail/piece.js index e4ff4ea7..9864cf95 100644 --- a/js/components/ascribe_detail/piece.js +++ b/js/components/ascribe_detail/piece.js @@ -15,19 +15,19 @@ import MediaContainer from './media_container'; */ let Piece = React.createClass({ propTypes: { - piece: React.PropTypes.object, + piece: React.PropTypes.object.isRequired, + + buttons: React.PropTypes.object, currentUser: React.PropTypes.object, header: React.PropTypes.object, subheader: React.PropTypes.object, - buttons: React.PropTypes.object, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element ]) }, - - updateObject() { + updatePiece() { return PieceActions.fetchPiece(this.props.piece.id); }, @@ -40,7 +40,7 @@ let Piece = React.createClass({ + refreshObject={this.updatePiece} /> {header} diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8ee3111f..0f9df8f6 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -7,39 +7,35 @@ import Moment from 'moment'; import ReactError from '../../mixins/react_error'; import { ResourceNotFoundError } from '../../models/errors'; +import EditionListActions from '../../actions/edition_list_actions'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + import PieceActions from '../../actions/piece_actions'; import PieceStore from '../../stores/piece_store'; import PieceListActions from '../../actions/piece_list_actions'; import PieceListStore from '../../stores/piece_list_store'; -import UserActions from '../../actions/user_actions'; -import UserStore from '../../stores/user_store'; - -import EditionListActions from '../../actions/edition_list_actions'; - -import Piece from './piece'; -import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; import FurtherDetails from './further_details'; - import DetailProperty from './detail_property'; -import LicenseDetail from './license_detail'; import HistoryIterator from './history_iterator'; +import LicenseDetail from './license_detail'; +import Note from './note'; +import Piece from './piece'; import AclButtonList from './../ascribe_buttons/acl_button_list'; -import CreateEditionsForm from '../ascribe_forms/create_editions_form'; +import AclInformation from '../ascribe_buttons/acl_information'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; import DeleteButton from '../ascribe_buttons/delete_button'; -import AclInformation from '../ascribe_buttons/acl_information'; -import AclProxy from '../acl_proxy'; +import CollapsibleParagraph from './../ascribe_collapsible/collapsible_paragraph'; +import CreateEditionsForm from '../ascribe_forms/create_editions_form'; import ListRequestActions from '../ascribe_forms/list_form_request_actions'; -import GlobalNotificationModel from '../../models/global_notification_model'; -import GlobalNotificationActions from '../../actions/global_notification_actions'; - -import Note from './note'; +import AclProxy from '../acl_proxy'; import ApiUrls from '../../constants/api_urls'; import AscribeSpinner from '../ascribe_spinner'; @@ -54,6 +50,13 @@ import { setDocumentTitle } from '../../utils/dom_utils'; let PieceContainer = React.createClass({ propTypes: { furtherDetailsType: React.PropTypes.func, + + // Provided from AscribeApp + currentUser: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object, + + // Provided from router + location: React.PropTypes.object, params: React.PropTypes.object }, @@ -67,7 +70,6 @@ let PieceContainer = React.createClass({ getInitialState() { return mergeOptions( - UserStore.getState(), PieceListStore.getState(), PieceStore.getInitialState(), { @@ -77,12 +79,10 @@ let PieceContainer = React.createClass({ }, componentDidMount() { - UserStore.listen(this.onChange); PieceListStore.listen(this.onChange); PieceStore.listen(this.onChange); this.loadPiece(); - UserActions.fetchCurrentUser(); }, // This is done to update the container when the user clicks on the prev or next @@ -105,7 +105,6 @@ let PieceContainer = React.createClass({ componentWillUnmount() { PieceStore.unlisten(this.onChange); - UserStore.unlisten(this.onChange); PieceListStore.unlisten(this.onChange); }, @@ -207,15 +206,17 @@ let PieceContainer = React.createClass({ }, getActions() { - const { piece, currentUser } = this.state; + const { piece } = this.state; + const { currentUser } = this.props; if (piece.notifications && piece.notifications.length > 0) { return ( ); + notifications={piece.notifications} + pieceOrEditions={piece} /> + ); } else { return ( ); } else if (action === 'acl_loan_request') { @@ -122,7 +123,7 @@ let AclFormFactory = React.createClass({ message={formMessage} id={this.getFormDataId()} url={this.isPiece() ? ApiUrls.ownership_shares_pieces - : ApiUrls.ownership_shares_editions} + : ApiUrls.ownership_shares_editions} handleSuccess={showNotification ? this.showSuccessNotification : handleSuccess} /> ); } else { diff --git a/js/components/ascribe_forms/form_copyright_association.js b/js/components/ascribe_forms/form_copyright_association.js index 124a980a..f9b68f48 100644 --- a/js/components/ascribe_forms/form_copyright_association.js +++ b/js/components/ascribe_forms/form_copyright_association.js @@ -15,30 +15,30 @@ import { getLangText } from '../../utils/lang_utils'; let CopyrightAssociationForm = React.createClass({ propTypes: { - currentUser: React.PropTypes.object + currentUser: React.PropTypes.object.isRequired }, - handleSubmitSuccess(){ - let notification = getLangText('Copyright association updated'); - notification = new GlobalNotificationModel(notification, 'success', 10000); + handleSubmitSuccess() { + const notification = new GlobalNotificationModel(getLangText('Copyright association updated'), 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); }, - getProfileFormData(){ - return {email: this.props.currentUser.email}; + getProfileFormData() { + return { email: this.props.currentUser.email }; }, render() { - let selectedState; - let selectDefaultValue = ' -- ' + getLangText('select an association') + ' -- '; + const { currentUser } = this.props; + const selectDefaultValue = ' -- ' + getLangText('select an association') + ' -- '; - if (this.props.currentUser && this.props.currentUser.profile - && this.props.currentUser.profile.copyright_association) { - selectedState = AppConstants.copyrightAssociations.indexOf(this.props.currentUser.profile.copyright_association); - selectedState = selectedState !== -1 ? AppConstants.copyrightAssociations[selectedState] : selectDefaultValue; + let selectedState = selectDefaultValue; + if (currentUser.profile && currentUser.profile.copyright_association) { + if (AppConstants.copyrightAssociations.indexOf(currentUser.profile.copyright_association) !== -1) { + selectedState = AppConstants.copyrightAssociations[selectedState]; + } } - if (this.props.currentUser && this.props.currentUser.email){ + if (currentUser.email) { return (
); + } else { + return null; } - return null; } }); diff --git a/js/components/ascribe_forms/form_login.js b/js/components/ascribe_forms/form_login.js index a604850d..67069cac 100644 --- a/js/components/ascribe_forms/form_login.js +++ b/js/components/ascribe_forms/form_login.js @@ -6,7 +6,6 @@ import { History } 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'; @@ -23,8 +22,6 @@ let LoginForm = React.createClass({ propTypes: { headerMessage: React.PropTypes.string, submitMessage: React.PropTypes.string, - redirectOnLoggedIn: React.PropTypes.bool, - redirectOnLoginSuccess: React.PropTypes.bool, location: React.PropTypes.object }, @@ -32,40 +29,26 @@ let LoginForm = React.createClass({ getDefaultProps() { return { - headerMessage: getLangText('Enter ascribe'), - submitMessage: getLangText('Log in'), - redirectOnLoggedIn: true, - redirectOnLoginSuccess: true + headerMessage: getLangText('Enter') + ' ascribe', + submitMessage: getLangText('Log in') }; }, - getInitialState() { - return UserStore.getState(); - }, - - componentDidMount() { - UserStore.listen(this.onChange); - }, - - componentWillUnmount() { - UserStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - handleSuccess({ success }){ - let notification = new GlobalNotificationModel('Login successful', 'success', 10000); + handleSuccess({ success }) { + const notification = new GlobalNotificationModel(getLangText('Login successful'), 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); - if(success) { + if (success) { UserActions.fetchCurrentUser(true); } }, render() { - let email = this.props.location.query.email || null; + const { + headerMessage, + location: { query: { email: emailQuery } }, + submitMessage } = this.props; + return (
- {this.props.submitMessage} + {submitMessage} } spinner={ @@ -85,7 +68,7 @@ let LoginForm = React.createClass({ }>
-

{this.props.headerMessage}

+

{headerMessage}

- {this.props.submitMessage} - } + {submitMessage} + + } spinner={ - }> + }>
-

{this.props.headerMessage}

+

{headerMessage}

- {this.props.children} + {children} diff --git a/js/components/ascribe_forms/form_transfer.js b/js/components/ascribe_forms/form_transfer.js index 956d766c..245de177 100644 --- a/js/components/ascribe_forms/form_transfer.js +++ b/js/components/ascribe_forms/form_transfer.js @@ -18,26 +18,26 @@ import { getLangText } from '../../utils/lang_utils.js'; let TransferForm = React.createClass({ propTypes: { - url: React.PropTypes.string, - id: React.PropTypes.object, - message: React.PropTypes.string, - editions: React.PropTypes.array, - currentUser: React.PropTypes.object, - handleSuccess: React.PropTypes.func + id: React.PropTypes.object.isRequired, + url: React.PropTypes.string.isRequired, + + handleSuccess: React.PropTypes.func, + message: React.PropTypes.string }, - getFormData(){ + getFormData() { return this.props.id; }, render() { + const { handleSuccess, message, url } = this.props; return (

@@ -70,7 +70,7 @@ let TransferForm = React.createClass({ overrideForm={true}> diff --git a/js/components/ascribe_forms/list_form_request_actions.js b/js/components/ascribe_forms/list_form_request_actions.js index 4ec01b46..3aa61359 100644 --- a/js/components/ascribe_forms/list_form_request_actions.js +++ b/js/components/ascribe_forms/list_form_request_actions.js @@ -4,32 +4,35 @@ import React from 'react'; import RequestActionForm from './form_request_action'; let ListRequestActions = React.createClass({ - propTypes: { + notifications: React.PropTypes.array.isRequired, pieceOrEditions: React.PropTypes.oneOfType([ React.PropTypes.object, React.PropTypes.array ]).isRequired, + currentUser: React.PropTypes.object, - handleSuccess: React.PropTypes.func.isRequired, - notifications: React.PropTypes.array.isRequired + handleSuccess: React.PropTypes.func }, render () { - if (this.props.notifications && - this.props.notifications.length > 0) { + const { currentUser, handleSuccess, notifications, pieceOrEditions } = this.props; + + if (notifications.length) { return (

- {this.props.notifications.map((notification) => + {notifications.map((notification) => )} + pieceOrEditions={pieceOrEditions} /> + )}
); + } else { + return null; } - return null; } }); diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index 6d46a9d3..16238802 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -294,18 +294,18 @@ const Property = React.createClass({ }, getCheckbox() { - const { checkboxLabel } = this.props; + const { checkboxLabel, name } = this.props; - if(checkboxLabel) { + if (checkboxLabel) { return (
+ onChange={this.handleCheckboxToggle} + type="checkbox" /> {' ' + checkboxLabel}
); diff --git a/js/components/ascribe_modal/modal_wrapper.js b/js/components/ascribe_modal/modal_wrapper.js index 511e7f8c..53f1c90b 100644 --- a/js/components/ascribe_modal/modal_wrapper.js +++ b/js/components/ascribe_modal/modal_wrapper.js @@ -6,6 +6,10 @@ import Modal from 'react-bootstrap/lib/Modal'; let ModalWrapper = React.createClass({ propTypes: { + children: React.PropTypes.oneOfType([ + React.PropTypes.arrayOf(React.PropTypes.element), + React.PropTypes.element + ]).isRequired, title: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element, @@ -14,11 +18,7 @@ let ModalWrapper = React.createClass({ handleCancel: React.PropTypes.func, handleSuccess: React.PropTypes.func, - trigger: React.PropTypes.element, - children: React.PropTypes.oneOfType([ - React.PropTypes.arrayOf(React.PropTypes.element), - React.PropTypes.element - ]) + trigger: React.PropTypes.element }, getInitialState() { diff --git a/js/components/ascribe_routes/proxy_handler.js b/js/components/ascribe_routes/proxy_handler.js index 7752912a..52084e6b 100644 --- a/js/components/ascribe_routes/proxy_handler.js +++ b/js/components/ascribe_routes/proxy_handler.js @@ -5,7 +5,6 @@ import { RouteContext } from 'react-router'; import history from '../../history'; import UserStore from '../../stores/user_store'; -import UserActions from '../../actions/user_actions'; import AppConstants from '../../constants/application_constants'; @@ -21,8 +20,8 @@ const WHEN_ENUM = ['loggedIn', 'loggedOut']; export function AuthRedirect({to, when}) { // validate `when`, must be contained in `WHEN_ENUM`. // Throw an error otherwise. - if(WHEN_ENUM.indexOf(when) === -1) { - let whenValues = WHEN_ENUM.join(', '); + if (WHEN_ENUM.indexOf(when) === -1) { + const whenValues = WHEN_ENUM.join(', '); throw new Error(`"when" must be one of: [${whenValues}] got "${when}" instead`); } @@ -35,23 +34,22 @@ export function AuthRedirect({to, when}) { // // So if when === 'loggedIn', we're checking if the user is logged in (and // vice versa) - let exprToValidate = when === 'loggedIn' ? currentUser && currentUser.email - : currentUser && !currentUser.email; + const isLoggedIn = Object.keys(currentUser).length && currentUser.email; + const exprToValidate = when === 'loggedIn' ? isLoggedIn : !isLoggedIn; // and redirect if `true`. - if(exprToValidate) { + if (exprToValidate) { window.setTimeout(() => history.replace({ query, pathname: to })); return true; // Otherwise there can also be the case that the backend // wants to redirect the user to a specific route when the user is logged out already - } else if(!exprToValidate && when === 'loggedIn' && redirect) { - + } else if (!exprToValidate && when === 'loggedIn' && redirect) { delete query.redirect; window.setTimeout(() => history.replace({ query, pathname: '/' + redirect })); return true; - } else if(!exprToValidate && when === 'loggedOut' && redirectAuthenticated) { + } else if (!exprToValidate && when === 'loggedOut' && redirectAuthenticated) { /* * redirectAuthenticated contains an arbitrary path * eg pieces/, editions/, collection, settings, ... @@ -64,6 +62,7 @@ export function AuthRedirect({to, when}) { window.location = AppConstants.baseUrl + redirectAuthenticated; return true; } + return false; }; } @@ -81,6 +80,11 @@ export function ProxyHandler(...redirectFunctions) { displayName: 'ProxyHandler', propTypes: { + // Provided from AscribeApp + currentUser: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object, + + // Provided from router location: object }, @@ -88,43 +92,33 @@ export function ProxyHandler(...redirectFunctions) { // to use the `Lifecycle` widget in further down nested components mixins: [RouteContext], - getInitialState() { - return UserStore.getState(); - }, - componentDidMount() { - UserStore.listen(this.onChange); - UserActions.fetchCurrentUser(); + this.evaluateRedirectFunctions(); }, - componentDidUpdate() { - if(!UserStore.isLoading()) { - const { currentUser } = this.state; - const { query } = this.props.location; + componentWillReceiveProps(nextProps) { + this.evaluateRedirectFunctions(nextProps); + }, - for(let i = 0; i < redirectFunctions.length; i++) { + evaluateRedirectFunctions(props = this.props) { + const { currentUser, location: { query } } = props; + + if (UserStore.hasLoaded() && !UserStore.isLoading()) { + for (let i = 0; i < redirectFunctions.length; i++) { // if a redirectFunction redirects the user, // it should return `true` and therefore // stop/avoid the execution of all functions // that follow - if(redirectFunctions[i](currentUser, query)) { + if (redirectFunctions[i](currentUser, query)) { break; } } } }, - componentWillUnmount() { - UserStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - render() { return ( - + ); } }); diff --git a/js/components/ascribe_settings/account_settings.js b/js/components/ascribe_settings/account_settings.js index 57d66171..c8d4d64a 100644 --- a/js/components/ascribe_settings/account_settings.js +++ b/js/components/ascribe_settings/account_settings.js @@ -26,21 +26,23 @@ let AccountSettings = React.createClass({ whitelabel: React.PropTypes.object.isRequired }, - handleSuccess(){ + handleSuccess() { this.props.loadUser(true); - let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000); + + const notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); }, - getFormDataProfile(){ - return {'email': this.props.currentUser.email}; + getFormDataProfile() { + return { 'email': this.props.currentUser.email }; }, render() { - let content = ; + const { currentUser, whitelabel } = this.props; + let content = ; let profile = null; - if (this.props.currentUser.username) { + if (currentUser.username) { content = (
@@ -61,7 +63,7 @@ let AccountSettings = React.createClass({ editable={false}>
@@ -70,7 +72,7 @@ let AccountSettings = React.createClass({ ); profile = ( + defaultChecked={currentUser.profile.hash_locally}> {' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')} @@ -96,9 +98,9 @@ let AccountSettings = React.createClass({ defaultExpanded={true}> {content} - + {profile} diff --git a/js/components/ascribe_settings/contract_settings.js b/js/components/ascribe_settings/contract_settings.js index be723295..37305381 100644 --- a/js/components/ascribe_settings/contract_settings.js +++ b/js/components/ascribe_settings/contract_settings.js @@ -8,12 +8,6 @@ import CreateContractForm from '../ascribe_forms/form_create_contract'; import ContractListStore from '../../stores/contract_list_store'; import ContractListActions from '../../actions/contract_list_actions'; -import UserStore from '../../stores/user_store'; -import UserActions from '../../actions/user_actions'; - -import WhitelabelStore from '../../stores/whitelabel_store'; -import WhitelabelActions from '../../actions/whitelabel_actions'; - import ActionPanel from '../ascribe_panel/action_panel'; import ContractSettingsUpdateButton from './contract_settings_update_button'; @@ -24,30 +18,29 @@ import AclProxy from '../acl_proxy'; import { getLangText } from '../../utils/lang_utils'; import { setDocumentTitle } from '../../utils/dom_utils'; -import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils'; +import { truncateTextAtCharIndex } from '../../utils/general_utils'; let ContractSettings = React.createClass({ + propTypes: { + // Provided from AscribeApp + currentUser: React.PropTypes.object.isRequired, + whitelabel: React.PropTypes.object.isRequired, + + // Provided from router + location: React.PropTypes.object + }, + getInitialState() { - return mergeOptions( - ContractListStore.getState(), - UserStore.getState() - ); + return ContractListStore.getState(); }, componentDidMount() { ContractListStore.listen(this.onChange); - UserStore.listen(this.onChange); - WhitelabelStore.listen(this.onChange); - - WhitelabelActions.fetchWhitelabel(); - UserActions.fetchCurrentUser(); ContractListActions.fetchContractList(true); }, componentWillUnmount() { - WhitelabelStore.unlisten(this.onChange); - UserStore.unlisten(this.onChange); ContractListStore.unlisten(this.onChange); }, @@ -79,6 +72,7 @@ let ContractSettings = React.createClass({ }, render() { + const { currentUser, location, whitelabel } = this.props; const publicContracts = this.getPublicContracts(); const privateContracts = this.getPrivateContracts(); let createPublicContractForm = null; @@ -88,11 +82,11 @@ let ContractSettings = React.createClass({ if (publicContracts.length === 0) { createPublicContractForm = ( + }} + isPublic={true} /> ); } @@ -103,7 +97,7 @@ let ContractSettings = React.createClass({ defaultExpanded={true}> + aclObject={currentUser.acl}>
{createPublicContractForm} {publicContracts.map((contract, i) => { @@ -115,10 +109,9 @@ let ContractSettings = React.createClass({ buttons={
- + + aclObject={currentUser.acl}>
+ fileClassToUpload={{ + singular: getLangText('new contract'), + plural: getLangText('new contracts') + }} + isPublic={false} /> {privateContracts.map((contract, i) => { return ( - + - {this.props.children} + whitelabel={whitelabel} /> + {children} diff --git a/js/components/coa_verify_container.js b/js/components/coa_verify_container.js index 4a270cf4..f0649196 100644 --- a/js/components/coa_verify_container.js +++ b/js/components/coa_verify_container.js @@ -18,6 +18,11 @@ import { setDocumentTitle } from '../utils/dom_utils'; let CoaVerifyContainer = React.createClass({ propTypes: { + // Provided from AscribeApp + currentUser: React.PropTypes.object, + whitelabel: React.PropTypes.object, + + // Provided from router location: React.PropTypes.object }, diff --git a/js/components/error_not_found_page.js b/js/components/error_not_found_page.js index 4ea0f4d1..046c07a0 100644 --- a/js/components/error_not_found_page.js +++ b/js/components/error_not_found_page.js @@ -8,7 +8,14 @@ import { getLangText } from '../utils/lang_utils'; let ErrorNotFoundPage = React.createClass({ propTypes: { - message: React.PropTypes.string + message: React.PropTypes.string, + + // Provided from AscribeApp + currentUser: React.PropTypes.object, + whitelabel: React.PropTypes.object, + + // Provided from router + location: React.PropTypes.object }, mixins: [History], diff --git a/js/components/header.js b/js/components/header.js index 2587c92c..ed04a736 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -14,41 +14,28 @@ import NavItem from 'react-bootstrap/lib/NavItem'; import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; -import AclProxy from './acl_proxy'; - import EventActions from '../actions/event_actions'; import PieceListStore from '../stores/piece_list_store'; -import UserActions from '../actions/user_actions'; -import UserStore from '../stores/user_store'; - -import WhitelabelActions from '../actions/whitelabel_actions'; -import WhitelabelStore from '../stores/whitelabel_store'; - +import AclProxy from './acl_proxy'; import HeaderNotifications from './header_notification'; - import HeaderNotificationDebug from './header_notification_debug'; - import NavRoutesLinks from './nav_routes_links'; -import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; - import { constructHead } from '../utils/dom_utils'; let Header = React.createClass({ propTypes: { - routes: React.PropTypes.arrayOf(React.PropTypes.object) + currentUser: React.PropTypes.object.isRequired, + routes: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + whitelabel: React.PropTypes.object.isRequired }, getInitialState() { - return mergeOptions( - PieceListStore.getState(), - WhitelabelStore.getState(), - UserStore.getState() - ); + return PieceListStore.getState(); }, componentDidMount() { @@ -56,35 +43,14 @@ let Header = React.createClass({ // conflicts with routes that may need to wait to load the piece list PieceListStore.listen(this.onChange); - UserStore.listen(this.onChange); - UserActions.fetchCurrentUser.defer(); - - WhitelabelStore.listen(this.onChange); - WhitelabelActions.fetchWhitelabel.defer(); - // react-bootstrap 0.25.1 has a bug in which it doesn't // close the mobile expanded navigation after a click by itself. // To get rid of this, we set the state of the component ourselves. history.listen(this.onRouteChange); - - if (this.state.currentUser && this.state.currentUser.email) { - EventActions.profileDidLoad.defer(this.state.currentUser); - } - }, - - componentWillUpdate(nextProps, nextState) { - const { currentUser: { email: curEmail } = {} } = this.state; - const { currentUser: { email: nextEmail } = {} } = nextState; - - if (nextEmail && curEmail !== nextEmail) { - EventActions.profileDidLoad.defer(nextState.currentUser); - } }, componentWillUnmount() { PieceListStore.unlisten(this.onChange); - UserStore.unlisten(this.onChange); - WhitelabelStore.unlisten(this.onChange); //history.unlisten(this.onRouteChange); }, @@ -93,7 +59,7 @@ let Header = React.createClass({ }, getLogo() { - let { whitelabel } = this.state; + const { whitelabel } = this.props; if (whitelabel.head) { constructHead(whitelabel.head); @@ -117,7 +83,7 @@ let Header = React.createClass({ getPoweredBy() { return (
  • @@ -164,7 +130,9 @@ let Header = React.createClass({ }, render() { - const { currentUser, unfilteredPieceListCount } = this.state; + const { currentUser, routes } = this.props; + const { unfilteredPieceListCount } = this.state; + let account; let signup; let navRoutesLinks; @@ -179,8 +147,7 @@ let Header = React.createClass({ - + {getLangText('Account Settings')} @@ -190,17 +157,14 @@ let Header = React.createClass({ - + {getLangText('Contract Settings')} - - + + {getLangText('Log out')} @@ -217,21 +181,19 @@ let Header = React.createClass({ navbar right hasPieces={!!unfilteredPieceListCount} - routes={this.props.routes} + routes={routes} userAcl={currentUser.acl} /> ); } else { account = ( - + {getLangText('LOGIN')} ); signup = ( - + {getLangText('SIGNUP')} @@ -247,13 +209,12 @@ let Header = React.createClass({ toggleNavKey={0} fixedTop={true} className="hidden-print"> - + diff --git a/js/components/header_notification.js b/js/components/header_notification.js index 784f8786..fe50e150 100644 --- a/js/components/header_notification.js +++ b/js/components/header_notification.js @@ -11,16 +11,12 @@ import Nav from 'react-bootstrap/lib/Nav'; import NotificationActions from '../actions/notification_actions'; import NotificationStore from '../stores/notification_store'; -import { mergeOptions } from '../utils/general_utils'; import { getLangText } from '../utils/lang_utils'; let HeaderNotifications = React.createClass({ - getInitialState() { - return mergeOptions( - NotificationStore.getState() - ); + return NotificationStore.getState(); }, componentDidMount() { @@ -62,7 +58,7 @@ let HeaderNotifications = React.createClass({ this.refs.dropdownbutton.setDropdownState(false); }, - getPieceNotifications(){ + getPieceNotifications() { if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) { return (
    @@ -87,7 +83,7 @@ let HeaderNotifications = React.createClass({ return null; }, - getEditionNotifications(){ + getEditionNotifications() { if (this.state.editionListNotifications && this.state.editionListNotifications.length > 0) { return (
    @@ -114,7 +110,7 @@ let HeaderNotifications = React.createClass({ render() { if ((this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) || - (this.state.editionListNotifications && this.state.editionListNotifications.length > 0)){ + (this.state.editionListNotifications && this.state.editionListNotifications.length > 0)) { let numNotifications = 0; if (this.state.pieceListNotifications && this.state.pieceListNotifications.length > 0) { numNotifications += this.state.pieceListNotifications.length; @@ -125,7 +121,7 @@ let HeaderNotifications = React.createClass({ return (