1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 01:25:17 +01:00

Merge remote-tracking branch 'origin/master' into AD-1080-restyle-webapp-with-new-corporate-identity

Conflicts:
	js/components/ascribe_accordion_list/accordion_list_item_piece.js
	js/components/ascribe_app.js
	js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js
	js/components/logout_container.js
	js/components/whitelabel/wallet/wallet_app.js
This commit is contained in:
Tim Daubenschütz 2015-10-19 10:30:14 +02:00
commit f0d8e32ecf
76 changed files with 1126 additions and 836 deletions

View File

@ -5,6 +5,7 @@ import Q from 'q';
import PieceListFetcher from '../fetchers/piece_list_fetcher'; import PieceListFetcher from '../fetchers/piece_list_fetcher';
class PieceListActions { class PieceListActions {
constructor() { constructor() {
this.generateActions( this.generateActions(
@ -21,17 +22,16 @@ class PieceListActions {
this.actions.updatePieceList({ this.actions.updatePieceList({
page, page,
pageSize, pageSize,
search,
orderBy, orderBy,
orderAsc, orderAsc,
filterBy, filterBy,
search: '',
pieceList: [], pieceList: [],
pieceListCount: -1, pieceListCount: -1,
unfilteredPieceListCount: -1 unfilteredPieceListCount: -1
}); });
// afterwards, we can load the list // afterwards, we can load the list
return Q.Promise((resolve, reject) => { return Q.Promise((resolve, reject) => {
PieceListFetcher PieceListFetcher
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy) .fetch(page, pageSize, search, orderBy, orderAsc, filterBy)

View File

@ -13,7 +13,7 @@ class UserActions {
} }
fetchCurrentUser() { fetchCurrentUser() {
return UserFetcher.fetchOne() UserFetcher.fetchOne()
.then((res) => { .then((res) => {
this.actions.updateCurrentUser(res.users[0]); this.actions.updateCurrentUser(res.users[0]);
}) })
@ -24,7 +24,7 @@ class UserActions {
} }
logoutCurrentUser() { logoutCurrentUser() {
return UserFetcher.logout() UserFetcher.logout()
.then(() => { .then(() => {
this.actions.deleteCurrentUser(); this.actions.deleteCurrentUser();
}) })

View File

@ -3,7 +3,8 @@
require('babel/polyfill'); require('babel/polyfill');
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Router, Redirect } from 'react-router';
import history from './history';
/* eslint-disable */ /* eslint-disable */
import fetch from 'isomorphic-fetch'; import fetch from 'isomorphic-fetch';
@ -46,7 +47,6 @@ requests.defaults({
} }
}); });
class AppGateway { class AppGateway {
start() { start() {
let settings; let settings;
@ -68,22 +68,36 @@ class AppGateway {
load(settings) { load(settings) {
let type = 'default'; let type = 'default';
let subdomain = 'www'; let subdomain = 'www';
let redirectRoute = (<Redirect from="/" to="/collection" />);
if (settings) { if (settings) {
type = settings.type; type = settings.type;
subdomain = settings.subdomain; subdomain = settings.subdomain;
} }
// www and cc do not have a landing page
if(subdomain && subdomain !== 'cc') {
redirectRoute = null;
}
// Adds a client specific class to the body for whitelabel styling
window.document.body.classList.add('client--' + subdomain); window.document.body.classList.add('client--' + subdomain);
// Send the applicationWillBoot event to the third-party stores
EventActions.applicationWillBoot(settings); EventActions.applicationWillBoot(settings);
window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
React.render( // `history.listen` is called on every route change, which is perfect for
<App />, // us in that case.
document.getElementById('main') history.listen(EventActions.routeDidChange);
);
EventActions.routeDidChange(); React.render((
}); <Router history={history}>
{redirectRoute}
{getRoutes(type, subdomain)}
</Router>
), document.getElementById('main'));
// Send the applicationDidBoot event to the third-party stores
EventActions.applicationDidBoot(settings); EventActions.applicationDidBoot(settings);
} }
} }

View File

@ -9,21 +9,49 @@ let AccordionList = React.createClass({
className: React.PropTypes.string, className: React.PropTypes.string,
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired, children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
loadingElement: React.PropTypes.element, loadingElement: React.PropTypes.element,
count: React.PropTypes.number count: React.PropTypes.number,
itemList: React.PropTypes.arrayOf(React.PropTypes.object),
search: React.PropTypes.string,
searchFor: React.PropTypes.func
},
clearSearch() {
this.props.searchFor('');
}, },
render() { render() {
const { search } = this.props;
if(this.props.itemList && this.props.itemList.length > 0) { if(this.props.itemList && this.props.itemList.length > 0) {
return ( return (
<div className={this.props.className}> <div className={this.props.className}>
{this.props.children} {this.props.children}
</div> </div>
); );
} else if(this.props.count === 0) { } else if(this.props.count === 0 && !search) {
return ( return (
<div className="ascribe-accordion-list-placeholder"> <div className="ascribe-accordion-list-placeholder">
<p className="text-center">{getLangText('We could not find any works related to you...')}</p> <p className="text-center">
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p> {getLangText('We could not find any works related to you...')}
</p>
<p className="text-center">
{getLangText('To register one, click')}
<a href="register_piece">{getLangText('here')}</a>!
</p>
</div>
);
} else if(this.props.count === 0 && search) {
return (
<div className="ascribe-accordion-list-placeholder">
<p className="text-center">
{getLangText('We could not find any works related to you...')}
</p>
<p className="text-center">
{getLangText('You\'re filtering by the search keyword: \'%s\' ', search)}
</p>
<p className="text-center">
<button className="btn btn-sm btn-default" onClick={this.clearSearch}>{getLangText('Clear search')}</button>
</p>
</div> </div>
); );
} else { } else {

View File

@ -1,9 +1,8 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
let Link = Router.Link;
let AccordionListItem = React.createClass({ let AccordionListItem = React.createClass({
propTypes: { propTypes: {
@ -21,42 +20,50 @@ let AccordionListItem = React.createClass({
]) ])
}, },
mixins: [Router.Navigation],
render() { render() {
const { linkData,
className,
thumbnail,
heading,
subheading,
subsubheading,
buttons,
badge,
children } = this.props;
return ( return (
<div className="row"> <div className="row">
<div className={this.props.className}> <div className={className}>
<div className="wrapper"> <div className="wrapper">
<div className="pull-left"> <div className="pull-left">
<Link {...this.props.linkData()}> <Link to={linkData}>
<div className="thumbnail-wrapper"> <div className="thumbnail-wrapper">
{this.props.thumbnail} {thumbnail}
</div> </div>
</Link> </Link>
</div> </div>
<div className="accordion-list-item-header"> <div className="accordion-list-item-header">
<Link {...this.props.linkData()}> <Link to={linkData}>
{this.props.heading} {heading}
</Link> </Link>
<Link {...this.props.linkData()}> <Link to={linkData}>
{this.props.subheading} {subheading}
{this.props.subsubheading} {subsubheading}
</Link> </Link>
<div className="accordion-list-item-buttons"> <div className="accordion-list-item-buttons">
{this.props.buttons} {buttons}
</div> </div>
</div> </div>
<span style={{'clear': 'both'}}></span> <span style={{'clear': 'both'}}></span>
<div className="request-action-badge"> <div className="request-action-badge">
{this.props.badge} {badge}
</div> </div>
</div> </div>
</div> </div>
{this.props.children} {children}
</div> </div>
); );
} }

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import AccordionListItem from './accordion_list_item'; import AccordionListItem from './accordion_list_item';
@ -22,26 +22,15 @@ let AccordionListItemPiece = React.createClass({
badge: React.PropTypes.object badge: React.PropTypes.object
}, },
mixins: [Router.Navigation],
getLinkData() { getLinkData() {
let { piece } = this.props;
if(piece && piece.first_edition) {
return `/editions/${piece.first_edition.bitcoin_id}`;
if(this.props.piece && this.props.piece.first_edition) {
return {
to: 'edition',
params: {
editionId: this.props.piece.first_edition.bitcoin_id
}
};
} else { } else {
return { return `/pieces/${piece.id}`;
to: 'piece',
params: {
pieceId: this.props.piece.id
} }
};
}
}, },
render() { render() {
@ -59,7 +48,7 @@ let AccordionListItemPiece = React.createClass({
subsubheading={this.props.subsubheading} subsubheading={this.props.subsubheading}
buttons={this.props.buttons} buttons={this.props.buttons}
badge={this.props.badge} badge={this.props.badge}
linkData={this.getLinkData} linkData={this.getLinkData()}
> >
{this.props.children} {this.props.children}
</AccordionListItem> </AccordionListItem>

View File

@ -110,7 +110,7 @@ let AccordionListItemTableEditions = React.createClass({
showExpandOption = true; showExpandOption = true;
} }
let transition = new TransitionModel('edition', 'editionId', 'bitcoin_id', (e) => e.stopPropagation() ); let transition = new TransitionModel('editions', 'editionId', 'bitcoin_id', (e) => e.stopPropagation() );
let columnList = [ let columnList = [
new ColumnModel( new ColumnModel(

View File

@ -1,23 +1,30 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import Header from '../components/header'; import Header from '../components/header';
import Footer from '../components/footer'; import Footer from '../components/footer';
import GlobalNotification from './global_notification'; import GlobalNotification from './global_notification';
import getRoutes from '../routes';
let RouteHandler = Router.RouteHandler;
let AscribeApp = React.createClass({ let AscribeApp = React.createClass({
propTypes: {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
]),
routes: React.PropTypes.arrayOf(React.PropTypes.object)
},
render() { render() {
let { children, routes } = this.props;
return ( return (
<div className="container ascribe-default-app"> <div className="container ascribe-default-app">
<Header routes={getRoutes()} /> <Header routes={routes} />
{/* Routes are injected here */}
<div className="ascribe-body"> <div className="ascribe-body">
<RouteHandler /> {children}
</div> </div>
<Footer /> <Footer />
<GlobalNotification /> <GlobalNotification />

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import Button from 'react-bootstrap/lib/Button'; import Button from 'react-bootstrap/lib/Button';
@ -24,8 +23,6 @@ let DeleteButton = React.createClass({
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
mixins: [Router.Navigation],
render() { render() {
let availableAcls; let availableAcls;
let btnDelete; let btnDelete;

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link, History } from 'react-router';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
@ -22,7 +22,7 @@ import Form from './../ascribe_forms/form';
import Property from './../ascribe_forms/property'; import Property from './../ascribe_forms/property';
import EditionDetailProperty from './detail_property'; import EditionDetailProperty from './detail_property';
import LicenseDetail from './license_detail'; import LicenseDetail from './license_detail';
import EditionFurtherDetails from './further_details'; import FurtherDetails from './further_details';
import EditionActionPanel from './edition_action_panel'; import EditionActionPanel from './edition_action_panel';
@ -34,17 +34,18 @@ import AscribeSpinner from '../ascribe_spinner';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link;
/** /**
* This is the component that implements display-specific functionality * This is the component that implements display-specific functionality
*/ */
let Edition = React.createClass({ let Edition = React.createClass({
propTypes: { propTypes: {
edition: React.PropTypes.object, edition: React.PropTypes.object,
loadEdition: React.PropTypes.func loadEdition: React.PropTypes.func,
location: React.PropTypes.object
}, },
mixins: [Router.Navigation], mixins: [History],
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
@ -150,12 +151,13 @@ let Edition = React.createClass({
show={this.props.edition.acl.acl_edit show={this.props.edition.acl.acl_edit
|| Object.keys(this.props.edition.extra_data).length > 0 || Object.keys(this.props.edition.extra_data).length > 0
|| this.props.edition.other_data.length > 0}> || this.props.edition.other_data.length > 0}>
<EditionFurtherDetails <FurtherDetails
editable={this.props.edition.acl.acl_edit} editable={this.props.edition.acl.acl_edit}
pieceId={this.props.edition.parent} pieceId={this.props.edition.parent}
extraData={this.props.edition.extra_data} extraData={this.props.edition.extra_data}
otherData={this.props.edition.other_data} otherData={this.props.edition.other_data}
handleSuccess={this.props.loadEdition}/> handleSuccess={this.props.loadEdition}
location={this.props.location}/>
</CollapsibleParagraph> </CollapsibleParagraph>
<CollapsibleParagraph <CollapsibleParagraph
@ -260,7 +262,7 @@ let CoaDetails = React.createClass({
{getLangText('Download')} <Glyphicon glyph="cloud-download"/> {getLangText('Download')} <Glyphicon glyph="cloud-download"/>
</button> </button>
</a> </a>
<Link to="coa_verify"> <Link to="/coa_verify">
<button className="btn btn-default btn-xs"> <button className="btn btn-default btn-xs">
{getLangText('Verify')} <Glyphicon glyph="check"/> {getLangText('Verify')} <Glyphicon glyph="check"/>
</button> </button>

View File

@ -9,11 +9,14 @@ import Edition from './edition';
import AscribeSpinner from '../ascribe_spinner'; import AscribeSpinner from '../ascribe_spinner';
/** /**
* This is the component that implements resource/data specific functionality * This is the component that implements resource/data specific functionality
*/ */
let EditionContainer = React.createClass({ let EditionContainer = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return EditionStore.getState(); return EditionStore.getState();
}, },
@ -64,7 +67,8 @@ let EditionContainer = React.createClass({
return ( return (
<Edition <Edition
edition={this.state.edition} edition={this.state.edition}
loadEdition={this.loadEdition}/> loadEdition={this.loadEdition}
location={this.props.location}/>
); );
} else { } else {
return ( return (

View File

@ -23,7 +23,8 @@ let FurtherDetails = React.createClass({
pieceId: React.PropTypes.number, pieceId: React.PropTypes.number,
extraData: React.PropTypes.object, extraData: React.PropTypes.object,
otherData: React.PropTypes.arrayOf(React.PropTypes.object), otherData: React.PropTypes.arrayOf(React.PropTypes.object),
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func,
location: React.PropTypes.object
}, },
getInitialState() { getInitialState() {
@ -85,7 +86,8 @@ let FurtherDetails = React.createClass({
overrideForm={true} overrideForm={true}
pieceId={this.props.pieceId} pieceId={this.props.pieceId}
otherData={this.props.otherData} otherData={this.props.otherData}
multiple={true}/> multiple={true}
location={this.props.location}/>
</Form> </Form>
</Col> </Col>
</Row> </Row>

View File

@ -20,7 +20,8 @@ let FurtherDetailsFileuploader = React.createClass({
submitFile: React.PropTypes.func, submitFile: React.PropTypes.func,
isReadyForFormSubmission: React.PropTypes.func, isReadyForFormSubmission: React.PropTypes.func,
editable: React.PropTypes.bool, editable: React.PropTypes.bool,
multiple: React.PropTypes.bool multiple: React.PropTypes.bool,
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -88,7 +89,8 @@ let FurtherDetailsFileuploader = React.createClass({
}} }}
areAssetsDownloadable={true} areAssetsDownloadable={true}
areAssetsEditable={this.props.editable} areAssetsEditable={this.props.editable}
multiple={this.props.multiple}/> multiple={this.props.multiple}
location={this.props.location}/>
</Property> </Property>
); );
} }

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import PieceActions from '../../actions/piece_actions'; import PieceActions from '../../actions/piece_actions';
import PieceStore from '../../stores/piece_store'; import PieceStore from '../../stores/piece_store';
@ -44,8 +44,11 @@ import { getLangText } from '../../utils/lang_utils';
* This is the component that implements resource/data specific functionality * This is the component that implements resource/data specific functionality
*/ */
let PieceContainer = React.createClass({ let PieceContainer = React.createClass({
propTypes: {
location: React.PropTypes.object
},
mixins: [Router.Navigation], mixins: [History],
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
@ -133,7 +136,7 @@ let PieceContainer = React.createClass({
let notification = new GlobalNotificationModel(response.notification, 'success'); let notification = new GlobalNotificationModel(response.notification, 'success');
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces'); this.history.pushState(null, '/collection');
}, },
getCreateEditionsDialog() { getCreateEditionsDialog() {
@ -262,7 +265,8 @@ let PieceContainer = React.createClass({
pieceId={this.state.piece.id} pieceId={this.state.piece.id}
extraData={this.state.piece.extra_data} extraData={this.state.piece.extra_data}
otherData={this.state.piece.other_data} otherData={this.state.piece.other_data}
handleSuccess={this.loadPiece}/> handleSuccess={this.loadPiece}
location={this.props.location}/>
</CollapsibleParagraph> </CollapsibleParagraph>
</Piece> </Piece>

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import ContractListActions from '../../actions/contract_list_actions'; import ContractListActions from '../../actions/contract_list_actions';
import ContractListStore from '../../stores/contract_list_store'; import ContractListStore from '../../stores/contract_list_store';
@ -26,7 +26,7 @@ let ContractAgreementForm = React.createClass({
handleSuccess: React.PropTypes.func handleSuccess: React.PropTypes.func
}, },
mixins: [Router.Navigation, Router.State], mixins: [History],
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
@ -58,7 +58,8 @@ let ContractAgreementForm = React.createClass({
let notification = 'Contract agreement send'; let notification = 'Contract agreement send';
notification = new GlobalNotificationModel(notification, 'success', 10000); notification = new GlobalNotificationModel(notification, 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces');
this.history.pushState(null, '/collection');
}, },
getFormData(){ getFormData(){
@ -139,7 +140,7 @@ let ContractAgreementForm = React.createClass({
<div> <div>
<p className="text-center"> <p className="text-center">
{getLangText('No contracts uploaded yet, please go to the ')} {getLangText('No contracts uploaded yet, please go to the ')}
<a href="settings">{getLangText('settings page')}</a> <a href="contract_settings">{getLangText('contract settings page')}</a>
{getLangText(' and create them.')} {getLangText(' and create them.')}
</p> </p>
</div> </div>

View File

@ -28,7 +28,8 @@ let CreateContractForm = React.createClass({
fileClassToUpload: React.PropTypes.shape({ fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string, singular: React.PropTypes.string,
plural: React.PropTypes.string plural: React.PropTypes.string
}) }),
location: React.PropTypes.object
}, },
getInitialState() { getInitialState() {
@ -86,7 +87,8 @@ let CreateContractForm = React.createClass({
areAssetsEditable={true} areAssetsEditable={true}
setIsUploadReady={this.setIsUploadReady} setIsUploadReady={this.setIsUploadReady}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
fileClassToUpload={this.props.fileClassToUpload}/> fileClassToUpload={this.props.fileClassToUpload}
location={this.props.location}/>
</Property> </Property>
<Property <Property
name='name' name='name'

View File

@ -130,6 +130,9 @@ let LoanForm = React.createClass({
src={contract.blob.url_safe} src={contract.blob.url_safe}
alt="pdf" alt="pdf"
pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/> pluginspage="http://www.adobe.com/products/acrobat/readstep2.html"/>
<a href={contract.blob.url_safe} target="_blank">
<span className="glyphicon glyphicon-download" aria-hidden="true"></span> {getLangText('Download contract')}
</a>
{/* We still need to send the server information that we're accepting */} {/* We still need to send the server information that we're accepting */}
<InputCheckbox <InputCheckbox
style={{'display': 'none'}} style={{'display': 'none'}}

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
@ -25,10 +25,10 @@ let LoginForm = React.createClass({
submitMessage: React.PropTypes.string, submitMessage: React.PropTypes.string,
redirectOnLoggedIn: React.PropTypes.bool, redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool, redirectOnLoginSuccess: React.PropTypes.bool,
onLogin: React.PropTypes.func location: React.PropTypes.object
}, },
mixins: [Router.Navigation, Router.State], mixins: [History],
getDefaultProps() { getDefaultProps() {
return { return {
@ -45,10 +45,6 @@ let LoginForm = React.createClass({
componentDidMount() { componentDidMount() {
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
let { redirect } = this.getQuery();
if (redirect && redirect !== 'login'){
this.transitionTo(redirect, null, this.getQuery());
}
}, },
componentWillUnmount() { componentWillUnmount() {
@ -57,65 +53,19 @@ let LoginForm = React.createClass({
onChange(state) { onChange(state) {
this.setState(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) {
// FIXME: hack to redirect out of the dispatch cycle
let { redirectAuthenticated } = this.getQuery();
if ( redirectAuthenticated) {
/*
* redirectAuthenticated contains an arbirary path
* eg pieces/<id>, editions/<bitcoin_id>, collection, settings, ...
* hence transitionTo cannot be used directly
*/
window.location = AppConstants.baseUrl + redirectAuthenticated;
}
window.setTimeout(() => this.transitionTo('pieces'), 0);
}
}, },
handleSuccess(){ handleSuccess({ success }){
let notification = new GlobalNotificationModel('Login successful', 'success', 10000); let notification = new GlobalNotificationModel('Login successful', 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
// register_piece is waiting for a login success as login_container and it is wrapped if(success) {
// in a slides_container component. UserActions.fetchCurrentUser();
// 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()
.then(() => {
if(this.props.redirectOnLoginSuccess) {
/* 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
*/
let { redirectAuthenticated } = this.getQuery();
if ( redirectAuthenticated) {
window.location = AppConstants.baseUrl + redirectAuthenticated;
} }
else {
window.location = AppConstants.baseUrl + 'collection';
}
} else if(this.props.onLogin) {
// In some instances we want to give a callback to an outer container,
// to show that the one login action the user triggered actually went through.
// We can not do this by listening on a store's state as it wouldn't really tell us
// if the user did log in or was just fetching the user's data again
this.props.onLogin();
}
})
.catch((err) => {
console.logGlobal(err);
});
}, },
render() { render() {
let email = this.getQuery().email || null; let email = this.props.location.query.email || null;
return ( return (
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"

View File

@ -30,7 +30,8 @@ let RegisterPieceForm = React.createClass({
onLoggedOut: React.PropTypes.func, onLoggedOut: React.PropTypes.func,
// For this form to work with SlideContainer, we sometimes have to disable it // For this form to work with SlideContainer, we sometimes have to disable it
disabled: React.PropTypes.bool disabled: React.PropTypes.bool,
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -114,7 +115,8 @@ let RegisterPieceForm = React.createClass({
isFineUploaderActive={this.props.isFineUploaderActive} isFineUploaderActive={this.props.isFineUploaderActive}
onLoggedOut={this.props.onLoggedOut} onLoggedOut={this.props.onLoggedOut}
disabled={!this.props.isFineUploaderEditable} disabled={!this.props.isFineUploaderEditable}
enableLocalHashing={enableLocalHashing}/> enableLocalHashing={enableLocalHashing}
location={this.props.location}/>
</Property> </Property>
<Property <Property
name='artist_name' name='artist_name'

View File

@ -1,9 +1,10 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import UserStore from '../../stores/user_store'; import UserStore from '../../stores/user_store';
import UserActions from '../../actions/user_actions';
import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions'; import GlobalNotificationActions from '../../actions/global_notification_actions';
@ -12,7 +13,6 @@ import Form from './form';
import Property from './property'; import Property from './property';
import InputCheckbox from './input_checkbox'; import InputCheckbox from './input_checkbox';
import AppConstants from '../../constants/application_constants';
import ApiUrls from '../../constants/api_urls'; import ApiUrls from '../../constants/api_urls';
import AscribeSpinner from '../ascribe_spinner'; import AscribeSpinner from '../ascribe_spinner';
@ -20,15 +20,15 @@ import { getLangText } from '../../utils/lang_utils';
let SignupForm = React.createClass({ let SignupForm = React.createClass({
propTypes: { propTypes: {
headerMessage: React.PropTypes.string, headerMessage: React.PropTypes.string,
submitMessage: React.PropTypes.string, submitMessage: React.PropTypes.string,
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
children: React.PropTypes.element children: React.PropTypes.element,
location: React.PropTypes.object
}, },
mixins: [Router.Navigation, Router.State], mixins: [History],
getDefaultProps() { getDefaultProps() {
return { return {
@ -36,16 +36,13 @@ let SignupForm = React.createClass({
submitMessage: getLangText('Sign up') submitMessage: getLangText('Sign up')
}; };
}, },
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
}, },
componentDidMount() { componentDidMount() {
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
let { redirect } = this.getQuery();
if (redirect && redirect !== 'signup'){
this.transitionTo(redirect, null, this.getQuery());
}
}, },
componentWillUnmount() { componentWillUnmount() {
@ -54,45 +51,23 @@ let SignupForm = React.createClass({
onChange(state) { onChange(state) {
this.setState(state); this.setState(state);
// if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email) {
let { redirectAuthenticated } = this.getQuery();
if ( redirectAuthenticated) {
/*
* redirectAuthenticated contains an arbirary path
* eg pieces/<id>, editions/<bitcoin_id>, collection, settings, ...
* hence transitionTo cannot be used directly
*/
window.location = AppConstants.baseUrl + redirectAuthenticated;
}
window.setTimeout(() => this.transitionTo('pieces'));
}
}, },
handleSuccess(response){ handleSuccess(response) {
if (response.user) { if (response.user) {
let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000); let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
// Refactor this to its own component
this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.'); this.props.handleSuccess(getLangText('We sent an email to your address') + ' ' + response.user.email + ', ' + getLangText('please confirm') + '.');
} } else {
else if (response.redirect) { UserActions.fetchCurrentUser();
let { redirectAuthenticated } = this.getQuery();
if ( redirectAuthenticated) {
/*
* redirectAuthenticated contains an arbirary path
* eg pieces/<id>, editions/<bitcoin_id>, collection, settings, ...
* hence transitionTo cannot be used directly
*/
window.location = AppConstants.baseUrl + redirectAuthenticated;
}
this.transitionTo('pieces');
} }
}, },
getFormData() { getFormData() {
if (this.getQuery().token){ if (this.props.location.query.token){
return {token: this.getQuery().token}; return {token: this.props.location.query.token};
} }
return null; return null;
}, },
@ -101,7 +76,9 @@ let SignupForm = React.createClass({
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' + 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('This password is securing your digital property like a bank account') + '.\n ' +
getLangText('Store it in a safe place') + '!'; getLangText('Store it in a safe place') + '!';
let email = this.getQuery().email || null;
let email = this.props.location.query.email || null;
return ( return (
<Form <Form
className="ascribe-form-bordered" className="ascribe-form-bordered"

View File

@ -46,7 +46,8 @@ let InputFineUploader = React.createClass({
fileClassToUpload: React.PropTypes.shape({ fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string, singular: React.PropTypes.string,
plural: React.PropTypes.string plural: React.PropTypes.string
}) }),
location: React.PropTypes.object
}, },
getInitialState() { getInitialState() {
@ -106,7 +107,8 @@ let InputFineUploader = React.createClass({
}} }}
onInactive={this.props.onLoggedOut} onInactive={this.props.onLoggedOut}
enableLocalHashing={this.props.enableLocalHashing} enableLocalHashing={this.props.enableLocalHashing}
fileClassToUpload={this.props.fileClassToUpload}/> fileClassToUpload={this.props.fileClassToUpload}
location={this.props.location}/>
); );
} }
}); });

View File

@ -1,12 +1,11 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import { getLangText } from '../../utils/lang_utils'; import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link;
let PaginationButton = React.createClass({ let PaginationButton = React.createClass({
propTypes: { propTypes: {
@ -44,7 +43,7 @@ let PaginationButton = React.createClass({
if (this.isInRange(page)) { if (this.isInRange(page)) {
anchor = ( anchor = (
<Link to="pieces" <Link to="/collection"
query={{page}} query={{page}}
onClick={this.props.goToPage(page)}> onClick={this.props.goToPage(page)}>
{directionDisplay} {directionDisplay}

View File

@ -4,16 +4,16 @@ import React from 'react';
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget'; import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
import PieceListToolbarOrderWidget from './piece_list_toolbar_order_widget'; import PieceListToolbarOrderWidget from './piece_list_toolbar_order_widget';
import SearchBar from '../search_bar';
import AppConstants from '../../constants/application_constants';
import Input from 'react-bootstrap/lib/Input';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import { getLangText } from '../../utils/lang_utils';
let PieceListToolbar = React.createClass({ let PieceListToolbar = React.createClass({
propTypes: { propTypes: {
className: React.PropTypes.string, className: React.PropTypes.string,
searchFor: React.PropTypes.func, searchFor: React.PropTypes.func,
searchQuery: React.PropTypes.string,
filterParams: React.PropTypes.arrayOf( filterParams: React.PropTypes.arrayOf(
React.PropTypes.shape({ React.PropTypes.shape({
label: React.PropTypes.string, label: React.PropTypes.string,
@ -39,11 +39,6 @@ let PieceListToolbar = React.createClass({
]) ])
}, },
searchFor() {
let searchTerm = this.refs.search.getInputDOMNode().value;
this.props.searchFor(searchTerm);
},
getFilterWidget(){ getFilterWidget(){
if (this.props.filterParams){ if (this.props.filterParams){
return ( return (
@ -55,6 +50,7 @@ let PieceListToolbar = React.createClass({
} }
return null; return null;
}, },
getOrderWidget(){ getOrderWidget(){
if (this.props.orderParams){ if (this.props.orderParams){
return ( return (
@ -68,29 +64,25 @@ let PieceListToolbar = React.createClass({
}, },
render() { render() {
let searchIcon = <span className="ascribe-icon icon-ascribe-search"/>; const { className, children, searchFor, searchQuery } = this.props;
return ( return (
<div className={this.props.className}> <div className={className}>
<div className="row"> <div className="row">
<div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2"> <div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<div className="row"> <div className="row">
<span className="pull-left"> <span className="pull-left">
{this.props.children} {children}
</span> </span>
<span className="pull-right"> <span className="pull-right">
{this.getOrderWidget()} {this.getOrderWidget()}
{this.getFilterWidget()} {this.getFilterWidget()}
</span> </span>
<span className="pull-right search-bar ascribe-input-glyph"> <SearchBar
<Input className="pull-right search-bar ascribe-input-glyph"
type='text' searchFor={searchFor}
ref="search" searchQuery={searchQuery}
placeholder={getLangText('Search%s', '...')} threshold={AppConstants.searchThreshold}/>
onChange={this.searchFor}
addonAfter={searchIcon} />
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,107 @@
'use strict';
import React from 'react';
import { History } from 'react-router';
import UserStore from '../../../stores/user_store';
import UserActions from '../../../actions/user_actions';
import AppConstants from '../../../constants/application_constants';
const { object } = React.PropTypes;
const WHEN_ENUM = ['loggedIn', 'loggedOut'];
/**
* Can be used in combination with `Route` as an intermediate Handler
* between the actual component we want to display dependent on a certain state
* that is required to display that component.
*
* @param {string} options.to Any type of route path that is defined in routes.js
* @param {enum/string} options.when ('loggedIn' || 'loggedOut')
*/
export default function AuthProxyHandler({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(', ');
throw new Error(`"when" must be one of: [${whenValues}] got "${when}" instead`);
}
return (Component) => {
return React.createClass({
propTypes: {
location: object
},
mixins: [History],
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentDidUpdate() {
this.redirectConditionally();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
redirectConditionally() {
const { query } = this.props.location;
const { redirectAuthenticated, redirect } = query;
// The user of this handler specifies with `when`, what kind of status
// needs to be checked to conditionally do - if that state is `true` -
// a redirect.
//
// So if when === 'loggedIn', we're checking if the user is logged in (and
// vice versa)
let exprToValidate = when === 'loggedIn' ?
this.state.currentUser && this.state.currentUser.email :
this.state.currentUser && !this.state.currentUser.email;
// and redirect if `true`.
if(exprToValidate) {
window.setTimeout(() => this.history.replaceState(null, to, query));
// 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) {
delete query.redirect;
window.setTimeout(() => this.history.replaceState(null, '/' + redirect, query));
} else if(!exprToValidate && when === 'loggedOut' && redirectAuthenticated) {
/*
* redirectAuthenticated contains an arbirary path
* eg pieces/<id>, editions/<bitcoin_id>, collection, settings, ...
* hence transitionTo cannot be used directly.
*
* While we're getting rid of `query.redirect` explicitly in the
* above `else if` statement, here it's sufficient to just call
* `baseUrl` + `redirectAuthenticated`, as it gets rid of queries as well.
*/
window.location = AppConstants.baseUrl + redirectAuthenticated;
}
},
onChange(state) {
this.setState(state);
},
render() {
return (
<Component {...this.props}/>
);
}
});
};
}

View File

@ -27,6 +27,10 @@ import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils
let ContractSettings = React.createClass({ let ContractSettings = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
ContractListStore.getState(), ContractListStore.getState(),
@ -89,7 +93,8 @@ let ContractSettings = React.createClass({
fileClassToUpload={{ fileClassToUpload={{
singular: 'new contract', singular: 'new contract',
plural: 'new contracts' plural: 'new contracts'
}}/> }}
location={this.props.location}/>
); );
} }
@ -114,7 +119,9 @@ let ContractSettings = React.createClass({
<AclProxy <AclProxy
aclObject={this.state.whitelabel} aclObject={this.state.whitelabel}
aclName="acl_update_public_contract"> aclName="acl_update_public_contract">
<ContractSettingsUpdateButton contract={contract}/> <ContractSettingsUpdateButton
contract={contract}
location={this.props.location}/>
</AclProxy> </AclProxy>
<a <a
className="btn btn-default btn-sm margin-left-2px" className="btn btn-default btn-sm margin-left-2px"
@ -144,7 +151,8 @@ let ContractSettings = React.createClass({
fileClassToUpload={{ fileClassToUpload={{
singular: getLangText('new contract'), singular: getLangText('new contract'),
plural: getLangText('new contracts') plural: getLangText('new contracts')
}}/> }}
location={this.props.location}/>
{privateContracts.map((contract, i) => { {privateContracts.map((contract, i) => {
return ( return (
<ActionPanel <ActionPanel
@ -156,7 +164,9 @@ let ContractSettings = React.createClass({
<AclProxy <AclProxy
aclObject={this.state.whitelabel} aclObject={this.state.whitelabel}
aclName="acl_update_private_contract"> aclName="acl_update_private_contract">
<ContractSettingsUpdateButton contract={contract}/> <ContractSettingsUpdateButton
contract={contract}
location={this.props.location}/>
</AclProxy> </AclProxy>
<a <a
className="btn btn-default btn-sm margin-left-2px" className="btn btn-default btn-sm margin-left-2px"

View File

@ -20,7 +20,8 @@ import { getLangText } from '../../utils/lang_utils';
let ContractSettingsUpdateButton = React.createClass({ let ContractSettingsUpdateButton = React.createClass({
propTypes: { propTypes: {
contract: React.PropTypes.object contract: React.PropTypes.object,
location: React.PropTypes.object
}, },
submitFile(file) { submitFile(file) {
@ -90,7 +91,7 @@ let ContractSettingsUpdateButton = React.createClass({
}} }}
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
submitFile={this.submitFile} submitFile={this.submitFile}
/> location={this.props.location}/>
); );
} }
}); });

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import UserStore from '../../stores/user_store'; import UserStore from '../../stores/user_store';
import UserActions from '../../actions/user_actions'; import UserActions from '../../actions/user_actions';
@ -25,8 +24,6 @@ let SettingsContainer = React.createClass({
React.PropTypes.element]) React.PropTypes.element])
}, },
mixins: [Router.Navigation],
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
UserStore.getState(), UserStore.getState(),

View File

@ -1,96 +1,40 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react/addons';
import Router from 'react-router'; import { History } from 'react-router';
import ReactAddons from 'react/addons';
import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs'; import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs';
let State = Router.State;
let Navigation = Router.Navigation;
const { arrayOf, element, bool, shape, string, object } = React.PropTypes;
let SlidesContainer = React.createClass({ const SlidesContainer = React.createClass({
propTypes: { propTypes: {
children: React.PropTypes.arrayOf(React.PropTypes.element), children: arrayOf(element),
forwardProcess: React.PropTypes.bool.isRequired, forwardProcess: bool.isRequired,
glyphiconClassNames: React.PropTypes.shape({ glyphiconClassNames: shape({
pending: React.PropTypes.string, pending: string,
complete: React.PropTypes.string complete: string
}) }),
location: object
}, },
mixins: [State, Navigation], mixins: [History],
getInitialState() { getInitialState() {
// handle queryParameters
let queryParams = this.getQuery();
let slideNum = -1;
let startFrom = -1;
// We can actually need to check if slide_num is present as a key in queryParams.
// We do not really care about its value though...
if(queryParams && 'slide_num' in queryParams) {
slideNum = parseInt(queryParams.slide_num, 10);
}
// if slide_num is not set, this will be done in componentDidMount
// the query param 'start_from' removes all slide children before the respective number
// Also, we use the 'in' keyword for the same reason as above in 'slide_num'
if(queryParams && 'start_from' in queryParams) {
startFrom = parseInt(queryParams.start_from, 10);
}
return { return {
slideNum, containerWidth: 0
startFrom,
containerWidth: 0,
historyLength: window.history.length
}; };
}, },
componentDidMount() { componentDidMount() {
// check if slide_num was defined, and if not then default to 0
let queryParams = this.getQuery();
// We use 'in' to check if the key is present in the user's browser url bar,
// we do not really care about its value at this point
if(!('slide_num' in queryParams)) {
// we're first requiring all the other possible queryParams and then set
// the specific one we need instead of overwriting them
queryParams.slide_num = 0;
this.replaceWith(this.getPathname(), null, queryParams);
}
// init container width
this.handleContainerResize();
// we're using an event listener on window here, // we're using an event listener on window here,
// as it was not possible to listen to the resize events of a dom node // as it was not possible to listen to the resize events of a dom node
window.addEventListener('resize', this.handleContainerResize); window.addEventListener('resize', this.handleContainerResize);
},
componentWillReceiveProps() { // Initially, we need to dispatch 'resize' once to render correctly
let queryParams = this.getQuery(); window.dispatchEvent(new Event('resize'));
// also check if start_from was updated
// This applies for example when the user tries to submit a already existing piece
// (starting from slide 1 for example) and then clicking on + NEW WORK
if(queryParams && !('start_from' in queryParams)) {
this.setState({
startFrom: -1
});
}
},
componentDidUpdate() {
let queryParams = this.getQuery();
// check if slide_num was defined, and if not then default to 0
this.setSlideNum(queryParams.slide_num);
}, },
componentWillUnmount() { componentWillUnmount() {
@ -105,80 +49,26 @@ let SlidesContainer = React.createClass({
}, },
// When the start_from parameter is used, this.setSlideNum can not simply be used anymore. // When the start_from parameter is used, this.setSlideNum can not simply be used anymore.
nextSlide() { nextSlide(additionalQueryParams) {
let nextSlide = this.state.slideNum + 1; const slideNum = parseInt(this.props.location.query.slide_num, 10) || 0;
this.setSlideNum(nextSlide); let nextSlide = slideNum + 1;
this.setSlideNum(nextSlide, additionalQueryParams);
}, },
// We let every one from the outsite set the page number of the slider, setSlideNum(nextSlideNum, additionalQueryParams = {}) {
// though only if the slideNum is actually in the range of our children-list. let queryParams = Object.assign(this.props.location.query, additionalQueryParams);
setSlideNum(slideNum) { queryParams.slide_num = nextSlideNum;
this.history.pushState(null, this.props.location.pathname, queryParams);
// we do not want to overwrite other queryParams
let queryParams = this.getQuery();
// slideNum can in some instances be not a number,
// therefore we have to parse it to one and make sure that its not NaN
slideNum = parseInt(slideNum, 10);
// if slideNum is not a number (even after we parsed it to one) and there has
// never been a transition to another slide (this.state.slideNum ==== -1 indicates that)
// then we want to "replace" (in this case append) the current url with ?slide_num=0
if(isNaN(slideNum) && this.state.slideNum === -1) {
slideNum = 0;
queryParams.slide_num = slideNum;
this.replaceWith(this.getPathname(), null, queryParams);
this.setState({slideNum: slideNum});
return;
// slideNum always represents the future state. So if slideNum and
// this.state.slideNum are equal, there is no sense in redirecting
} else if(slideNum === this.state.slideNum) {
return;
// if slideNum is within the range of slides and none of the previous cases
// where matched, we can actually do transitions
} else if(slideNum >= 0 || slideNum < this.customChildrenCount()) {
if(slideNum !== this.state.slideNum - 1) {
// Bootstrapping the component, getInitialState is called once to save
// the tabs history length.
// In order to know if we already pushed a new state on the history stack or not,
// we're comparing the old history length with the new one and if it didn't change then
// we push a new state on it ONCE (ever).
// Otherwise, we're able to use the browsers history.forward() method
// to keep the stack clean
if(this.props.forwardProcess) {
queryParams.slide_num = slideNum;
this.transitionTo(this.getPathname(), null, queryParams);
} else {
if(this.state.historyLength === window.history.length) {
queryParams.slide_num = slideNum;
this.transitionTo(this.getPathname(), null, queryParams);
} else {
window.history.forward();
}
}
}
this.setState({
slideNum: slideNum
});
} else {
throw new Error('You\'re calling a page number that is out of range.');
}
}, },
// breadcrumbs are defined as attributes of the slides. // breadcrumbs are defined as attributes of the slides.
// To extract them we have to read the DOM element's attributes // To extract them we have to read the DOM element's attributes
extractBreadcrumbs() { extractBreadcrumbs() {
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
let breadcrumbs = []; let breadcrumbs = [];
ReactAddons.Children.map(this.props.children, (child, i) => { React.Children.map(this.props.children, (child, i) => {
if(child && i >= this.state.startFrom && child.props['data-slide-title']) { if(child && i >= startFrom && child.props['data-slide-title']) {
breadcrumbs.push(child.props['data-slide-title']); breadcrumbs.push(child.props['data-slide-title']);
} }
}); });
@ -191,9 +81,11 @@ let SlidesContainer = React.createClass({
// Therefore React.Children.count does not work anymore and we // Therefore React.Children.count does not work anymore and we
// need to implement our own method. // need to implement our own method.
customChildrenCount() { customChildrenCount() {
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
let count = 0; let count = 0;
React.Children.forEach(this.props.children, (child, i) => { React.Children.forEach(this.props.children, (child, i) => {
if(i >= this.state.startFrom) { if(i >= startFrom) {
count++; count++;
} }
}); });
@ -212,7 +104,7 @@ let SlidesContainer = React.createClass({
return ( return (
<SlidesContainerBreadcrumbs <SlidesContainerBreadcrumbs
breadcrumbs={breadcrumbs} breadcrumbs={breadcrumbs}
slideNum={this.state.slideNum} slideNum={parseInt(this.props.location.query.slide_num, 10) || 0}
numOfSlides={breadcrumbs.length} numOfSlides={breadcrumbs.length}
containerWidth={this.state.containerWidth} containerWidth={this.state.containerWidth}
glyphiconClassNames={this.props.glyphiconClassNames}/> glyphiconClassNames={this.props.glyphiconClassNames}/>
@ -225,12 +117,13 @@ let SlidesContainer = React.createClass({
// Since we need to give the slides a width, we need to call ReactAddons.addons.cloneWithProps // Since we need to give the slides a width, we need to call ReactAddons.addons.cloneWithProps
// Also, a key is nice to have! // Also, a key is nice to have!
renderChildren() { renderChildren() {
return ReactAddons.Children.map(this.props.children, (child, i) => { const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
return React.Children.map(this.props.children, (child, i) => {
// since the default parameter of startFrom is -1, we do not need to check // since the default parameter of startFrom is -1, we do not need to check
// if its actually present in the url bar, as it will just not match // if its actually present in the url bar, as it will just not match
if(child && i >= this.state.startFrom) { if(child && i >= startFrom) {
return ReactAddons.addons.cloneWithProps(child, { return React.addons.cloneWithProps(child, {
className: 'ascribe-slide', className: 'ascribe-slide',
style: { style: {
width: this.state.containerWidth width: this.state.containerWidth
@ -246,7 +139,7 @@ let SlidesContainer = React.createClass({
}, },
render() { render() {
let spacing = this.state.containerWidth * this.state.slideNum; let spacing = this.state.containerWidth * parseInt(this.props.location.query.slide_num, 10) || 0;
let translateXValue = 'translateX(' + (-1) * spacing + 'px)'; let translateXValue = 'translateX(' + (-1) * spacing + 'px)';
/* /*

View File

@ -35,14 +35,7 @@ export class TransitionModel {
this.callback = callback; this.callback = callback;
} }
toReactRouterLinkProps(queryValue) { toReactRouterLink(queryValue) {
let props = { return '/' + this.to + '/' + queryValue;
to: this.to,
params: {}
};
props.params[this.queryKey] = queryValue;
return props;
} }
} }

View File

@ -1,11 +1,10 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import { ColumnModel } from './models/table_models'; import { ColumnModel } from './models/table_models';
let Link = Router.Link;
let TableItemWrapper = React.createClass({ let TableItemWrapper = React.createClass({
propTypes: { propTypes: {
@ -15,8 +14,6 @@ let TableItemWrapper = React.createClass({
onClick: React.PropTypes.func onClick: React.PropTypes.func
}, },
mixins: [Router.Navigation],
render() { render() {
return ( return (
<tr onClick={this.props.onClick}> <tr onClick={this.props.onClick}>
@ -35,18 +32,13 @@ let TableItemWrapper = React.createClass({
); );
} else { } else {
let linkProps = column.transition.toReactRouterLinkProps(this.props.columnContent[column.transition.valueKey]); let linkString = column.transition.toReactRouterLink(this.props.columnContent[column.transition.valueKey]);
/**
* If a transition is defined in columnContent, then we can use
* Router.Navigation.transitionTo to redirect the user
* programmatically
*/
return ( return (
<td key={i} className={column.className}> <td key={i} className={column.className}>
<Link <Link
to={linkString}
className={'ascribe-table-item-column'} className={'ascribe-table-item-column'}
onClick={column.transition.callback} onClick={column.transition.callback}>
{...linkProps}>
<TypeElement {...typeElementProps} /> <TypeElement {...typeElementProps} />
</Link> </Link>
</td> </td>

View File

@ -40,7 +40,8 @@ let FileDragAndDrop = React.createClass({
plural: React.PropTypes.string plural: React.PropTypes.string
}), }),
allowedExtensions: React.PropTypes.string allowedExtensions: React.PropTypes.string,
location: React.PropTypes.object
}, },
handleDragOver(event) { handleDragOver(event) {
@ -142,7 +143,8 @@ let FileDragAndDrop = React.createClass({
fileClassToUpload, fileClassToUpload,
areAssetsDownloadable, areAssetsDownloadable,
areAssetsEditable, areAssetsEditable,
allowedExtensions allowedExtensions,
location
} = this.props; } = this.props;
// has files only is true if there are files that do not have the status deleted or canceled // has files only is true if there are files that do not have the status deleted or canceled
@ -179,7 +181,8 @@ let FileDragAndDrop = React.createClass({
hasFiles={hasFiles} hasFiles={hasFiles}
onClick={this.handleOnClick} onClick={this.handleOnClick}
enableLocalHashing={enableLocalHashing} enableLocalHashing={enableLocalHashing}
fileClassToUpload={fileClassToUpload}/> fileClassToUpload={fileClassToUpload}
location={location}/>
<FileDragAndDropPreviewIterator <FileDragAndDropPreviewIterator
files={filesToUpload} files={filesToUpload}
handleDeleteFile={this.handleDeleteFile} handleDeleteFile={this.handleDeleteFile}

View File

@ -1,11 +1,12 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import { getLangText } from '../../../utils/lang_utils'; import { getLangText } from '../../../utils/lang_utils';
import { dragAndDropAvailable } from '../../../utils/feature_detection_utils';
let Link = Router.Link;
let FileDragAndDropDialog = React.createClass({ let FileDragAndDropDialog = React.createClass({
propTypes: { propTypes: {
@ -19,13 +20,24 @@ let FileDragAndDropDialog = React.createClass({
fileClassToUpload: React.PropTypes.shape({ fileClassToUpload: React.PropTypes.shape({
singular: React.PropTypes.string, singular: React.PropTypes.string,
plural: React.PropTypes.string plural: React.PropTypes.string
}) }),
location: React.PropTypes.object
}, },
mixins: [Router.State], getDragDialog(fileClass) {
if(dragAndDropAvailable) {
return [
<p>{getLangText('Drag %s here', fileClass)}</p>,
<p>{getLangText('or')}</p>
];
} else {
return null;
}
},
render() { render() {
const queryParams = this.getQuery(); const queryParams = this.props.location.query;
if(this.props.hasFiles) { if(this.props.hasFiles) {
return null; return null;
@ -38,11 +50,13 @@ let FileDragAndDropDialog = React.createClass({
let queryParamsUpload = Object.assign({}, queryParams); let queryParamsUpload = Object.assign({}, queryParams);
queryParamsUpload.method = 'upload'; queryParamsUpload.method = 'upload';
let { location } = this.props;
return ( return (
<div className="file-drag-and-drop-dialog present-options"> <div className="file-drag-and-drop-dialog present-options">
<p>{getLangText('Would you rather')}</p> <p>{getLangText('Would you rather')}</p>
<Link <Link
to={this.getPath()} to={location.pathname}
query={queryParamsHash}> query={queryParamsHash}>
<span className="btn btn-default btn-sm"> <span className="btn btn-default btn-sm">
{getLangText('Hash your work')} {getLangText('Hash your work')}
@ -52,7 +66,7 @@ let FileDragAndDropDialog = React.createClass({
<span> or </span> <span> or </span>
<Link <Link
to={this.getPath()} to={location.pathname}
query={queryParamsUpload}> query={queryParamsUpload}>
<span className="btn btn-default btn-sm"> <span className="btn btn-default btn-sm">
{getLangText('Upload and hash your work')} {getLangText('Upload and hash your work')}
@ -64,8 +78,7 @@ let FileDragAndDropDialog = React.createClass({
if(this.props.multipleFiles) { if(this.props.multipleFiles) {
return ( return (
<span className="file-drag-and-drop-dialog"> <span className="file-drag-and-drop-dialog">
<p>{getLangText('Drag %s here', this.props.fileClassToUpload.plural)}</p> {this.getDragDialog(this.props.fileClassToUpload.plural)}
<p>{getLangText('or')}</p>
<span <span
className="btn btn-default" className="btn btn-default"
onClick={this.props.onClick}> onClick={this.props.onClick}>
@ -78,8 +91,7 @@ let FileDragAndDropDialog = React.createClass({
return ( return (
<span className="file-drag-and-drop-dialog"> <span className="file-drag-and-drop-dialog">
<p>{getLangText('Drag a %s here', this.props.fileClassToUpload.singular)}</p> {this.getDragDialog(this.props.fileClassToUpload.singular)}
<p>{getLangText('or')}</p>
<span <span
className="btn btn-default" className="btn btn-default"
onClick={this.props.onClick}> onClick={this.props.onClick}>

View File

@ -2,7 +2,6 @@
import React from 'react/addons'; import React from 'react/addons';
import fineUploader from 'fineUploader'; import fineUploader from 'fineUploader';
import Router from 'react-router';
import Q from 'q'; import Q from 'q';
import S3Fetcher from '../../fetchers/s3_fetcher'; import S3Fetcher from '../../fetchers/s3_fetcher';
@ -127,10 +126,10 @@ let ReactS3FineUploader = React.createClass({
fileInputElement: React.PropTypes.oneOfType([ fileInputElement: React.PropTypes.oneOfType([
React.PropTypes.func, React.PropTypes.func,
React.PropTypes.element React.PropTypes.element
]) ]),
},
mixins: [Router.State], location: React.PropTypes.object
},
getDefaultProps() { getDefaultProps() {
return { return {
@ -649,7 +648,7 @@ let ReactS3FineUploader = React.createClass({
// //
// In the view this only happens when the user is allowed to do local hashing as well // In the view this only happens when the user is allowed to do local hashing as well
// as when the correct query parameter is present in the url ('hash' and not 'upload') // as when the correct query parameter is present in the url ('hash' and not 'upload')
let queryParams = this.getQuery(); let queryParams = this.props.location.query;
if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') { if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') {
let convertedFilePromises = []; let convertedFilePromises = [];
@ -830,7 +829,7 @@ let ReactS3FineUploader = React.createClass({
isDropzoneInactive() { isDropzoneInactive() {
let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1); let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1);
let queryParams = this.getQuery(); let queryParams = this.props.location.query;
if((this.props.enableLocalHashing && !queryParams.method) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) { if((this.props.enableLocalHashing && !queryParams.method) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) {
return true; return true;
@ -859,12 +858,20 @@ let ReactS3FineUploader = React.createClass({
enableLocalHashing, enableLocalHashing,
fileClassToUpload, fileClassToUpload,
validation, validation,
fileInputElement fileInputElement,
location
} = this.props; } = this.props;
// Here we initialize the template that has been either provided from the outside // Here we initialize the template that has been either provided from the outside
// or the default input that is FileDragAndDrop. // or the default input that is FileDragAndDrop.
return React.createElement(fileInputElement, { return React.createElement(fileInputElement, {
multiple,
areAssetsDownloadable,
areAssetsEditable,
onInactive,
enableLocalHashing,
fileClassToUpload,
location,
onDrop: this.handleUploadFile, onDrop: this.handleUploadFile,
filesToUpload: this.state.filesToUpload, filesToUpload: this.state.filesToUpload,
handleDeleteFile: this.handleDeleteFile, handleDeleteFile: this.handleDeleteFile,
@ -872,14 +879,8 @@ let ReactS3FineUploader = React.createClass({
handlePauseFile: this.handlePauseFile, handlePauseFile: this.handlePauseFile,
handleResumeFile: this.handleResumeFile, handleResumeFile: this.handleResumeFile,
handleCancelHashing: this.handleCancelHashing, handleCancelHashing: this.handleCancelHashing,
multiple: multiple,
areAssetsDownloadable: areAssetsDownloadable,
areAssetsEditable: areAssetsEditable,
onInactive: onInactive,
dropzoneInactive: this.isDropzoneInactive(), dropzoneInactive: this.isDropzoneInactive(),
hashingProgress: this.state.hashingProgress, hashingProgress: this.state.hashingProgress,
enableLocalHashing: enableLocalHashing,
fileClassToUpload: fileClassToUpload,
allowedExtensions: this.getAllowedExtensions() allowedExtensions: this.getAllowedExtensions()
}); });
} }

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import GlobalNotificationModel from '../models/global_notification_model'; import GlobalNotificationModel from '../models/global_notification_model';
import GlobalNotificationActions from '../actions/global_notification_actions'; import GlobalNotificationActions from '../actions/global_notification_actions';
@ -17,8 +16,6 @@ import { getLangText } from '../utils/lang_utils';
let CoaVerifyContainer = React.createClass({ let CoaVerifyContainer = React.createClass({
mixins: [Router.Navigation],
render() { render() {
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
@ -47,8 +44,6 @@ let CoaVerifyContainer = React.createClass({
let CoaVerifyForm = React.createClass({ let CoaVerifyForm = React.createClass({
mixins: [Router.Navigation],
handleSuccess(response){ handleSuccess(response){
let notification = null; let notification = null;
if (response.verdict) { if (response.verdict) {

View File

@ -1,15 +1,15 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import Nav from 'react-bootstrap/lib/Nav'; import Nav from 'react-bootstrap/lib/Nav';
import Navbar from 'react-bootstrap/lib/Navbar'; import Navbar from 'react-bootstrap/lib/Navbar';
import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav';
import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import DropdownButton from 'react-bootstrap/lib/DropdownButton';
import MenuItem from 'react-bootstrap/lib/MenuItem'; import MenuItem from 'react-bootstrap/lib/MenuItem';
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink'; import NavItem from 'react-bootstrap/lib/NavItem';
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import AclProxy from './acl_proxy'; import AclProxy from './acl_proxy';
@ -33,11 +33,9 @@ import { getLangText } from '../utils/lang_utils';
let Header = React.createClass({ let Header = React.createClass({
propTypes: { propTypes: {
showAddWork: React.PropTypes.bool, showAddWork: React.PropTypes.bool,
routes: React.PropTypes.element routes: React.PropTypes.arrayOf(React.PropTypes.object)
}, },
mixins: [Router.State],
getDefaultProps() { getDefaultProps() {
return { return {
showAddWork: true showAddWork: true
@ -133,38 +131,61 @@ let Header = React.createClass({
ref='dropdownbutton' ref='dropdownbutton'
eventKey="1" eventKey="1"
title={this.state.currentUser.username}> title={this.state.currentUser.username}>
<MenuItemLink <LinkContainer
eventKey="2" to="/settings"
to="settings"
onClick={this.onMenuItemClick}> onClick={this.onMenuItemClick}>
<MenuItem
eventKey="2">
{getLangText('Account Settings')} {getLangText('Account Settings')}
</MenuItemLink> </MenuItem>
</LinkContainer>
<AclProxy <AclProxy
aclObject={this.state.currentUser.acl} aclObject={this.state.currentUser.acl}
aclName="acl_view_settings_contract"> aclName="acl_view_settings_contract">
<MenuItemLink <LinkContainer
to="contract_settings" to="/contract_settings"
onClick={this.onMenuItemClick}> onClick={this.onMenuItemClick}>
<MenuItem
eventKey="2">
{getLangText('Contract Settings')} {getLangText('Contract Settings')}
</MenuItemLink> </MenuItem>
</LinkContainer>
</AclProxy> </AclProxy>
<MenuItem divider /> <MenuItem divider />
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink> <LinkContainer
to="/logout">
<MenuItem
eventKey="3">
{getLangText('Log out')}
</MenuItem>
</LinkContainer>
</DropdownButton> </DropdownButton>
); );
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} userAcl={this.state.currentUser.acl} navbar right/>; navRoutesLinks = <NavRoutesLinks routes={this.props.routes} userAcl={this.state.currentUser.acl} navbar right/>;
} }
else { else {
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>; account = (
signup = <NavItemLink to="signup">{getLangText('SIGNUP')}</NavItemLink>; <LinkContainer
to="/login">
<NavItem>
{getLangText('LOGIN')}
</NavItem>
</LinkContainer>
);
signup = (
<LinkContainer
to="/signup">
<NavItem>
{getLangText('SIGNUP')}
</NavItem>
</LinkContainer>
);
} }
return ( return (
<div> <div>
<Navbar <Navbar
brand={ brand={this.getLogo()}
this.getLogo()
}
toggleNavKey={0} toggleNavKey={0}
fixedTop={true}> fixedTop={true}>
<CollapsibleNav eventKey={0}> <CollapsibleNav eventKey={0}>

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import DropdownButton from 'react-bootstrap/lib/DropdownButton';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import MenuItem from 'react-bootstrap/lib/MenuItem'; import MenuItem from 'react-bootstrap/lib/MenuItem';
@ -14,8 +14,6 @@ import NotificationStore from '../stores/notification_store';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
let Link = Router.Link;
let HeaderNotifications = React.createClass({ let HeaderNotifications = React.createClass({
@ -39,7 +37,7 @@ let HeaderNotifications = React.createClass({
this.setState(state); this.setState(state);
}, },
onMenuItemClick(event) { onMenuItemClick() {
/* /*
This is a hack to make the dropdown close after clicking on an item This is a hack to make the dropdown close after clicking on an item
The function just need to be defined The function just need to be defined
@ -158,23 +156,13 @@ let NotificationListItem = React.createClass({
}, },
getLinkData() { getLinkData() {
let { pieceOrEdition } = this.props;
if (this.isPiece()) { if (this.isPiece()) {
return { return `/pieces/${pieceOrEdition.id}`;
to: 'piece',
params: {
pieceId: this.props.pieceOrEdition.id
}
};
} else { } else {
return { return `/editions/${pieceOrEdition.bitcoin_id}`;
to: 'edition',
params: {
editionId: this.props.pieceOrEdition.bitcoin_id
} }
};
}
}, },
onClick(event){ onClick(event){
@ -184,7 +172,7 @@ let NotificationListItem = React.createClass({
getNotificationText(){ getNotificationText(){
let numNotifications = null; let numNotifications = null;
if (this.props.notification.length > 1){ if (this.props.notification.length > 1){
numNotifications = <div>+ {this.props.notification.length - 1} more...</div>; numNotifications = <div>+ {this.props.notification.length - 1} {getLangText('more...')}</div>;
} }
return ( return (
<div className="notification-action"> <div className="notification-action">
@ -196,7 +184,7 @@ let NotificationListItem = React.createClass({
render() { render() {
if (this.props.pieceOrEdition) { if (this.props.pieceOrEdition) {
return ( return (
<Link {...this.getLinkData()} onClick={this.onClick}> <Link to={this.getLinkData()} onClick={this.onClick}>
<div className="row notification-wrapper"> <div className="row notification-wrapper">
<div className="col-xs-4 clear-paddings"> <div className="col-xs-4 clear-paddings">
<div className="thumbnail-wrapper"> <div className="thumbnail-wrapper">

View File

@ -1,21 +1,20 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import LoginForm from './ascribe_forms/form_login'; import LoginForm from './ascribe_forms/form_login';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
let Link = Router.Link;
let LoginContainer = React.createClass({ let LoginContainer = React.createClass({
propTypes: { propTypes: {
message: React.PropTypes.string, message: React.PropTypes.string,
redirectOnLoggedIn: React.PropTypes.bool, redirectOnLoggedIn: React.PropTypes.bool,
redirectOnLoginSuccess: React.PropTypes.bool, redirectOnLoginSuccess: React.PropTypes.bool,
onLogin: React.PropTypes.func onLogin: React.PropTypes.func,
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -33,10 +32,11 @@ let LoginContainer = React.createClass({
redirectOnLoggedIn={this.props.redirectOnLoggedIn} redirectOnLoggedIn={this.props.redirectOnLoggedIn}
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess} redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
message={this.props.message} message={this.props.message}
onLogin={this.props.onLogin}/> onLogin={this.props.onLogin}
location={this.props.location}/>
<div className="ascribe-login-text"> <div className="ascribe-login-text">
{getLangText('Not an ascribe user')}&#63; <Link to="signup">{getLangText('Sign up')}...</Link><br/> {getLangText('Not an ascribe user')}&#63; <Link to="/signup">{getLangText('Sign up')}...</Link><br/>
{getLangText('Forgot my password')}&#63; <Link to="password_reset">{getLangText('Rescue me')}...</Link> {getLangText('Forgot my password')}&#63; <Link to="/password_reset">{getLangText('Rescue me')}...</Link>
</div> </div>
</div> </div>
); );

View File

@ -1,38 +1,27 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import AscribeSpinner from './ascribe_spinner'; import AscribeSpinner from './ascribe_spinner';
import UserActions from '../actions/user_actions'; import UserActions from '../actions/user_actions';
import { alt, altWhitelabel, altUser, altThirdParty } from '../alt'; import { alt, altWhitelabel, altUser, altThirdParty } from '../alt';
import AppConstants from '../constants/application_constants';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
let baseUrl = AppConstants.baseUrl;
let LogoutContainer = React.createClass({ let LogoutContainer = React.createClass({
mixins: [History],
mixins: [Router.Navigation, Router.State],
componentDidMount() { componentDidMount() {
UserActions.logoutCurrentUser() UserActions.logoutCurrentUser();
.then(() => {
alt.flush(); alt.flush();
altWhitelabel.flush(); altWhitelabel.flush();
altUser.flush(); altUser.flush();
altThirdParty.flush(); altThirdParty.flush();
// kill intercom (with fire) // kill intercom (with fire)
window.Intercom('shutdown'); window.Intercom('shutdown');
this.replaceWith(baseUrl);
})
.catch((err) => {
console.logGlobal(err);
});
}, },
render() { render() {
@ -45,7 +34,6 @@ let LogoutContainer = React.createClass({
</div> </div>
); );
} }
}); });

View File

@ -13,7 +13,7 @@ import { sanitizeList } from '../utils/general_utils';
let NavRoutesLinks = React.createClass({ let NavRoutesLinks = React.createClass({
propTypes: { propTypes: {
routes: React.PropTypes.element, routes: React.PropTypes.arrayOf(React.PropTypes.object),
userAcl: React.PropTypes.object userAcl: React.PropTypes.object
}, },
@ -33,15 +33,15 @@ let NavRoutesLinks = React.createClass({
return; return;
} }
let links = node.props.children.map((child, j) => { let links = node.childRoutes.map((child, j) => {
let childrenFn = null; let childrenFn = null;
let { aclName, headerTitle, name, children } = child.props; let { aclName, headerTitle, path, childRoutes } = child;
// If the node has children that could be rendered, then we want // If the node has children that could be rendered, then we want
// to execute this function again with the child as the root // to execute this function again with the child as the root
// //
// Otherwise we'll just pass childrenFn as false // Otherwise we'll just pass childrenFn as false
if(child.props.children && child.props.children.length > 0) { if(child.childRoutes && child.childRoutes.length > 0) {
childrenFn = this.extractLinksFromRoutes(child, userAcl, i++); childrenFn = this.extractLinksFromRoutes(child, userAcl, i++);
} }
@ -58,7 +58,7 @@ let NavRoutesLinks = React.createClass({
aclObject={this.props.userAcl}> aclObject={this.props.userAcl}>
<NavRoutesLinksLink <NavRoutesLinksLink
headerTitle={headerTitle} headerTitle={headerTitle}
routeName={name} routePath={'/' + path}
depth={i} depth={i}
children={childrenFn}/> children={childrenFn}/>
</AclProxy> </AclProxy>
@ -68,7 +68,7 @@ let NavRoutesLinks = React.createClass({
<NavRoutesLinksLink <NavRoutesLinksLink
key={j} key={j}
headerTitle={headerTitle} headerTitle={headerTitle}
routeName={name} routePath={'/' + path}
depth={i} depth={i}
children={childrenFn}/> children={childrenFn}/>
); );
@ -88,7 +88,7 @@ let NavRoutesLinks = React.createClass({
return ( return (
<Nav {...this.props}> <Nav {...this.props}>
{this.extractLinksFromRoutes(routes, userAcl, 0)} {this.extractLinksFromRoutes(routes[0], userAcl, 0)}
</Nav> </Nav>
); );
} }

View File

@ -3,13 +3,16 @@
import React from 'react'; import React from 'react';
import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import DropdownButton from 'react-bootstrap/lib/DropdownButton';
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink'; import MenuItem from 'react-bootstrap/lib/MenuItem';
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink'; import NavItem from 'react-bootstrap/lib/NavItem';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
let NavRoutesLinksLink = React.createClass({ let NavRoutesLinksLink = React.createClass({
propTypes: { propTypes: {
headerTitle: React.PropTypes.string, headerTitle: React.PropTypes.string,
routeName: React.PropTypes.string, routePath: React.PropTypes.string,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
@ -20,10 +23,10 @@ let NavRoutesLinksLink = React.createClass({
}, },
render() { render() {
let { children, headerTitle, depth, routeName } = this.props; let { children, headerTitle, depth, routePath } = this.props;
// if the route has children, we're returning a DropdownButton that will get filled // if the route has children, we're returning a DropdownButton that will get filled
// with MenuItemLinks // with MenuItems
if(children) { if(children) {
return ( return (
<DropdownButton title={headerTitle}> <DropdownButton title={headerTitle}>
@ -33,13 +36,17 @@ let NavRoutesLinksLink = React.createClass({
} else { } else {
if(depth === 1) { if(depth === 1) {
// if the node's child is actually a node of level one (a child of a node), we're // if the node's child is actually a node of level one (a child of a node), we're
// returning a DropdownButton matching MenuItemLink // returning a DropdownButton matching MenuItem
return ( return (
<MenuItemLink to={routeName}>{headerTitle}</MenuItemLink> <LinkContainer to={routePath}>
<MenuItem>{headerTitle}</MenuItem>
</LinkContainer>
); );
} else if(depth === 0) { } else if(depth === 0) {
return ( return (
<NavItemLink to={routeName}>{headerTitle}</NavItemLink> <LinkContainer to={routePath}>
<NavItem>{headerTitle}</NavItem>
</LinkContainer>
); );
} else { } else {
return null; return null;

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import Form from './ascribe_forms/form'; import Form from './ascribe_forms/form';
import Property from './ascribe_forms/property'; import Property from './ascribe_forms/property';
@ -14,7 +14,9 @@ import { getLangText } from '../utils/lang_utils';
let PasswordResetContainer = React.createClass({ let PasswordResetContainer = React.createClass({
mixins: [Router.Navigation], propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return {isRequested: false}; return {isRequested: false};
@ -25,12 +27,14 @@ let PasswordResetContainer = React.createClass({
}, },
render() { render() {
if (this.props.query.email && this.props.query.token) { let { location } = this.props;
if (location.query.email && location.query.token) {
return ( return (
<div> <div>
<PasswordResetForm <PasswordResetForm
email={this.props.query.email} email={location.query.email}
token={this.props.query.token}/> token={location.query.token}/>
</div> </div>
); );
} }
@ -113,7 +117,7 @@ let PasswordResetForm = React.createClass({
token: React.PropTypes.string token: React.PropTypes.string
}, },
mixins: [Router.Navigation], mixins: [History],
getFormData() { getFormData() {
return { return {
@ -123,7 +127,7 @@ let PasswordResetForm = React.createClass({
}, },
handleSuccess() { handleSuccess() {
this.transitionTo('pieces'); this.history.pushState(null, '/collection');
let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000); let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
}, },

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import PieceListStore from '../stores/piece_list_store'; import PieceListStore from '../stores/piece_list_store';
import PieceListActions from '../actions/piece_list_actions'; import PieceListActions from '../actions/piece_list_actions';
@ -35,10 +35,11 @@ let PieceList = React.createClass({
customSubmitButton: React.PropTypes.element, customSubmitButton: React.PropTypes.element,
filterParams: React.PropTypes.array, filterParams: React.PropTypes.array,
orderParams: React.PropTypes.array, orderParams: React.PropTypes.array,
orderBy: React.PropTypes.string orderBy: React.PropTypes.string,
location: React.PropTypes.object
}, },
mixins: [Router.Navigation, Router.State], mixins: [History],
getDefaultProps() { getDefaultProps() {
return { return {
@ -62,7 +63,7 @@ let PieceList = React.createClass({
}, },
componentDidMount() { componentDidMount() {
let page = this.getQuery().page || 1; let page = this.props.location.query.page || 1;
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
EditionListStore.listen(this.onChange); EditionListStore.listen(this.onChange);
@ -77,7 +78,7 @@ let PieceList = React.createClass({
componentDidUpdate() { componentDidUpdate() {
if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) { if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
// FIXME: hack to redirect out of the dispatch cycle // FIXME: hack to redirect out of the dispatch cycle
window.setTimeout(() => this.transitionTo(this.props.redirectTo, this.getQuery())); window.setTimeout(() => this.history.pushState(null, this.props.redirectTo, this.props.location.query), 0);
} }
}, },
@ -102,7 +103,7 @@ let PieceList = React.createClass({
}, },
getPagination() { getPagination() {
let currentPage = parseInt(this.getQuery().page, 10) || 1; let currentPage = parseInt(this.props.location.query.page, 10) || 1;
let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize); let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize);
if (this.state.pieceListCount > 20) { if (this.state.pieceListCount > 20) {
@ -118,7 +119,7 @@ let PieceList = React.createClass({
searchFor(searchTerm) { searchFor(searchTerm) {
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy, PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy,
this.state.orderAsc, this.state.filterBy); this.state.orderAsc, this.state.filterBy);
this.transitionTo(this.getPathname(), {page: 1}); this.history.pushState(null, this.props.location.pathname, {page: 1});
}, },
applyFilterBy(filterBy){ applyFilterBy(filterBy){
@ -142,7 +143,7 @@ let PieceList = React.createClass({
// we have to redirect the user always to page one as it could be that there is no page two // we have to redirect the user always to page one as it could be that there is no page two
// for filtered pieces // for filtered pieces
this.transitionTo(this.getPathname(), {page: 1}); this.history.pushState(null, this.props.location.pathname, {page: 1});
}, },
applyOrderBy(orderBy) { applyOrderBy(orderBy) {
@ -159,6 +160,7 @@ let PieceList = React.createClass({
<PieceListToolbar <PieceListToolbar
className="ascribe-piece-list-toolbar" className="ascribe-piece-list-toolbar"
searchFor={this.searchFor} searchFor={this.searchFor}
searchQuery={this.state.search}
filterParams={this.props.filterParams} filterParams={this.props.filterParams}
orderParams={this.props.orderParams} orderParams={this.props.orderParams}
filterBy={this.state.filterBy} filterBy={this.state.filterBy}
@ -184,6 +186,7 @@ let PieceList = React.createClass({
orderBy={this.state.orderBy} orderBy={this.state.orderBy}
orderAsc={this.state.orderAsc} orderAsc={this.state.orderAsc}
search={this.state.search} search={this.state.search}
searchFor={this.searchFor}
page={this.state.page} page={this.state.page}
pageSize={this.state.pageSize} pageSize={this.state.pageSize}
loadingElement={loadingElement}> loadingElement={loadingElement}>

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
@ -20,10 +20,6 @@ import GlobalNotificationActions from '../actions/global_notification_actions';
import PropertyCollapsible from './ascribe_forms/property_collapsible'; import PropertyCollapsible from './ascribe_forms/property_collapsible';
import RegisterPieceForm from './ascribe_forms/form_register_piece'; import RegisterPieceForm from './ascribe_forms/form_register_piece';
import LoginContainer from './login_container';
import SlidesContainer from './ascribe_slides_container/slides_container';
import { mergeOptions } from '../utils/general_utils'; import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
@ -37,10 +33,11 @@ let RegisterPiece = React.createClass( {
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element, React.PropTypes.element,
React.PropTypes.string React.PropTypes.string
]) ]),
location: React.PropTypes.object
}, },
mixins: [Router.Navigation], mixins: [History],
getDefaultProps() { getDefaultProps() {
return { return {
@ -60,10 +57,10 @@ let RegisterPiece = React.createClass( {
}, },
componentDidMount() { componentDidMount() {
WhitelabelActions.fetchWhitelabel();
PieceListStore.listen(this.onChange); PieceListStore.listen(this.onChange);
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
WhitelabelStore.listen(this.onChange); WhitelabelStore.listen(this.onChange);
WhitelabelActions.fetchWhitelabel();
}, },
componentWillUnmount() { componentWillUnmount() {
@ -98,7 +95,7 @@ let RegisterPiece = React.createClass( {
this.state.filterBy this.state.filterBy
); );
this.transitionTo('piece', {pieceId: response.piece.id}); this.history.pushState(null, `/pieces/${response.piece.id}`);
}, },
getSpecifyEditions() { getSpecifyEditions() {
@ -117,53 +114,20 @@ let RegisterPiece = React.createClass( {
} }
}, },
// basically redirects to the second slide (index: 1), when the user is not logged in
onLoggedOut() {
// only transition to the login store, if user is not logged in
// ergo the currentUser object is not properly defined
if(this.state.currentUser && !this.state.currentUser.email) {
this.refs.slidesContainer.setSlideNum(1);
}
},
onLogin() {
// once the currentUser object from UserStore is defined (eventually the user was transitioned
// to the login form via the slider and successfully logged in), we can direct him back to the
// register_piece slide
if(this.state.currentUser && this.state.currentUser.email) {
window.history.back();
}
},
render() { render() {
return ( return (
<SlidesContainer
ref="slidesContainer"
forwardProcess={false}>
<div
onClick={this.onLoggedOut}
onFocus={this.onLoggedOut}>
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
<RegisterPieceForm <RegisterPieceForm
{...this.props} {...this.props}
isFineUploaderActive={this.state.isFineUploaderActive} isFineUploaderActive={this.state.isFineUploaderActive}
handleSuccess={this.handleSuccess} handleSuccess={this.handleSuccess}
onLoggedOut={this.onLoggedOut}> location={this.props.location}>
{this.props.children} {this.props.children}
{this.getSpecifyEditions()} {this.getSpecifyEditions()}
</RegisterPieceForm> </RegisterPieceForm>
</Col> </Col>
</Row> </Row>
</div>
<div>
<LoginContainer
message={getLangText('Please login before ascribing your work%s', '...')}
redirectOnLoggedIn={false}
redirectOnLoginSuccess={false}
onLogin={this.onLogin}/>
</div>
</SlidesContainer>
); );
} }
}); });

140
js/components/search_bar.js Normal file
View File

@ -0,0 +1,140 @@
'use strict';
import React from 'react';
import Input from 'react-bootstrap/lib/Input';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import { getLangText } from '../utils/lang_utils';
const { func, string, number } = React.PropTypes;
const SearchBar = React.createClass({
propTypes: {
// a function that accepts a string as a search query and updates the
// propagated `searchQuery` after successfully retrieving the
// request from the server
searchFor: func.isRequired,
searchQuery: string.isRequired,
className: string,
// the number of milliseconds the component
// should wait before requesting search results from the server
threshold: number.isRequired
},
getInitialState() {
return {
timer: null,
searchQuery: '',
loading: false
};
},
componentDidUpdate(prevProps) {
const searchQueryProps = this.props.searchQuery;
const searchQueryPrevProps = prevProps.searchQuery;
const searchQueryState = this.state.searchQuery;
const { loading } = this.state;
/**
* 1. Condition: `loading` must be true, which implies that `evaluateTimer`,
* has already been called
*
* AND
*
* (
* 2. Condition: `searchQueryProps` and `searchQueryState` are true and equal
* (which means that the search query has been propagated to the inner
* fetch method of `fetchPieceList`, which in turn means that a fetch
* has completed)
*
* OR
*
* 3. Condition: `searchQueryProps` and `searchQueryState` can be any value (`true` or
* `false`, as long as they're equal). This case only occurs when the user
* has entered a `searchQuery` and deletes the query in one go, reseting
* `searchQueryProps` to empty string ('' === false) again.
* )
*/
const firstCondition = !!loading;
const secondCondition = searchQueryProps && searchQueryState && searchQueryProps === searchQueryState;
const thirdCondition = !searchQueryPrevProps && searchQueryProps === searchQueryState;
if(firstCondition && (secondCondition || thirdCondition)) {
this.setState({ loading: false });
}
},
componentWillReceiveProps(nextProps) {
/**
* This enables the `PieceListStore` to override the state
* of that component in case someone is changing the `searchQuery` on
* another component.
*
* Like how it's being done in the 'Clear search' dialog.
*/
if(this.props.searchQuery !== nextProps.searchQuery || !this.state.searchQuery) {
this.setState({ searchQuery: nextProps.searchQuery });
}
},
startTimer(searchQuery) {
const { timer } = this.state;
const { threshold } = this.props;
// The timer waits for the specified threshold time in milliseconds
// and then calls `evaluateTimer`.
// If another letter has been called in the mean time (timespan < `threshold`),
// the present timer gets cleared and a new one is added to `this.state`.
// This means that `evaluateTimer`, will only be called when the threshold has actually
// passed,
clearTimeout(timer); // apparently `clearTimeout` can be called with null, without throwing errors
const newTimer = setTimeout(this.evaluateTimer(searchQuery), threshold);
this.setState({ timer: newTimer });
},
evaluateTimer(searchQuery) {
return () => {
this.setState({ timer: null, loading: true }, () => {
// search for the query
this.props.searchFor(searchQuery);
});
};
},
handleChange({ target: { value }}) {
// On each letter entry we're updating the state of the component
// and start a timer, which we're also pushing to the state
// of the component
this.startTimer(value);
this.setState({ searchQuery: value });
},
render() {
let searchIcon = <Glyphicon glyph='search' className="filter-glyph"/>;
const { className } = this.props;
const { loading, searchQuery } = this.state;
if(loading) {
searchIcon = <span className="glyph-ascribe-spool-chunked ascribe-color spin"/>;
}
return (
<span className={className}>
<Input
type='text'
value={searchQuery}
placeholder={getLangText('Search%s', '...')}
onChange={this.handleChange}
addonAfter={searchIcon} />
</span>
);
}
});
export default SearchBar;

View File

@ -1,15 +1,18 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import SignupForm from './ascribe_forms/form_signup'; import SignupForm from './ascribe_forms/form_signup';
import { getLangText } from '../utils/lang_utils'; import { getLangText } from '../utils/lang_utils';
let Link = Router.Link;
let SignupContainer = React.createClass({ let SignupContainer = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return { return {
submitted: false, submitted: false,
@ -37,9 +40,11 @@ let SignupContainer = React.createClass({
} }
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<SignupForm handleSuccess={this.handleSuccess} /> <SignupForm
handleSuccess={this.handleSuccess}
location={this.props.location}/>
<div className="ascribe-login-text"> <div className="ascribe-login-text">
{getLangText('Already an ascribe user')}&#63; <Link to="login">{getLangText('Log in')}...</Link><br/> {getLangText('Already an ascribe user')}&#63; <Link to="/login">{getLangText('Log in')}...</Link><br/>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import StarRating from 'react-star-rating'; import StarRating from 'react-star-rating';
import PieceListActions from '../../../../../actions/piece_list_actions'; import PieceListActions from '../../../../../actions/piece_list_actions';
@ -21,12 +21,9 @@ import GlobalNotificationActions from '../../../../../actions/global_notificatio
import AclProxy from '../../../../acl_proxy'; import AclProxy from '../../../../acl_proxy';
import SubmitToPrizeButton from './../ascribe_buttons/submit_to_prize_button'; import SubmitToPrizeButton from './../ascribe_buttons/submit_to_prize_button';
import { getLangText } from '../../../../../utils/lang_utils'; import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils'; import { mergeOptions } from '../../../../../utils/general_utils';
let Link = Router.Link;
let AccordionListItemPrize = React.createClass({ let AccordionListItemPrize = React.createClass({
propTypes: { propTypes: {
@ -85,7 +82,7 @@ let AccordionListItemPrize = React.createClass({
return ( return (
<div id="list-rating" className="pull-right"> <div id="list-rating" className="pull-right">
<Link to='piece' params={{pieceId: this.props.content.id}}> <Link to={`/pieces/${this.props.content.id}`}>
<StarRating <StarRating
ref='rating' ref='rating'
name="prize-rating" name="prize-rating"
@ -108,7 +105,7 @@ let AccordionListItemPrize = React.createClass({
// jury and no rating yet // jury and no rating yet
return ( return (
<div className="react-rating-caption pull-right"> <div className="react-rating-caption pull-right">
<Link to='piece' params={{pieceId: this.props.content.id}}> <Link to={`/pieces/${this.props.content.id}`}>
{getLangText('Submit your rating')} {getLangText('Submit your rating')}
</Link> </Link>
</div> </div>

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import Moment from 'moment'; import Moment from 'moment';
import StarRating from 'react-star-rating'; import StarRating from 'react-star-rating';
@ -41,7 +41,6 @@ import ApiUrls from '../../../../../constants/api_urls';
import { mergeOptions } from '../../../../../utils/general_utils'; import { mergeOptions } from '../../../../../utils/general_utils';
import { getLangText } from '../../../../../utils/lang_utils'; import { getLangText } from '../../../../../utils/lang_utils';
let Link = Router.Link;
/** /**
* This is the component that implements resource/data specific functionality * This is the component that implements resource/data specific functionality
@ -169,12 +168,12 @@ let NavigationHeader = React.createClass({
return ( return (
<div style={{marginBottom: '1em'}}> <div style={{marginBottom: '1em'}}>
<div className="row no-margin"> <div className="row no-margin">
<Link className="disable-select" to='piece' params={{pieceId: nav.prev_index ? nav.prev_index : this.props.piece.id}}> <Link className="disable-select" to={`/pieces/${ nav.prev_index || this.props.piece.id }`}>
<span className="glyphicon glyphicon-chevron-left pull-left link-ascribe" aria-hidden="true"> <span className="glyphicon glyphicon-chevron-left pull-left link-ascribe" aria-hidden="true">
{getLangText('Previous')} {getLangText('Previous')}
</span> </span>
</Link> </Link>
<Link className="disable-select" to='piece' params={{pieceId: nav.next_index ? nav.next_index : this.props.piece.id}}> <Link className="disable-select" to={`/pieces/${ nav.next_index || this.props.piece.id }`}>
<span className="pull-right link-ascribe"> <span className="pull-right link-ascribe">
{getLangText('Next')} {getLangText('Next')}
<span className="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> <span className="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>

View File

@ -1,14 +1,16 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import PrizeActions from '../actions/prize_actions'; import PrizeActions from '../actions/prize_actions';
import PrizeStore from '../stores/prize_store'; import PrizeStore from '../stores/prize_store';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; import Button from 'react-bootstrap/lib/Button';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup'; import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import UserStore from '../../../../stores/user_store'; import UserStore from '../../../../stores/user_store';
import UserActions from '../../../../actions/user_actions'; import UserActions from '../../../../actions/user_actions';
@ -17,7 +19,7 @@ import { getLangText } from '../../../../utils/lang_utils';
let Landing = React.createClass({ let Landing = React.createClass({
mixins: [Router.Navigation], mixins: [History],
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
@ -44,7 +46,7 @@ let Landing = React.createClass({
// if user is already logged in, redirect him to piece list // if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email) { if(this.state.currentUser && this.state.currentUser.email) {
// FIXME: hack to redirect out of the dispatch cycle // FIXME: hack to redirect out of the dispatch cycle
window.setTimeout(() => this.replaceWith('pieces'), 0); window.setTimeout(() => this.history.replaceState(null, '/collection'), 0);
} }
}, },
@ -52,16 +54,20 @@ let Landing = React.createClass({
if (this.state.prize && this.state.prize.active){ if (this.state.prize && this.state.prize.active){
return ( return (
<ButtonGroup className="enter" bsSize="large" vertical> <ButtonGroup className="enter" bsSize="large" vertical>
<ButtonLink to="signup"> <LinkContainer to="/signup">
<Button>
{getLangText('Sign up to submit')} {getLangText('Sign up to submit')}
</ButtonLink> </Button>
</LinkContainer>
<p> <p>
{getLangText('or, already an ascribe user?')} {getLangText('or, already an ascribe user?')}
</p> </p>
<ButtonLink to="login"> <LinkContainer to="/login">
<Button>
{getLangText('Log in to submit')} {getLangText('Log in to submit')}
</ButtonLink> </Button>
</LinkContainer>
</ButtonGroup> </ButtonGroup>
); );
} }
@ -74,9 +80,11 @@ let Landing = React.createClass({
<p> <p>
{getLangText('or, already an ascribe user?')} {getLangText('or, already an ascribe user?')}
</p> </p>
<ButtonLink to="login"> <LinkContainer to="/login">
<Button>
{getLangText('Log in')} {getLangText('Log in')}
</ButtonLink> </Button>
</LinkContainer>
</ButtonGroup> </ButtonGroup>
); );
}, },

View File

@ -1,29 +1,32 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Link } from 'react-router';
import LoginForm from '../../../ascribe_forms/form_login'; import LoginForm from '../../../ascribe_forms/form_login';
import { getLangText } from '../../../../utils/lang_utils'; import { getLangText } from '../../../../utils/lang_utils';
let Link = Router.Link;
let LoginContainer = React.createClass({ let LoginContainer = React.createClass({
propTypes: {
location: React.PropTypes.object
},
render() { render() {
return ( return (
<div className="ascribe-login-wrapper"> <div className="ascribe-login-wrapper">
<LoginForm <LoginForm
headerMessage={getLangText('Log in with ascribe')} /> headerMessage={getLangText('Log in with ascribe')}
location={this.props.location}/>
<div <div
className="ascribe-login-text"> className="ascribe-login-text">
{getLangText('I\'m not a user') + ' '} {getLangText('I\'m not a user') + ' '}
<Link to="signup">{getLangText('Sign up...')}</Link> <Link to="/signup">{getLangText('Sign up...')}</Link>
<br/> <br/>
{getLangText('I forgot my password') + ' '} {getLangText('I forgot my password') + ' '}
<Link to="password_reset">{getLangText('Rescue me...')}</Link> <Link to="/password_reset">{getLangText('Rescue me...')}</Link>
</div> </div>
</div> </div>
); );

View File

@ -9,13 +9,20 @@ import UserStore from '../../../../stores/user_store';
import PrizeActions from '../actions/prize_actions'; import PrizeActions from '../actions/prize_actions';
import PrizeStore from '../stores/prize_store'; import PrizeStore from '../stores/prize_store';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; import Button from 'react-bootstrap/lib/Button';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import AccordionListItemPrize from './ascribe_accordion_list/accordion_list_item_prize'; import AccordionListItemPrize from './ascribe_accordion_list/accordion_list_item_prize';
import { mergeOptions } from '../../../../utils/general_utils'; import { mergeOptions } from '../../../../utils/general_utils';
import { getLangText } from '../../../../utils/lang_utils'; import { getLangText } from '../../../../utils/lang_utils';
let PrizePieceList = React.createClass({ let PrizePieceList = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
PrizeStore.getState(), PrizeStore.getState(),
@ -42,9 +49,11 @@ let PrizePieceList = React.createClass({
getButtonSubmit() { getButtonSubmit() {
if (this.state.prize && this.state.prize.active && !this.state.currentUser.is_jury){ if (this.state.prize && this.state.prize.active && !this.state.currentUser.is_jury){
return ( return (
<ButtonLink to="register_piece"> <LinkContainer to="/register_piece">
<Button>
{getLangText('Submit to prize')} {getLangText('Submit to prize')}
</ButtonLink> </Button>
</LinkContainer>
); );
} }
else if (this.state.prize && this.state.currentUser.is_judge){ else if (this.state.prize && this.state.currentUser.is_judge){
@ -65,12 +74,13 @@ let PrizePieceList = React.createClass({
<div> <div>
<PieceList <PieceList
ref="list" ref="list"
redirectTo="register_piece" redirectTo="/register_piece"
accordionListItemType={AccordionListItemPrize} accordionListItemType={AccordionListItemPrize}
orderParams={orderParams} orderParams={orderParams}
orderBy={this.state.currentUser.is_jury ? 'rating' : null} orderBy={this.state.currentUser.is_jury ? 'rating' : null}
filterParams={[]} filterParams={[]}
customSubmitButton={this.getButtonSubmit()}/> customSubmitButton={this.getButtonSubmit()}
location={this.props.location}/>
</div> </div>
); );
} }

View File

@ -6,6 +6,10 @@ import SignupForm from '../../../ascribe_forms/form_signup';
import { getLangText } from '../../../../utils/lang_utils'; import { getLangText } from '../../../../utils/lang_utils';
let SignupContainer = React.createClass({ let SignupContainer = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return { return {
submitted: false, submitted: false,
@ -35,7 +39,8 @@ let SignupContainer = React.createClass({
<SignupForm <SignupForm
headerMessage={getLangText('Create account for submission')} headerMessage={getLangText('Create account for submission')}
submitMessage={getLangText('Sign up')} submitMessage={getLangText('Sign up')}
handleSuccess={this.handleSuccess} /> handleSuccess={this.handleSuccess}
location={this.props.location}/>
</div> </div>
); );
} }

View File

@ -1,38 +1,44 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import Hero from './components/prize_hero'; import Hero from './components/prize_hero';
import Header from '../../header'; import Header from '../../header';
import Footer from '../../footer'; import Footer from '../../footer';
import GlobalNotification from '../../global_notification'; import GlobalNotification from '../../global_notification';
import getRoutes from './prize_routes';
import { getSubdomain } from '../../../utils/general_utils'; import { getSubdomain } from '../../../utils/general_utils';
let RouteHandler = Router.RouteHandler;
let PrizeApp = React.createClass({ let PrizeApp = React.createClass({
mixins: [Router.State], propTypes: {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
]),
history: React.PropTypes.object,
routes: React.PropTypes.arrayOf(React.PropTypes.object)
},
render() { render() {
const { history, routes } = this.props;
let header = null; let header = null;
let subdomain = getSubdomain(); let subdomain = getSubdomain();
let ROUTES = getRoutes(null, subdomain); // The second element of routes is always the active component object, where we can
// extract the path.
let path = routes[1] ? routes[1].path : null;
if (this.isActive('landing') || this.isActive('login') || this.isActive('signup')) { // if the path of the current activeRoute is not defined, then this is the IndexRoute
if (!path || history.isActive('/login') || history.isActive('/signup')) {
header = <Hero />; header = <Hero />;
} else { } else {
header = <Header showAddWork={false} routes={ROUTES}/>; header = <Header showAddWork={false} routes={routes}/>;
} }
return ( return (
<div className={'container ascribe-prize-app client--' + subdomain}> <div className={'container ascribe-prize-app client--' + subdomain}>
{header} {header}
<RouteHandler /> {this.props.children}
<GlobalNotification /> <GlobalNotification />
<div id="modal" className="container"></div> <div id="modal" className="container"></div>
<Footer /> <Footer />

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Route, IndexRoute } from 'react-router';
import Landing from './components/prize_landing'; import Landing from './components/prize_landing';
import LoginContainer from './components/prize_login_container'; import LoginContainer from './components/prize_login_container';
@ -17,28 +17,42 @@ import CoaVerifyContainer from '../../../components/coa_verify_container';
import ErrorNotFoundPage from '../../../components/error_not_found_page'; import ErrorNotFoundPage from '../../../components/error_not_found_page';
import App from './prize_app'; import App from './prize_app';
import AppConstants from '../../../constants/application_constants';
let Route = Router.Route; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler';
let NotFoundRoute = Router.NotFoundRoute;
let baseUrl = AppConstants.baseUrl;
function getRoutes() { function getRoutes() {
return ( return (
<Route name="app" path={baseUrl} handler={App}> <Route path='/' component={App}>
<Route name="landing" path={baseUrl} handler={Landing} /> <IndexRoute component={Landing} />
<Route name="login" path="login" handler={LoginContainer} /> <Route
<Route name="logout" path="logout" handler={LogoutContainer} /> path='login'
<Route name="signup" path="signup" handler={SignupContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route
<Route name="register_piece" path="register_piece" handler={PrizeRegisterPiece} headerTitle="+ NEW WORK" /> path='logout'
<Route name="pieces" path="collection" handler={PrizePieceList} headerTitle="COLLECTION" /> component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
<Route name="piece" path="pieces/:pieceId" handler={PrizePieceContainer} /> <Route
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> path='signup'
<Route name="settings" path="settings" handler={SettingsContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> <Route
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} /> path='password_reset'
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(PasswordResetContainer)} />
<Route
path='settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(SettingsContainer)}/>
<Route
path='register_piece'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(PrizeRegisterPiece)}
headerTitle='+ NEW WORK'/>
<Route
path='collection'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(PrizePieceList)}
headerTitle='COLLECTION'/>
<Route path='pieces/:pieceId' component={PrizePieceContainer} />
<Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
</Route> </Route>
); );
} }

View File

@ -11,6 +11,9 @@ import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils'; import { mergeOptions } from '../../../../../utils/general_utils';
let CCRegisterPiece = React.createClass({ let CCRegisterPiece = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
@ -82,7 +85,8 @@ let CCRegisterPiece = React.createClass({
<RegisterPiece <RegisterPiece
enableLocalHashing={false} enableLocalHashing={false}
headerMessage={getLangText('Register under a Creative Commons license')} headerMessage={getLangText('Register under a Creative Commons license')}
submitMessage={getLangText('Submit')}> submitMessage={getLangText('Submit')}
location={this.props.location}>
{this.getLicenses()} {this.getLicenses()}
</RegisterPiece> </RegisterPiece>
); );

View File

@ -3,7 +3,9 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; import Button from 'react-bootstrap/lib/Button';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import WhitelabelActions from '../../../../../../actions/whitelabel_actions'; import WhitelabelActions from '../../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../../stores/whitelabel_store'; import WhitelabelStore from '../../../../../../stores/whitelabel_store';
@ -37,29 +39,21 @@ let CylandSubmitButton = React.createClass({
}, },
render() { render() {
let piece = this.props.piece; const { piece, className } = this.props;
let startFrom = 1;
// In the Cyland register page a user has to complete three steps.
// Since every one of those steps is atomic a user should always be able to continue
// where he left of.
// This is why we start the process form slide 1/2 if the user has already finished
// it in another session.
if(piece && piece.extra_data && Object.keys(piece.extra_data).length > 0) {
startFrom = 2;
}
return ( return (
<ButtonLink <LinkContainer
to="register_piece" to="/register_piece"
query={{ query={{
'slide_num': 0, 'slide_num': 0,
'start_from': startFrom, 'start_from': 1,
'piece_id': this.props.piece.id 'piece_id': piece.id
}} }}>
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}> <Button
className={classNames('btn', 'btn-default', 'btn-xs', className)}>
{getLangText('Submit to Cyland')} {getLangText('Submit to Cyland')}
</ButtonLink> </Button>
</LinkContainer>
); );
} }
}); });

View File

@ -22,6 +22,7 @@ import { mergeOptions } from '../../../../../../utils/general_utils';
let CylandPieceContainer = React.createClass({ let CylandPieceContainer = React.createClass({
propTypes: { propTypes: {
location: React.PropTypes.object,
params: React.PropTypes.object params: React.PropTypes.object
}, },
@ -72,7 +73,8 @@ let CylandPieceContainer = React.createClass({
<CylandAdditionalDataForm <CylandAdditionalDataForm
piece={this.state.piece} piece={this.state.piece}
disabled={!this.state.piece.acl.acl_edit} disabled={!this.state.piece.acl.acl_edit}
isInline={true} /> isInline={true}
location={this.props.location}/>
</CollapsibleParagraph> </CollapsibleParagraph>
</WalletPieceContainer> </WalletPieceContainer>
); );

View File

@ -26,7 +26,8 @@ let CylandAdditionalDataForm = React.createClass({
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired, piece: React.PropTypes.object.isRequired,
disabled: React.PropTypes.bool, disabled: React.PropTypes.bool,
isInline: React.PropTypes.bool isInline: React.PropTypes.bool,
location: React.PropTypes.object
}, },
getDefaultProps() { getDefaultProps() {
@ -142,7 +143,8 @@ let CylandAdditionalDataForm = React.createClass({
isReadyForFormSubmission={formSubmissionValidation.fileOptional} isReadyForFormSubmission={formSubmissionValidation.fileOptional}
pieceId={piece.id} pieceId={piece.id}
otherData={piece.other_data} otherData={piece.other_data}
multiple={true}/> multiple={true}
location={this.props.location}/>
</Form> </Form>
); );
} else { } else {

View File

@ -1,12 +1,15 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import WhitelabelActions from '../../../../../actions/whitelabel_actions'; import WhitelabelActions from '../../../../../actions/whitelabel_actions';
import WhitelabelStore from '../../../../../stores/whitelabel_store'; import WhitelabelStore from '../../../../../stores/whitelabel_store';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; import Button from 'react-bootstrap/lib/Button';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import UserStore from '../../../../../stores/user_store'; import UserStore from '../../../../../stores/user_store';
import UserActions from '../../../../../actions/user_actions'; import UserActions from '../../../../../actions/user_actions';
@ -16,7 +19,7 @@ import { getLangText } from '../../../../../utils/lang_utils';
let CylandLanding = React.createClass({ let CylandLanding = React.createClass({
mixins: [Router.Navigation], mixins: [History],
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
@ -43,7 +46,7 @@ let CylandLanding = React.createClass({
// if user is already logged in, redirect him to piece list // if user is already logged in, redirect him to piece list
if(this.state.currentUser && this.state.currentUser.email) { if(this.state.currentUser && this.state.currentUser.email) {
// FIXME: hack to redirect out of the dispatch cycle // FIXME: hack to redirect out of the dispatch cycle
window.setTimeout(() => this.replaceWith('pieces'), 0); window.setTimeout(() => this.history.replaceState(null, '/collection'), 0);
} }
}, },
@ -67,17 +70,21 @@ let CylandLanding = React.createClass({
<p> <p>
{getLangText('Existing ascribe user?')} {getLangText('Existing ascribe user?')}
</p> </p>
<ButtonLink to="login"> <LinkContainer to="/login">
<Button>
{getLangText('Log in')} {getLangText('Log in')}
</ButtonLink> </Button>
</LinkContainer>
</div> </div>
<div className="col-sm-6"> <div className="col-sm-6">
<p> <p>
{getLangText('Do you need an account?')} {getLangText('Do you need an account?')}
</p> </p>
<ButtonLink to="signup"> <LinkContainer to="/signup">
<Button>
{getLangText('Sign up')} {getLangText('Sign up')}
</ButtonLink> </Button>
</LinkContainer>
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,6 +12,10 @@ import { getLangText } from '../../../../../utils/lang_utils';
let CylandPieceList = React.createClass({ let CylandPieceList = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
}, },
@ -33,7 +37,7 @@ let CylandPieceList = React.createClass({
return ( return (
<div> <div>
<PieceList <PieceList
redirectTo="register_piece" redirectTo="/register_piece?slide_num=0"
accordionListItemType={CylandAccordionListItem} accordionListItemType={CylandAccordionListItem}
filterParams={[{ filterParams={[{
label: getLangText('Show works I have'), label: getLangText('Show works I have'),
@ -42,7 +46,7 @@ let CylandPieceList = React.createClass({
label: getLangText('loaned to Cyland') label: getLangText('loaned to Cyland')
}] }]
}]} }]}
/> location={this.props.location}/>
</div> </div>
); );
} }

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import Moment from 'moment'; import Moment from 'moment';
@ -39,8 +39,11 @@ import { getAclFormMessage } from '../../../../../utils/form_utils';
let CylandRegisterPiece = React.createClass({ let CylandRegisterPiece = React.createClass({
propTypes: {
location: React.PropTypes.object
},
mixins: [Router.Navigation, Router.State], mixins: [History],
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
@ -63,7 +66,7 @@ let CylandRegisterPiece = React.createClass({
UserActions.fetchCurrentUser(); UserActions.fetchCurrentUser();
WhitelabelActions.fetchWhitelabel(); WhitelabelActions.fetchWhitelabel();
let queryParams = this.getQuery(); let queryParams = this.props.location.query;
// Since every step of this register process is atomic, // Since every step of this register process is atomic,
// we may need to enter the process at step 1 or 2. // we may need to enter the process at step 1 or 2.
@ -101,12 +104,13 @@ let CylandRegisterPiece = React.createClass({
// also start loading the piece for the next step // also start loading the piece for the next step
if(response && response.piece) { if(response && response.piece) {
PieceActions.updatePiece({});
PieceActions.updatePiece(response.piece); PieceActions.updatePiece(response.piece);
} }
this.incrementStep(); this.incrementStep();
this.refs.slidesContainer.nextSlide(); this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id });
}, },
handleAdditionalDataSuccess() { handleAdditionalDataSuccess() {
@ -130,7 +134,8 @@ let CylandRegisterPiece = React.createClass({
this.refreshPieceList(); this.refreshPieceList();
PieceActions.fetchOne(this.state.piece.id); PieceActions.fetchOne(this.state.piece.id);
this.transitionTo('piece', {pieceId: this.state.piece.id});
this.history.pushState(null, `/pieces/${this.state.piece.id}`);
}, },
// We need to increase the step to lock the forms that are already filled out // We need to increase the step to lock the forms that are already filled out
@ -163,7 +168,7 @@ let CylandRegisterPiece = React.createClass({
// basically redirects to the second slide (index: 1), when the user is not logged in // basically redirects to the second slide (index: 1), when the user is not logged in
onLoggedOut() { onLoggedOut() {
this.transitionTo('login'); this.history.pushState(null, '/login');
}, },
render() { render() {
@ -179,7 +184,8 @@ let CylandRegisterPiece = React.createClass({
glyphiconClassNames={{ glyphiconClassNames={{
pending: 'glyphicon glyphicon-chevron-right', pending: 'glyphicon glyphicon-chevron-right',
completed: 'glyphicon glyphicon-lock' completed: 'glyphicon glyphicon-lock'
}}> }}
location={this.props.location}>
<div data-slide-title={getLangText('Register work')}> <div data-slide-title={getLangText('Register work')}>
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
@ -190,7 +196,8 @@ let CylandRegisterPiece = React.createClass({
submitMessage={getLangText('Submit')} submitMessage={getLangText('Submit')}
isFineUploaderActive={this.state.isFineUploaderActive} isFineUploaderActive={this.state.isFineUploaderActive}
handleSuccess={this.handleRegisterSuccess} handleSuccess={this.handleRegisterSuccess}
onLoggedOut={this.onLoggedOut} /> onLoggedOut={this.onLoggedOut}
location={this.props.location}/>
</Col> </Col>
</Row> </Row>
</div> </div>
@ -200,7 +207,8 @@ let CylandRegisterPiece = React.createClass({
<CylandAdditionalDataForm <CylandAdditionalDataForm
disabled={this.state.step > 1} disabled={this.state.step > 1}
handleSuccess={this.handleAdditionalDataSuccess} handleSuccess={this.handleAdditionalDataSuccess}
piece={this.state.piece}/> piece={this.state.piece}
location={this.props.location}/>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -2,7 +2,10 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
import Button from 'react-bootstrap/lib/Button';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import { getLangText } from '../../../../../../utils/lang_utils'; import { getLangText } from '../../../../../../utils/lang_utils';
@ -32,16 +35,18 @@ let IkonotvSubmitButton = React.createClass({
} }
return ( return (
<ButtonLink <LinkContainer
to="register_piece" to="/register_piece"
query={{ query={{
'slide_num': 0, 'slide_num': 0,
'start_from': startFrom, 'start_from': startFrom,
'piece_id': piece.id 'piece_id': piece.id
}} }}>
<Button
className={classNames('ascribe-margin-1px', this.props.className)}> className={classNames('ascribe-margin-1px', this.props.className)}>
{getLangText('Loan to IkonoTV')} {getLangText('Loan to IkonoTV')}
</ButtonLink> </Button>
</LinkContainer>
); );
} }
}); });

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { History } from 'react-router';
import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Button from 'react-bootstrap/lib/Button'; import Button from 'react-bootstrap/lib/Button';
@ -29,11 +29,10 @@ import AppConstants from '../../../../../constants/application_constants';
import { getLangText } from '../../../../../utils/lang_utils'; import { getLangText } from '../../../../../utils/lang_utils';
import { mergeOptions } from '../../../../../utils/general_utils'; import { mergeOptions } from '../../../../../utils/general_utils';
let Navigation = Router.Navigation;
let IkonotvContractNotifications = React.createClass({ let IkonotvContractNotifications = React.createClass({
mixins: [Navigation], mixins: [History],
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
@ -114,7 +113,7 @@ let IkonotvContractNotifications = React.createClass({
handleConfirmSuccess() { handleConfirmSuccess() {
let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000); let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces'); this.history.pushState(null, '/collection');
}, },
handleDeny() { handleDeny() {
@ -127,7 +126,7 @@ let IkonotvContractNotifications = React.createClass({
handleDenySuccess() { handleDenySuccess() {
let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000); let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification); GlobalNotificationActions.appendGlobalNotification(notification);
this.transitionTo('pieces'); this.history.pushState(null, '/collection');
}, },
getCopyrightAssociationForm(){ getCopyrightAssociationForm(){

View File

@ -1,9 +1,10 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink'; import Button from 'react-bootstrap/lib/Button';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import UserStore from '../../../../../stores/user_store'; import UserStore from '../../../../../stores/user_store';
import UserActions from '../../../../../actions/user_actions'; import UserActions from '../../../../../actions/user_actions';
@ -12,8 +13,9 @@ import { getLangText } from '../../../../../utils/lang_utils';
let IkonotvLanding = React.createClass({ let IkonotvLanding = React.createClass({
propTypes: {
mixins: [Router.Navigation, Router.State], location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
@ -33,18 +35,21 @@ let IkonotvLanding = React.createClass({
}, },
getEnterButton() { getEnterButton() {
let redirect = 'login'; let redirect = '/login';
if(this.state.currentUser && this.state.currentUser.email) { if(this.state.currentUser && this.state.currentUser.email) {
redirect = 'pieces'; redirect = '/collection';
} }
else if (this.getQuery() && this.getQuery().redirect) { else if (this.props.location.query && this.props.location.query.redirect) {
redirect = this.getQuery().redirect; redirect = '/' + this.props.location.query.redirect;
} }
return ( return (
<ButtonLink to={redirect} query={this.getQuery()}> <LinkContainer to={redirect} query={this.props.location.query}>
<Button>
{getLangText('ENTER TO START')} {getLangText('ENTER TO START')}
</ButtonLink> </Button>
</LinkContainer>
); );
}, },

View File

@ -12,6 +12,10 @@ import { getLangText } from '../../../../../utils/lang_utils';
let IkonotvPieceList = React.createClass({ let IkonotvPieceList = React.createClass({
propTypes: {
location: React.PropTypes.object
},
getInitialState() { getInitialState() {
return UserStore.getState(); return UserStore.getState();
}, },
@ -33,7 +37,7 @@ let IkonotvPieceList = React.createClass({
return ( return (
<div> <div>
<PieceList <PieceList
redirectTo="register_piece" redirectTo="/register_piece?slide_num=0"
accordionListItemType={IkonotvAccordionListItem} accordionListItemType={IkonotvAccordionListItem}
filterParams={[{ filterParams={[{
label: getLangText('Show works I have'), label: getLangText('Show works I have'),
@ -47,7 +51,8 @@ let IkonotvPieceList = React.createClass({
label: getLangText('loaned') label: getLangText('loaned')
} }
] ]
}]}/> }]}
location={this.props.location}/>
</div> </div>
); );
} }

View File

@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import Moment from 'moment'; import Moment from 'moment';
import Router from 'react-router'; import { History } from 'react-router';
import Col from 'react-bootstrap/lib/Col'; import Col from 'react-bootstrap/lib/Col';
import Row from 'react-bootstrap/lib/Row'; import Row from 'react-bootstrap/lib/Row';
@ -33,13 +33,13 @@ import { mergeOptions } from '../../../../../utils/general_utils';
import { getLangText } from '../../../../../utils/lang_utils'; import { getLangText } from '../../../../../utils/lang_utils';
let IkonotvRegisterPiece = React.createClass({ let IkonotvRegisterPiece = React.createClass({
propTypes: { propTypes: {
handleSuccess: React.PropTypes.func, handleSuccess: React.PropTypes.func,
piece: React.PropTypes.object.isRequired piece: React.PropTypes.object.isRequired,
location: React.PropTypes.object
}, },
mixins: [Router.Navigation, Router.State], mixins: [History],
getInitialState(){ getInitialState(){
return mergeOptions( return mergeOptions(
@ -61,7 +61,7 @@ let IkonotvRegisterPiece = React.createClass({
// not want to display to the user. // not want to display to the user.
PieceActions.updatePiece({}); PieceActions.updatePiece({});
let queryParams = this.getQuery(); let queryParams = this.props.location.query;
// Since every step of this register process is atomic, // Since every step of this register process is atomic,
// we may need to enter the process at step 1 or 2. // we may need to enter the process at step 1 or 2.
@ -102,7 +102,7 @@ let IkonotvRegisterPiece = React.createClass({
PieceActions.updatePiece(response.piece); PieceActions.updatePiece(response.piece);
} }
if (!this.canSubmit()) { if (!this.canSubmit()) {
this.transitionTo('pieces'); this.history.pushState(null, '/collection');
} }
else { else {
this.incrementStep(); this.incrementStep();
@ -132,7 +132,7 @@ let IkonotvRegisterPiece = React.createClass({
this.refreshPieceList(); this.refreshPieceList();
PieceActions.fetchOne(this.state.piece.id); PieceActions.fetchOne(this.state.piece.id);
this.transitionTo('piece', {pieceId: this.state.piece.id}); this.history.pushState(null, `/pieces/${this.state.piece.id}`);
}, },
// We need to increase the step to lock the forms that are already filled out // We need to increase the step to lock the forms that are already filled out
@ -165,7 +165,7 @@ let IkonotvRegisterPiece = React.createClass({
// basically redirects to the second slide (index: 1), when the user is not logged in // basically redirects to the second slide (index: 1), when the user is not logged in
onLoggedOut() { onLoggedOut() {
this.transitionTo('login'); this.history.pushState(null, '/login');
}, },
canSubmit() { canSubmit() {
@ -245,7 +245,8 @@ let IkonotvRegisterPiece = React.createClass({
glyphiconClassNames={{ glyphiconClassNames={{
pending: 'glyphicon glyphicon-chevron-right', pending: 'glyphicon glyphicon-chevron-right',
completed: 'glyphicon glyphicon-lock' completed: 'glyphicon glyphicon-lock'
}}> }}
location={this.props.location}>
<div data-slide-title={getLangText('Register work')}> <div data-slide-title={getLangText('Register work')}>
<Row className="no-margin"> <Row className="no-margin">
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}> <Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
@ -256,7 +257,8 @@ let IkonotvRegisterPiece = React.createClass({
submitMessage={getLangText('Register')} submitMessage={getLangText('Register')}
isFineUploaderActive={this.state.isFineUploaderActive} isFineUploaderActive={this.state.isFineUploaderActive}
handleSuccess={this.handleRegisterSuccess} handleSuccess={this.handleRegisterSuccess}
onLoggedOut={this.onLoggedOut} /> onLoggedOut={this.onLoggedOut}
location={this.props.location}/>
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -1,16 +0,0 @@
'use strict';
import React from 'react';
import ContractAgreementForm from '../../../../ascribe_forms/form_contract_agreement';
let IkonotvRequestLoan = React.createClass({
render() {
return (
<ContractAgreementForm />
);
}
});
export default IkonotvRequestLoan;

View File

@ -1,43 +1,50 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router';
import Header from '../../header'; import Header from '../../header';
import Footer from '../../footer'; import Footer from '../../footer';
import GlobalNotification from '../../global_notification'; import GlobalNotification from '../../global_notification';
import getRoutes from './wallet_routes';
import classNames from 'classnames'; import classNames from 'classnames';
import { getSubdomain } from '../../../utils/general_utils'; import { getSubdomain } from '../../../utils/general_utils';
let RouteHandler = Router.RouteHandler;
let WalletApp = React.createClass({ let WalletApp = React.createClass({
mixins: [Router.State], propTypes: {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
]),
history: React.PropTypes.object,
routes: React.PropTypes.arrayOf(React.PropTypes.object)
},
render() { render() {
let subdomain = getSubdomain();
let ROUTES = getRoutes(null, subdomain);
let activeRoutes = this.getRoutes().map(elem => 'route--' + elem.name);
let header = null; let header = null;
if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup') || this.isActive('contract_notifications')) let subdomain = getSubdomain();
const { history, routes, children } = this.props;
// The second element of routes is always the active component object, where we can
// extract the path.
let path = routes[1] ? routes[1].path : null;
// if the path of the current activeRoute is not defined, then this is the IndexRoute
if ((!path || history.isActive('/login') || history.isActive('/signup') || history.isActive('/contract_notifications'))
&& (['ikonotv']).indexOf(subdomain) > -1) { && (['ikonotv']).indexOf(subdomain) > -1) {
header = ( header = (<div className="hero"/>);
<div className="hero"/>);
} else { } else {
header = <Header showAddWork={true} routes={ROUTES} />; header = <Header showAddWork={true} routes={routes} />;
} }
// In react-router 1.0, Routes have no 'name' property anymore. To keep functionality however,
// we split the path by the first occurring slash and take the first splitter.
return ( return (
<div className={classNames('ascribe-wallet-app', activeRoutes)}> <div className={classNames('ascribe-wallet-app', 'route--' + (path ? path.split('/')[0] : 'landing'))}>
<div className='container'> <div className='container'>
{header} {header}
<RouteHandler /> {children}
<GlobalNotification /> <GlobalNotification />
<div id="modal" className="container"></div> <div id="modal" className="container"></div>
<Footer /> <Footer />

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Route, IndexRoute } from 'react-router';
// general components // general components
import CoaVerifyContainer from '../../../components/coa_verify_container'; import CoaVerifyContainer from '../../../components/coa_verify_container';
@ -23,74 +23,130 @@ import CylandPieceList from './components/cyland/cyland_piece_list';
import IkonotvLanding from './components/ikonotv/ikonotv_landing'; import IkonotvLanding from './components/ikonotv/ikonotv_landing';
import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list'; import IkonotvPieceList from './components/ikonotv/ikonotv_piece_list';
import IkonotvRequestLoan from './components/ikonotv/ikonotv_request_loan'; import ContractAgreementForm from '../../../components/ascribe_forms/form_contract_agreement';
import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece'; import IkonotvRegisterPiece from './components/ikonotv/ikonotv_register_piece';
import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container'; import IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container';
import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications'; import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications';
import CCRegisterPiece from './components/cc/cc_register_piece'; import CCRegisterPiece from './components/cc/cc_register_piece';
import WalletApp from './wallet_app'; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler';
import AppConstants from '../../../constants/application_constants';
let Route = Router.Route; import WalletApp from './wallet_app';
let NotFoundRoute = Router.NotFoundRoute;
let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl;
let ROUTES = { let ROUTES = {
'cyland': ( 'cyland': (
<Route name="app" path={baseUrl} handler={WalletApp}> <Route path='/' component={WalletApp}>
<Route name="landing" path={baseUrl} handler={CylandLanding} /> <IndexRoute component={CylandLanding} />
<Route name="login" path="login" handler={LoginContainer} /> <Route
<Route name="logout" path="logout" handler={LogoutContainer} /> path='login'
<Route name="signup" path="signup" handler={SignupContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route
<Route name="register_piece" path="register_piece" handler={CylandRegisterPiece} headerTitle="+ NEW WORK" /> path='logout'
<Route name="pieces" path="collection" handler={CylandPieceList} headerTitle="COLLECTION" /> component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
<Route name="piece" path="pieces/:pieceId" handler={CylandPieceContainer} /> <Route
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> path='signup'
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
<Route name="settings" path="settings" handler={SettingsContainer} /> <Route
<Route name="contract_settings" path="contract_settings" handler={ContractSettings} /> path='password_reset'
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(PasswordResetContainer)} />
<Route
path='settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(SettingsContainer)}/>
<Route
path='contract_settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/>
<Route
path='register_piece'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(CylandRegisterPiece)}
headerTitle='+ NEW WORK'/>
<Route
path='collection'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(CylandPieceList)}
headerTitle='COLLECTION'/>
<Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='pieces/:pieceId' component={CylandPieceContainer} />
<Route path='*' component={ErrorNotFoundPage} />
</Route> </Route>
), ),
'cc': ( 'cc': (
<Route name="app" path={baseUrl} handler={WalletApp}> <Route path='/' component={WalletApp}>
<Redirect from={baseUrl} to="login" /> <Route
<Redirect from={baseUrl + '/'} to="login" /> path='login'
<Route name="login" path="login" handler={LoginContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
<Route name="logout" path="logout" handler={LogoutContainer} /> <Route
<Route name="signup" path="signup" handler={SignupContainer} /> path='logout'
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
<Route name="register_piece" path="register_piece" handler={CCRegisterPiece} headerTitle="+ NEW WORK" /> <Route
<Route name="pieces" path="collection" handler={PieceList} headerTitle="COLLECTION" /> path='signup'
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> <Route
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> path='password_reset'
<Route name="settings" path="settings" handler={SettingsContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(PasswordResetContainer)} />
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} /> <Route
path='settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(SettingsContainer)}/>
<Route
path='contract_settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/>
<Route
path='register_piece'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(CCRegisterPiece)}
headerTitle='+ NEW WORK'/>
<Route
path='collection'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(PieceList)}
headerTitle='COLLECTION'/>
<Route path='pieces/:pieceId' component={PieceContainer} />
<Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
</Route> </Route>
), ),
'ikonotv': ( 'ikonotv': (
<Route name="app" path={baseUrl} handler={WalletApp}> <Route path='/' component={WalletApp}>
<Route name="landing" path={baseUrl} handler={IkonotvLanding} /> <IndexRoute component={IkonotvLanding} />
<Route name="login" path="login" handler={LoginContainer} /> <Route
<Route name="logout" path="logout" handler={LogoutContainer} /> path='login'
<Route name="signup" path="signup" handler={SignupContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> <Route
<Route name="request_loan" path="request_loan" handler={IkonotvRequestLoan} headerTitle="SEND NEW CONTRACT" aclName="acl_create_contractagreement" /> path='logout'
<Route name="register_piece" path="register_piece" handler={IkonotvRegisterPiece} headerTitle="+ NEW WORK" aclName="acl_create_piece"/> component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
<Route name="pieces" path="collection" handler={IkonotvPieceList} headerTitle="COLLECTION"/> <Route
<Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} /> path='signup'
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> <Route
<Route name="settings" path="settings" handler={SettingsContainer} /> path='password_reset'
<Route name="contract_settings" path="contract_settings" handler={ContractSettings} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(PasswordResetContainer)} />
<Route name="contract_notifications" path="contract_notifications" handler={IkonotvContractNotifications} /> <Route
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} /> path='settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(SettingsContainer)}/>
<Route
path='contract_settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/>
<Route
path='request_loan'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractAgreementForm)}
headerTitle='SEND NEW CONTRACT'
aclName='acl_create_contractagreement'/>
<Route
path='register_piece'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(IkonotvRegisterPiece)}
headerTitle='+ NEW WORK'
aclName='acl_create_piece'/>
<Route
path='collection'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(IkonotvPieceList)}
headerTitle='COLLECTION'/>
<Route
path='contract_notifications'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(IkonotvContractNotifications)}/>
<Route path='pieces/:pieceId' component={IkonotvPieceContainer} />
<Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
</Route> </Route>
) )
}; };

View File

@ -78,7 +78,8 @@ let constants = {
'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA', 'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA',
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART', 'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV', 'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV',
'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA'] 'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA'],
'searchThreshold': 500
}; };
export default constants; export default constants;

13
js/history.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
import useBasename from 'history/lib/useBasename';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import AppConstants from './constants/application_constants';
// Remove the trailing slash if present
let baseUrl = AppConstants.baseUrl.replace(/\/$/, '');
export default useBasename(createBrowserHistory)({
basename: baseUrl
});

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import Router from 'react-router'; import { Route } from 'react-router';
import getPrizeRoutes from './components/whitelabel/prize/prize_routes'; import getPrizeRoutes from './components/whitelabel/prize/prize_routes';
import getWalletRoutes from './components/whitelabel/wallet/wallet_routes'; import getWalletRoutes from './components/whitelabel/wallet/wallet_routes';
@ -25,30 +25,41 @@ import ErrorNotFoundPage from './components/error_not_found_page';
import RegisterPiece from './components/register_piece'; import RegisterPiece from './components/register_piece';
import AppConstants from './constants/application_constants'; import AuthProxyHandler from './components/ascribe_routes/proxy_routes/auth_proxy_handler';
let Route = Router.Route;
let NotFoundRoute = Router.NotFoundRoute;
let Redirect = Router.Redirect;
let baseUrl = AppConstants.baseUrl;
const COMMON_ROUTES = ( let COMMON_ROUTES = (
<Route name="app" path={baseUrl} handler={App}> <Route path='/' component={App}>
<Redirect from={baseUrl} to="login" /> <Route
<Redirect from={baseUrl + '/'} to="login" /> path='login'
<Route name="signup" path="signup" handler={SignupContainer} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
<Route name="login" path="login" handler={LoginContainer} /> <Route
<Route name="logout" path="logout" handler={LogoutContainer} /> path='register_piece'
<Route name="register_piece" path="register_piece" handler={RegisterPiece} headerTitle="+ NEW WORK" /> component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(RegisterPiece)}
<Route name="pieces" path="collection" handler={PieceList} headerTitle="COLLECTION" /> headerTitle='+ NEW WORK'/>
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} /> <Route
<Route name="edition" path="editions/:editionId" handler={EditionContainer} /> path='collection'
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} /> component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(PieceList)}
<Route name="settings" path="settings" handler={SettingsContainer} /> headerTitle='COLLECTION'/>
<Route name="contract_settings" path="contract_settings" handler={ContractSettings} /> <Route
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} /> path='signup'
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} /> component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
<Route
path='logout'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(LogoutContainer)}/>
<Route path='pieces/:pieceId' component={PieceContainer} />
<Route path='editions/:editionId' component={EditionContainer} />
<Route
path='password_reset'
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(PasswordResetContainer)} />
<Route
path='settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(SettingsContainer)}/>
<Route
path='contract_settings'
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(ContractSettings)}/>
<Route path='coa_verify' component={CoaVerifyContainer} />
<Route path='*' component={ErrorNotFoundPage} />
</Route> </Route>
); );

View File

@ -1,6 +1,9 @@
'use strict'; 'use strict';
import history from '../history';
import { altThirdParty } from '../alt'; import { altThirdParty } from '../alt';
import EventActions from '../actions/event_actions'; import EventActions from '../actions/event_actions';
import NotificationActions from '../actions/notification_actions'; import NotificationActions from '../actions/notification_actions';
@ -9,7 +12,6 @@ import { getSubdomain } from '../utils/general_utils';
class NotificationsHandler { class NotificationsHandler {
constructor() { constructor() {
this.bindActions(EventActions); this.bindActions(EventActions);
this.loaded = false; this.loaded = false;
@ -19,6 +21,7 @@ class NotificationsHandler {
if (this.loaded) { if (this.loaded) {
return; return;
} }
let subdomain = getSubdomain(); let subdomain = getSubdomain();
if (subdomain === 'ikonotv') { if (subdomain === 'ikonotv') {
NotificationActions.fetchContractAgreementListNotifications().then( NotificationActions.fetchContractAgreementListNotifications().then(
@ -26,7 +29,7 @@ class NotificationsHandler {
if (res.notifications && res.notifications.length > 0) { if (res.notifications && res.notifications.length > 0) {
this.loaded = true; this.loaded = true;
console.log('Contractagreement notifications loaded'); console.log('Contractagreement notifications loaded');
setTimeout(() => window.appRouter.transitionTo('contract_notifications'), 0); history.pushState(null, '/contract_notifications');
} }
} }
); );

View File

@ -8,8 +8,8 @@ export function getSubdomainSettings(subdomain) {
if(settings.length === 1) { if(settings.length === 1) {
return settings[0]; return settings[0];
} else if(settings.length === 0) { } else if(settings.length === 0) {
console.warn('There are no subdomain settings for the subdomain: ' + subdomain);
return appConstants.defaultDomain; return appConstants.defaultDomain;
// throw new Error('There are no subdomain settings for the subdomain: ' + subdomain);
} else { } else {
throw new Error('Matched multiple subdomains. Adjust constants file.'); throw new Error('Matched multiple subdomains. Adjust constants file.');
} }

View File

@ -0,0 +1,26 @@
'use strict';
/**
* PLEASE
*
* postfix your function with '-Available'.
*
* Like this:
*
* featureNameAvailable
*
*/
/**
* Even though it is not recommended to check (and maintain) the used browser,
* we're checking the browser's ability to drag and drop with this statement as
* there is no other way of detecting it another way.
*
* See this discussion for clarity:
* - https://github.com/Modernizr/Modernizr/issues/57#issuecomment-35831605
*
* @type {bool} Is drag and drop available on this browser
*/
export const dragAndDropAvailable = 'draggable' in document.createElement('div') &&
!/Mobile|Android|Slick\/|Kindle|BlackBerry|Opera Mini|Opera Mobi/i.test(navigator.userAgent);

View File

@ -45,7 +45,7 @@
"bootstrap-sass": "^3.3.4", "bootstrap-sass": "^3.3.4",
"browser-sync": "^2.7.5", "browser-sync": "^2.7.5",
"browserify": "^9.0.8", "browserify": "^9.0.8",
"browserify-shim": "^3.8.9", "browserify-shim": "^3.8.10",
"classnames": "^1.2.2", "classnames": "^1.2.2",
"compression": "^1.4.4", "compression": "^1.4.4",
"envify": "^3.4.0", "envify": "^3.4.0",
@ -64,6 +64,8 @@
"gulp-uglify": "^1.2.0", "gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.4", "gulp-util": "^3.0.4",
"harmonize": "^1.4.2", "harmonize": "^1.4.2",
"history": "^1.11.1",
"invariant": "^2.1.1",
"isomorphic-fetch": "^2.0.2", "isomorphic-fetch": "^2.0.2",
"jest-cli": "^0.4.0", "jest-cli": "^0.4.0",
"lodash": "^3.9.3", "lodash": "^3.9.3",
@ -72,11 +74,11 @@
"opn": "^3.0.2", "opn": "^3.0.2",
"q": "^1.4.1", "q": "^1.4.1",
"raven-js": "^1.1.19", "raven-js": "^1.1.19",
"react": "^0.13.2", "react": "0.13.2",
"react-bootstrap": "^0.25.1", "react-bootstrap": "0.25.1",
"react-datepicker": "^0.12.0", "react-datepicker": "^0.12.0",
"react-router": "^0.13.3", "react-router": "^1.0.0-rc1",
"react-router-bootstrap": "~0.16.0", "react-router-bootstrap": "^0.19.0",
"react-star-rating": "~1.3.2", "react-star-rating": "~1.3.2",
"react-textarea-autosize": "^2.5.2", "react-textarea-autosize": "^2.5.2",
"reactify": "^1.1.0", "reactify": "^1.1.0",

View File

@ -21,7 +21,6 @@
.notification-contract-pdf, .notification-contract-footer { .notification-contract-pdf, .notification-contract-footer {
width: 100%; width: 100%;
max-width: 750px;
margin: 0 auto; margin: 0 auto;
} }
.notification-contract-pdf { .notification-contract-pdf {
@ -29,7 +28,8 @@
border: 1px solid #cccccc; border: 1px solid #cccccc;
width: 100%; width: 100%;
height: 60vh; height: 60vh;
margin-bottom: 0; margin-top: .5em;
margin-bottom: 1em;
.loan-form { .loan-form {
margin-top: .5em; margin-top: .5em;