mirror of
https://github.com/ascribe/onion.git
synced 2024-12-22 17:33:14 +01:00
Merge remote-tracking branch 'origin/master' into AD-538-users-and-even-devs-are-unsure-wh
Conflicts: package.json
This commit is contained in:
commit
8d0e9a42fc
@ -22,7 +22,7 @@
|
||||
"react/jsx-sort-props": 0,
|
||||
"react/jsx-uses-react": 1,
|
||||
"react/jsx-uses-vars": 1,
|
||||
"react/no-did-mount-set-state": 1,
|
||||
"react/no-did-mount-set-state": [1, "allow-in-func"],
|
||||
"react/no-did-update-set-state": 1,
|
||||
"react/no-multi-comp": 0,
|
||||
"react/no-unknown-property": 1,
|
||||
|
@ -5,6 +5,7 @@ import Q from 'q';
|
||||
|
||||
import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
||||
|
||||
|
||||
class PieceListActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
@ -21,17 +22,16 @@ class PieceListActions {
|
||||
this.actions.updatePieceList({
|
||||
page,
|
||||
pageSize,
|
||||
search,
|
||||
orderBy,
|
||||
orderAsc,
|
||||
filterBy,
|
||||
search: '',
|
||||
pieceList: [],
|
||||
pieceListCount: -1,
|
||||
unfilteredPieceListCount: -1
|
||||
});
|
||||
|
||||
// afterwards, we can load the list
|
||||
|
||||
return Q.Promise((resolve, reject) => {
|
||||
PieceListFetcher
|
||||
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
|
||||
|
@ -13,7 +13,7 @@ class UserActions {
|
||||
}
|
||||
|
||||
fetchCurrentUser() {
|
||||
return UserFetcher.fetchOne()
|
||||
UserFetcher.fetchOne()
|
||||
.then((res) => {
|
||||
this.actions.updateCurrentUser(res.users[0]);
|
||||
})
|
||||
@ -24,7 +24,7 @@ class UserActions {
|
||||
}
|
||||
|
||||
logoutCurrentUser() {
|
||||
return UserFetcher.logout()
|
||||
UserFetcher.logout()
|
||||
.then(() => {
|
||||
this.actions.deleteCurrentUser();
|
||||
})
|
||||
|
32
js/app.js
32
js/app.js
@ -3,7 +3,8 @@
|
||||
require('babel/polyfill');
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Router, Redirect } from 'react-router';
|
||||
import history from './history';
|
||||
|
||||
/* eslint-disable */
|
||||
import fetch from 'isomorphic-fetch';
|
||||
@ -46,7 +47,6 @@ requests.defaults({
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
class AppGateway {
|
||||
start() {
|
||||
let settings;
|
||||
@ -68,22 +68,36 @@ class AppGateway {
|
||||
load(settings) {
|
||||
let type = 'default';
|
||||
let subdomain = 'www';
|
||||
let redirectRoute = (<Redirect from="/" to="/collection" />);
|
||||
|
||||
if (settings) {
|
||||
type = settings.type;
|
||||
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);
|
||||
|
||||
// Send the applicationWillBoot event to the third-party stores
|
||||
EventActions.applicationWillBoot(settings);
|
||||
window.appRouter = Router.run(getRoutes(type, subdomain), Router.HistoryLocation, (App) => {
|
||||
React.render(
|
||||
<App />,
|
||||
document.getElementById('main')
|
||||
);
|
||||
EventActions.routeDidChange();
|
||||
});
|
||||
|
||||
// `history.listen` is called on every route change, which is perfect for
|
||||
// us in that case.
|
||||
history.listen(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);
|
||||
}
|
||||
}
|
||||
|
@ -9,21 +9,49 @@ let AccordionList = React.createClass({
|
||||
className: React.PropTypes.string,
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
|
||||
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() {
|
||||
const { search } = this.props;
|
||||
|
||||
if(this.props.itemList && this.props.itemList.length > 0) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
} else if(this.props.count === 0) {
|
||||
} 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('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
||||
<p className="text-center">
|
||||
{getLangText('We could not find any works related to you...')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{getLangText('To register one, click')}
|
||||
<a href="register_piece">{getLangText('here')}</a>!
|
||||
</p>
|
||||
</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>
|
||||
);
|
||||
} else {
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
|
||||
let AccordionListItem = React.createClass({
|
||||
propTypes: {
|
||||
@ -18,10 +18,7 @@ let AccordionListItem = React.createClass({
|
||||
])
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={this.props.className}>
|
||||
|
@ -1,14 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import AccordionListItem from './accordion_list_item';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
|
||||
let AccordionListItemPiece = React.createClass({
|
||||
propTypes: {
|
||||
@ -24,26 +22,15 @@ let AccordionListItemPiece = React.createClass({
|
||||
badge: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
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 {
|
||||
return {
|
||||
to: 'piece',
|
||||
params: {
|
||||
pieceId: this.props.piece.id
|
||||
return `/pieces/${piece.id}`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
@ -51,11 +38,11 @@ let AccordionListItemPiece = React.createClass({
|
||||
<AccordionListItem
|
||||
className={this.props.className}
|
||||
thumbnail={
|
||||
<Link {...this.getLinkData()}>
|
||||
<Link to={this.getLinkData()}>
|
||||
<img src={this.props.piece.thumbnail.url_safe}/>
|
||||
</Link>}
|
||||
heading={
|
||||
<Link {...this.getLinkData()}>
|
||||
<Link to={this.getLinkData()}>
|
||||
<h1>{this.props.piece.title}</h1>
|
||||
</Link>}
|
||||
subheading={
|
||||
|
@ -110,7 +110,7 @@ let AccordionListItemTableEditions = React.createClass({
|
||||
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 = [
|
||||
new ColumnModel(
|
||||
|
@ -1,22 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
import GlobalNotification from './global_notification';
|
||||
|
||||
import getRoutes from '../routes';
|
||||
|
||||
|
||||
let RouteHandler = Router.RouteHandler;
|
||||
|
||||
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() {
|
||||
let { children, routes } = this.props;
|
||||
|
||||
return (
|
||||
<div className="container ascribe-default-app">
|
||||
<Header routes={getRoutes()} />
|
||||
<RouteHandler />
|
||||
<Header routes={routes} />
|
||||
{/* Routes are injected here */}
|
||||
{children}
|
||||
<Footer />
|
||||
<GlobalNotification />
|
||||
<div id="modal" className="container"></div>
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
@ -24,8 +23,6 @@ let DeleteButton = React.createClass({
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
let availableAcls;
|
||||
let btnDelete;
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link, History } from 'react-router';
|
||||
|
||||
import Row from 'react-bootstrap/lib/Row';
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
@ -22,7 +22,7 @@ import Form from './../ascribe_forms/form';
|
||||
import Property from './../ascribe_forms/property';
|
||||
import EditionDetailProperty from './detail_property';
|
||||
import LicenseDetail from './license_detail';
|
||||
import EditionFurtherDetails from './further_details';
|
||||
import FurtherDetails from './further_details';
|
||||
|
||||
import EditionActionPanel from './edition_action_panel';
|
||||
|
||||
@ -33,17 +33,18 @@ import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
/**
|
||||
* This is the component that implements display-specific functionality
|
||||
*/
|
||||
let Edition = React.createClass({
|
||||
propTypes: {
|
||||
edition: React.PropTypes.object,
|
||||
loadEdition: React.PropTypes.func
|
||||
loadEdition: React.PropTypes.func,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
@ -148,12 +149,13 @@ let Edition = React.createClass({
|
||||
show={this.props.edition.acl.acl_edit
|
||||
|| Object.keys(this.props.edition.extra_data).length > 0
|
||||
|| this.props.edition.other_data.length > 0}>
|
||||
<EditionFurtherDetails
|
||||
<FurtherDetails
|
||||
editable={this.props.edition.acl.acl_edit}
|
||||
pieceId={this.props.edition.parent}
|
||||
extraData={this.props.edition.extra_data}
|
||||
otherData={this.props.edition.other_data}
|
||||
handleSuccess={this.props.loadEdition}/>
|
||||
handleSuccess={this.props.loadEdition}
|
||||
location={this.props.location}/>
|
||||
</CollapsibleParagraph>
|
||||
<CollapsibleParagraph
|
||||
title={getLangText('SPOOL Details')}>
|
||||
@ -257,7 +259,7 @@ let CoaDetails = React.createClass({
|
||||
{getLangText('Download')} <Glyphicon glyph="cloud-download"/>
|
||||
</button>
|
||||
</a>
|
||||
<Link to="coa_verify">
|
||||
<Link to="/coa_verify">
|
||||
<button className="btn btn-default btn-xs">
|
||||
{getLangText('Verify')} <Glyphicon glyph="check"/>
|
||||
</button>
|
||||
|
@ -9,12 +9,17 @@ import Edition from './edition';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
|
||||
|
||||
/**
|
||||
* This is the component that implements resource/data specific functionality
|
||||
*/
|
||||
let EditionContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return EditionStore.getState();
|
||||
},
|
||||
@ -62,10 +67,13 @@ let EditionContainer = React.createClass({
|
||||
|
||||
render() {
|
||||
if(this.state.edition && this.state.edition.title) {
|
||||
setDocumentTitle([this.state.edition.artist_name, this.state.edition.title].join(', '));
|
||||
|
||||
return (
|
||||
<Edition
|
||||
edition={this.state.edition}
|
||||
loadEdition={this.loadEdition}/>
|
||||
loadEdition={this.loadEdition}
|
||||
location={this.props.location}/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
|
@ -23,7 +23,8 @@ let FurtherDetails = React.createClass({
|
||||
pieceId: React.PropTypes.number,
|
||||
extraData: React.PropTypes.object,
|
||||
otherData: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
handleSuccess: React.PropTypes.func
|
||||
handleSuccess: React.PropTypes.func,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -85,7 +86,8 @@ let FurtherDetails = React.createClass({
|
||||
overrideForm={true}
|
||||
pieceId={this.props.pieceId}
|
||||
otherData={this.props.otherData}
|
||||
multiple={true}/>
|
||||
multiple={true}
|
||||
location={this.props.location}/>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -20,7 +20,8 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
submitFile: React.PropTypes.func,
|
||||
isReadyForFormSubmission: React.PropTypes.func,
|
||||
editable: React.PropTypes.bool,
|
||||
multiple: React.PropTypes.bool
|
||||
multiple: React.PropTypes.bool,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -88,7 +89,8 @@ let FurtherDetailsFileuploader = React.createClass({
|
||||
}}
|
||||
areAssetsDownloadable={true}
|
||||
areAssetsEditable={this.props.editable}
|
||||
multiple={this.props.multiple}/>
|
||||
multiple={this.props.multiple}
|
||||
location={this.props.location}/>
|
||||
</Property>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import PieceActions from '../../actions/piece_actions';
|
||||
import PieceStore from '../../stores/piece_store';
|
||||
@ -40,13 +40,17 @@ import ApiUrls from '../../constants/api_urls';
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
|
||||
/**
|
||||
* This is the component that implements resource/data specific functionality
|
||||
*/
|
||||
let PieceContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -134,7 +138,7 @@ let PieceContainer = React.createClass({
|
||||
let notification = new GlobalNotificationModel(response.notification, 'success');
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
},
|
||||
|
||||
getCreateEditionsDialog() {
|
||||
@ -213,6 +217,8 @@ let PieceContainer = React.createClass({
|
||||
|
||||
render() {
|
||||
if(this.state.piece && this.state.piece.title) {
|
||||
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
|
||||
|
||||
return (
|
||||
<Piece
|
||||
piece={this.state.piece}
|
||||
@ -267,7 +273,8 @@ let PieceContainer = React.createClass({
|
||||
pieceId={this.state.piece.id}
|
||||
extraData={this.state.piece.extra_data}
|
||||
otherData={this.state.piece.other_data}
|
||||
handleSuccess={this.loadPiece}/>
|
||||
handleSuccess={this.loadPiece}
|
||||
location={this.props.location}/>
|
||||
</CollapsibleParagraph>
|
||||
|
||||
</Piece>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import ContractListActions from '../../actions/contract_list_actions';
|
||||
import ContractListStore from '../../stores/contract_list_store';
|
||||
@ -25,7 +25,7 @@ let ContractAgreementForm = React.createClass({
|
||||
handleSuccess: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -57,7 +57,8 @@ let ContractAgreementForm = React.createClass({
|
||||
let notification = 'Contract agreement send';
|
||||
notification = new GlobalNotificationModel(notification, 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
|
||||
this.history.pushState(null, '/collection');
|
||||
},
|
||||
|
||||
getFormData(){
|
||||
@ -138,7 +139,7 @@ let ContractAgreementForm = React.createClass({
|
||||
<div>
|
||||
<p className="text-center">
|
||||
{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.')}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -28,7 +28,8 @@ let CreateContractForm = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
}),
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -86,7 +87,8 @@ let CreateContractForm = React.createClass({
|
||||
areAssetsEditable={true}
|
||||
setIsUploadReady={this.setIsUploadReady}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||
fileClassToUpload={this.props.fileClassToUpload}
|
||||
location={this.props.location}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='name'
|
||||
|
@ -130,6 +130,9 @@ let LoanForm = React.createClass({
|
||||
src={contract.blob.url_safe}
|
||||
alt="pdf"
|
||||
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 */}
|
||||
<InputCheckbox
|
||||
style={{'display': 'none'}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
@ -24,10 +24,10 @@ let LoginForm = React.createClass({
|
||||
submitMessage: React.PropTypes.string,
|
||||
redirectOnLoggedIn: React.PropTypes.bool,
|
||||
redirectOnLoginSuccess: React.PropTypes.bool,
|
||||
onLogin: React.PropTypes.func
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -44,10 +44,6 @@ let LoginForm = React.createClass({
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
let { redirect } = this.getQuery();
|
||||
if (redirect && redirect !== 'login'){
|
||||
this.transitionTo(redirect, null, this.getQuery());
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -56,65 +52,19 @@ let LoginForm = React.createClass({
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
|
||||
// if user is already logged in, redirect him to piece list
|
||||
if(this.state.currentUser && this.state.currentUser.email && this.props.redirectOnLoggedIn) {
|
||||
// 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);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
|
||||
// register_piece is waiting for a login success as login_container and it is wrapped
|
||||
// in a slides_container component.
|
||||
// The easiest way to check if the user was successfully logged in is to fetch the user
|
||||
// in the user store (which is obviously only possible if the user is logged in), since
|
||||
// register_piece is listening to the changes of the user_store.
|
||||
UserActions.fetchCurrentUser()
|
||||
.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;
|
||||
if(success) {
|
||||
UserActions.fetchCurrentUser();
|
||||
}
|
||||
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() {
|
||||
let email = this.getQuery().email || null;
|
||||
let email = this.props.location.query.email || null;
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered"
|
||||
|
@ -29,7 +29,8 @@ let RegisterPieceForm = React.createClass({
|
||||
onLoggedOut: React.PropTypes.func,
|
||||
|
||||
// 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() {
|
||||
@ -113,7 +114,8 @@ let RegisterPieceForm = React.createClass({
|
||||
isFineUploaderActive={this.props.isFineUploaderActive}
|
||||
onLoggedOut={this.props.onLoggedOut}
|
||||
disabled={!this.props.isFineUploaderEditable}
|
||||
enableLocalHashing={enableLocalHashing}/>
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
location={this.props.location}/>
|
||||
</Property>
|
||||
<Property
|
||||
name='artist_name'
|
||||
|
@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
import UserStore from '../../stores/user_store';
|
||||
import UserActions from '../../actions/user_actions';
|
||||
|
||||
import GlobalNotificationModel from '../../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../../actions/global_notification_actions';
|
||||
@ -14,20 +15,19 @@ import Form from './form';
|
||||
import Property from './property';
|
||||
import InputCheckbox from './input_checkbox';
|
||||
|
||||
import AppConstants from '../../constants/application_constants';
|
||||
import ApiUrls from '../../constants/api_urls';
|
||||
|
||||
|
||||
let SignupForm = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
headerMessage: React.PropTypes.string,
|
||||
submitMessage: React.PropTypes.string,
|
||||
handleSuccess: React.PropTypes.func,
|
||||
children: React.PropTypes.element
|
||||
children: React.PropTypes.element,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -35,16 +35,13 @@ let SignupForm = React.createClass({
|
||||
submitMessage: getLangText('Sign up')
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
UserStore.listen(this.onChange);
|
||||
let { redirect } = this.getQuery();
|
||||
if (redirect && redirect !== 'signup'){
|
||||
this.transitionTo(redirect, null, this.getQuery());
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -53,45 +50,23 @@ let SignupForm = React.createClass({
|
||||
|
||||
onChange(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) {
|
||||
let notification = new GlobalNotificationModel(getLangText('Sign up successful'), 'success', 50000);
|
||||
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') + '.');
|
||||
}
|
||||
else if (response.redirect) {
|
||||
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');
|
||||
} else {
|
||||
UserActions.fetchCurrentUser();
|
||||
}
|
||||
},
|
||||
|
||||
getFormData() {
|
||||
if (this.getQuery().token){
|
||||
return {token: this.getQuery().token};
|
||||
if (this.props.location.query.token){
|
||||
return {token: this.props.location.query.token};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@ -100,7 +75,9 @@ let SignupForm = React.createClass({
|
||||
let tooltipPassword = getLangText('Your password must be at least 10 characters') + '.\n ' +
|
||||
getLangText('This password is securing your digital property like a bank account') + '.\n ' +
|
||||
getLangText('Store it in a safe place') + '!';
|
||||
let email = this.getQuery().email || null;
|
||||
|
||||
let email = this.props.location.query.email || null;
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="ascribe-form-bordered"
|
||||
|
@ -46,7 +46,8 @@ let InputFineUploader = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: React.PropTypes.string,
|
||||
plural: React.PropTypes.string
|
||||
})
|
||||
}),
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
@ -106,7 +107,8 @@ let InputFineUploader = React.createClass({
|
||||
}}
|
||||
onInactive={this.props.onLoggedOut}
|
||||
enableLocalHashing={this.props.enableLocalHashing}
|
||||
fileClassToUpload={this.props.fileClassToUpload}/>
|
||||
fileClassToUpload={this.props.fileClassToUpload}
|
||||
location={this.props.location}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -103,14 +103,17 @@ let Video = React.createClass({
|
||||
* ReactJS is responsible for DOM manipulation but VideoJS updates the DOM
|
||||
* to install itself to display the video, therefore ReactJS complains that we are
|
||||
* changing the DOM under its feet.
|
||||
* The component supports a fall-back to HTML5 video tag.
|
||||
*
|
||||
* What we do is the following:
|
||||
* 1) set `state.ready = false`
|
||||
* 2) render the cover using the `<Image />` component (because ready is false)
|
||||
* 1) set `state.libraryLoaded = null` (state.libraryLoaded can be in three states: `null`
|
||||
* if we don't know anything about it, `true` if the external library has been loaded,
|
||||
* `false` if we failed to load the external library)
|
||||
* 2) render the cover using the `<Image />` component (because libraryLoaded is null)
|
||||
* 3) on `componentDidMount`, we load the external `css` and `js` resources using
|
||||
* the `InjectInHeadMixin`, attaching a function to `Promise.then` to change
|
||||
* `state.ready` to true
|
||||
* 4) when the promise is succesfully resolved, we change `state.ready` triggering
|
||||
* `state.libraryLoaded` to true
|
||||
* 4) when the promise is succesfully resolved, we change `state.libraryLoaded` triggering
|
||||
* a re-render
|
||||
* 5) the new render calls `prepareVideoHTML` to get the raw HTML of the video tag
|
||||
* (that will be later processed and expanded by VideoJS)
|
||||
@ -129,18 +132,19 @@ let Video = React.createClass({
|
||||
mixins: [InjectInHeadMixin],
|
||||
|
||||
getInitialState() {
|
||||
return { ready: false, videoMounted: false };
|
||||
return { libraryLoaded: null, videoMounted: false };
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
Q.all([
|
||||
this.inject('//vjs.zencdn.net/4.12/video-js.css'),
|
||||
this.inject('//vjs.zencdn.net/4.12/video.js')
|
||||
]).then(this.ready);
|
||||
this.inject('//vjs.zencdn.net/4.12/video.js')])
|
||||
.then(() => this.setState({libraryLoaded: true}))
|
||||
.fail(() => this.setState({libraryLoaded: false}));
|
||||
},
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.ready && !this.state.videoMounted) {
|
||||
if (this.state.libraryLoaded && !this.state.videoMounted) {
|
||||
window.videojs('#mainvideo');
|
||||
/* eslint-disable */
|
||||
this.setState({videoMounted: true});
|
||||
@ -149,11 +153,9 @@ let Video = React.createClass({
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.state.videoMounted) {
|
||||
window.videojs('#mainvideo').dispose();
|
||||
},
|
||||
|
||||
ready() {
|
||||
this.setState({ready: true, videoMounted: false});
|
||||
}
|
||||
},
|
||||
|
||||
prepareVideoHTML() {
|
||||
@ -171,7 +173,7 @@ let Video = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.state.ready) {
|
||||
if (this.state.libraryLoaded !== null) {
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: this.prepareVideoHTML() }}/>
|
||||
);
|
||||
|
@ -1,11 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let PaginationButton = React.createClass({
|
||||
propTypes: {
|
||||
@ -43,7 +42,7 @@ let PaginationButton = React.createClass({
|
||||
|
||||
if (this.isInRange(page)) {
|
||||
anchor = (
|
||||
<Link to="pieces"
|
||||
<Link to="/collection"
|
||||
query={{page}}
|
||||
onClick={this.props.goToPage(page)}>
|
||||
{directionDisplay}
|
||||
|
@ -4,16 +4,16 @@ import React from 'react';
|
||||
|
||||
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_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({
|
||||
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
searchFor: React.PropTypes.func,
|
||||
searchQuery: React.PropTypes.string,
|
||||
filterParams: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
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(){
|
||||
if (this.props.filterParams){
|
||||
return (
|
||||
@ -55,6 +50,7 @@ let PieceListToolbar = React.createClass({
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getOrderWidget(){
|
||||
if (this.props.orderParams){
|
||||
return (
|
||||
@ -68,24 +64,21 @@ let PieceListToolbar = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let searchIcon = <Glyphicon glyph='search' className="filter-glyph"/>;
|
||||
const { className, children, searchFor, searchQuery } = this.props;
|
||||
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className={className}>
|
||||
<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="row">
|
||||
<span className="pull-left">
|
||||
{this.props.children}
|
||||
</span>
|
||||
<span className="pull-right search-bar ascribe-input-glyph">
|
||||
<Input
|
||||
type='text'
|
||||
ref="search"
|
||||
placeholder={getLangText('Search%s', '...')}
|
||||
onChange={this.searchFor}
|
||||
addonAfter={searchIcon} />
|
||||
{children}
|
||||
</span>
|
||||
<SearchBar
|
||||
className="pull-right search-bar ascribe-input-glyph"
|
||||
searchFor={searchFor}
|
||||
searchQuery={searchQuery}
|
||||
threshold={AppConstants.searchThreshold}/>
|
||||
<span className="pull-right">
|
||||
{this.getOrderWidget()}
|
||||
{this.getFilterWidget()}
|
||||
|
107
js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js
Normal file
107
js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js
Normal 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}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
@ -96,7 +96,11 @@ let AccountSettings = React.createClass({
|
||||
title={getLangText('Account')}
|
||||
defaultExpanded={true}>
|
||||
{content}
|
||||
<AclProxy
|
||||
aclObject={this.props.whitelabel}
|
||||
aclName="acl_view_settings_copyright_association">
|
||||
<CopyrightAssociationForm currentUser={this.props.currentUser}/>
|
||||
</AclProxy>
|
||||
{profile}
|
||||
</CollapsibleParagraph>
|
||||
);
|
||||
|
@ -23,10 +23,15 @@ import GlobalNotificationActions from '../../actions/global_notification_actions
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
import { mergeOptions, truncateTextAtCharIndex } from '../../utils/general_utils';
|
||||
|
||||
|
||||
let ContractSettings = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState(){
|
||||
return mergeOptions(
|
||||
ContractListStore.getState(),
|
||||
@ -82,6 +87,8 @@ let ContractSettings = React.createClass({
|
||||
let privateContracts = this.getPrivateContracts();
|
||||
let createPublicContractForm = null;
|
||||
|
||||
setDocumentTitle(getLangText('Contracts settings'));
|
||||
|
||||
if(publicContracts.length === 0) {
|
||||
createPublicContractForm = (
|
||||
<CreateContractForm
|
||||
@ -89,7 +96,8 @@ let ContractSettings = React.createClass({
|
||||
fileClassToUpload={{
|
||||
singular: 'new contract',
|
||||
plural: 'new contracts'
|
||||
}}/>
|
||||
}}
|
||||
location={this.props.location}/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -114,7 +122,9 @@ let ContractSettings = React.createClass({
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_update_public_contract">
|
||||
<ContractSettingsUpdateButton contract={contract}/>
|
||||
<ContractSettingsUpdateButton
|
||||
contract={contract}
|
||||
location={this.props.location}/>
|
||||
</AclProxy>
|
||||
<a
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
@ -144,7 +154,8 @@ let ContractSettings = React.createClass({
|
||||
fileClassToUpload={{
|
||||
singular: getLangText('new contract'),
|
||||
plural: getLangText('new contracts')
|
||||
}}/>
|
||||
}}
|
||||
location={this.props.location}/>
|
||||
{privateContracts.map((contract, i) => {
|
||||
return (
|
||||
<ActionPanel
|
||||
@ -156,7 +167,9 @@ let ContractSettings = React.createClass({
|
||||
<AclProxy
|
||||
aclObject={this.state.whitelabel}
|
||||
aclName="acl_update_private_contract">
|
||||
<ContractSettingsUpdateButton contract={contract}/>
|
||||
<ContractSettingsUpdateButton
|
||||
contract={contract}
|
||||
location={this.props.location}/>
|
||||
</AclProxy>
|
||||
<a
|
||||
className="btn btn-default btn-sm margin-left-2px"
|
||||
|
@ -20,7 +20,8 @@ import { getLangText } from '../../utils/lang_utils';
|
||||
|
||||
let ContractSettingsUpdateButton = React.createClass({
|
||||
propTypes: {
|
||||
contract: React.PropTypes.object
|
||||
contract: React.PropTypes.object,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
submitFile(file) {
|
||||
@ -90,7 +91,7 @@ let ContractSettingsUpdateButton = React.createClass({
|
||||
}}
|
||||
isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile}
|
||||
submitFile={this.submitFile}
|
||||
/>
|
||||
location={this.props.location}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import UserStore from '../../stores/user_store';
|
||||
import UserActions from '../../actions/user_actions';
|
||||
@ -16,6 +15,8 @@ import APISettings from './api_settings';
|
||||
import AclProxy from '../acl_proxy';
|
||||
|
||||
import { mergeOptions } from '../../utils/general_utils';
|
||||
import { getLangText } from '../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../utils/dom_utils';
|
||||
|
||||
|
||||
let SettingsContainer = React.createClass({
|
||||
@ -25,8 +26,6 @@ let SettingsContainer = React.createClass({
|
||||
React.PropTypes.element])
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
UserStore.getState(),
|
||||
@ -56,6 +55,8 @@ let SettingsContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Account settings'));
|
||||
|
||||
if (this.state.currentUser && this.state.currentUser.username) {
|
||||
return (
|
||||
<div className="settings-container">
|
||||
|
@ -1,96 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import ReactAddons from 'react/addons';
|
||||
import React from 'react/addons';
|
||||
import { History } from 'react-router';
|
||||
|
||||
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: {
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
forwardProcess: React.PropTypes.bool.isRequired,
|
||||
children: arrayOf(element),
|
||||
forwardProcess: bool.isRequired,
|
||||
|
||||
glyphiconClassNames: React.PropTypes.shape({
|
||||
pending: React.PropTypes.string,
|
||||
complete: React.PropTypes.string
|
||||
})
|
||||
glyphiconClassNames: shape({
|
||||
pending: string,
|
||||
complete: string
|
||||
}),
|
||||
location: object
|
||||
},
|
||||
|
||||
mixins: [State, Navigation],
|
||||
mixins: [History],
|
||||
|
||||
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 {
|
||||
slideNum,
|
||||
startFrom,
|
||||
containerWidth: 0,
|
||||
historyLength: window.history.length
|
||||
containerWidth: 0
|
||||
};
|
||||
},
|
||||
|
||||
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,
|
||||
// as it was not possible to listen to the resize events of a dom node
|
||||
window.addEventListener('resize', this.handleContainerResize);
|
||||
},
|
||||
|
||||
componentWillReceiveProps() {
|
||||
let queryParams = this.getQuery();
|
||||
|
||||
// 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);
|
||||
// Initially, we need to dispatch 'resize' once to render correctly
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -105,80 +49,26 @@ let SlidesContainer = React.createClass({
|
||||
},
|
||||
|
||||
// When the start_from parameter is used, this.setSlideNum can not simply be used anymore.
|
||||
nextSlide() {
|
||||
let nextSlide = this.state.slideNum + 1;
|
||||
this.setSlideNum(nextSlide);
|
||||
nextSlide(additionalQueryParams) {
|
||||
const slideNum = parseInt(this.props.location.query.slide_num, 10) || 0;
|
||||
let nextSlide = slideNum + 1;
|
||||
this.setSlideNum(nextSlide, additionalQueryParams);
|
||||
},
|
||||
|
||||
// We let every one from the outsite set the page number of the slider,
|
||||
// though only if the slideNum is actually in the range of our children-list.
|
||||
setSlideNum(slideNum) {
|
||||
|
||||
// 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.');
|
||||
}
|
||||
setSlideNum(nextSlideNum, additionalQueryParams = {}) {
|
||||
let queryParams = Object.assign(this.props.location.query, additionalQueryParams);
|
||||
queryParams.slide_num = nextSlideNum;
|
||||
this.history.pushState(null, this.props.location.pathname, queryParams);
|
||||
},
|
||||
|
||||
// breadcrumbs are defined as attributes of the slides.
|
||||
// To extract them we have to read the DOM element's attributes
|
||||
extractBreadcrumbs() {
|
||||
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
|
||||
let breadcrumbs = [];
|
||||
|
||||
ReactAddons.Children.map(this.props.children, (child, i) => {
|
||||
if(child && i >= this.state.startFrom && child.props['data-slide-title']) {
|
||||
React.Children.map(this.props.children, (child, i) => {
|
||||
if(child && i >= startFrom && 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
|
||||
// need to implement our own method.
|
||||
customChildrenCount() {
|
||||
const startFrom = parseInt(this.props.location.query.start_from, 10) || -1;
|
||||
let count = 0;
|
||||
|
||||
React.Children.forEach(this.props.children, (child, i) => {
|
||||
if(i >= this.state.startFrom) {
|
||||
if(i >= startFrom) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
@ -212,7 +104,7 @@ let SlidesContainer = React.createClass({
|
||||
return (
|
||||
<SlidesContainerBreadcrumbs
|
||||
breadcrumbs={breadcrumbs}
|
||||
slideNum={this.state.slideNum}
|
||||
slideNum={parseInt(this.props.location.query.slide_num, 10) || 0}
|
||||
numOfSlides={breadcrumbs.length}
|
||||
containerWidth={this.state.containerWidth}
|
||||
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
|
||||
// Also, a key is nice to have!
|
||||
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
|
||||
// if its actually present in the url bar, as it will just not match
|
||||
if(child && i >= this.state.startFrom) {
|
||||
return ReactAddons.addons.cloneWithProps(child, {
|
||||
if(child && i >= startFrom) {
|
||||
return React.addons.cloneWithProps(child, {
|
||||
className: 'ascribe-slide',
|
||||
style: {
|
||||
width: this.state.containerWidth
|
||||
@ -246,7 +139,7 @@ let SlidesContainer = React.createClass({
|
||||
},
|
||||
|
||||
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)';
|
||||
|
||||
/*
|
||||
|
@ -35,14 +35,7 @@ export class TransitionModel {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
toReactRouterLinkProps(queryValue) {
|
||||
let props = {
|
||||
to: this.to,
|
||||
params: {}
|
||||
};
|
||||
|
||||
props.params[this.queryKey] = queryValue;
|
||||
|
||||
return props;
|
||||
toReactRouterLink(queryValue) {
|
||||
return '/' + this.to + '/' + queryValue;
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { ColumnModel } from './models/table_models';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let TableItemWrapper = React.createClass({
|
||||
propTypes: {
|
||||
@ -15,8 +14,6 @@ let TableItemWrapper = React.createClass({
|
||||
onClick: React.PropTypes.func
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<tr onClick={this.props.onClick}>
|
||||
@ -35,18 +32,13 @@ let TableItemWrapper = React.createClass({
|
||||
);
|
||||
} else {
|
||||
|
||||
let linkProps = column.transition.toReactRouterLinkProps(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
|
||||
*/
|
||||
let linkString = column.transition.toReactRouterLink(this.props.columnContent[column.transition.valueKey]);
|
||||
return (
|
||||
<td key={i} className={column.className}>
|
||||
<Link
|
||||
to={linkString}
|
||||
className={'ascribe-table-item-column'}
|
||||
onClick={column.transition.callback}
|
||||
{...linkProps}>
|
||||
onClick={column.transition.callback}>
|
||||
<TypeElement {...typeElementProps} />
|
||||
</Link>
|
||||
</td>
|
||||
|
@ -12,6 +12,7 @@ import { getLangText } from '../../../utils/lang_utils';
|
||||
// Taken from: https://github.com/fedosejev/react-file-drag-and-drop
|
||||
let FileDragAndDrop = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
onDrop: React.PropTypes.func.isRequired,
|
||||
onDragOver: React.PropTypes.func,
|
||||
onInactive: React.PropTypes.func,
|
||||
@ -40,7 +41,8 @@ let FileDragAndDrop = React.createClass({
|
||||
plural: React.PropTypes.string
|
||||
}),
|
||||
|
||||
allowedExtensions: React.PropTypes.string
|
||||
allowedExtensions: React.PropTypes.string,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
handleDragOver(event) {
|
||||
@ -107,6 +109,7 @@ let FileDragAndDrop = React.createClass({
|
||||
},
|
||||
|
||||
handleOnClick() {
|
||||
let evt;
|
||||
// when multiple is set to false and the user already uploaded a piece,
|
||||
// do not propagate event
|
||||
if(this.props.dropzoneInactive) {
|
||||
@ -118,16 +121,18 @@ let FileDragAndDrop = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
// Firefox only recognizes the simulated mouse click if bubbles is set to true,
|
||||
// but since Google Chrome propagates the event much further than needed, we
|
||||
// need to stop propagation as soon as the event is created
|
||||
var evt = new MouseEvent('click', {
|
||||
try {
|
||||
evt = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
} catch(e) {
|
||||
// For browsers that do not support the new MouseEvent syntax
|
||||
evt = document.createEvent('MouseEvents');
|
||||
evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null);
|
||||
}
|
||||
|
||||
evt.stopPropagation();
|
||||
this.refs.fileinput.getDOMNode().dispatchEvent(evt);
|
||||
},
|
||||
|
||||
@ -142,7 +147,8 @@ let FileDragAndDrop = React.createClass({
|
||||
fileClassToUpload,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
allowedExtensions
|
||||
allowedExtensions,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
// has files only is true if there are files that do not have the status deleted or canceled
|
||||
@ -158,10 +164,10 @@ let FileDragAndDrop = React.createClass({
|
||||
<div className="file-drag-and-drop-hashing-dialog">
|
||||
<p>{getLangText('Computing hash(es)... This may take a few minutes.')}</p>
|
||||
<p>
|
||||
<a onClick={this.props.handleCancelHashing}> {getLangText('Cancel hashing')}</a>
|
||||
<a onClick={handleCancelHashing}> {getLangText('Cancel hashing')}</a>
|
||||
</p>
|
||||
<ProgressBar
|
||||
now={Math.ceil(this.props.hashingProgress)}
|
||||
now={Math.ceil(hashingProgress)}
|
||||
label="%(percent)s%"
|
||||
className="ascribe-progress-bar"/>
|
||||
</div>
|
||||
@ -179,7 +185,8 @@ let FileDragAndDrop = React.createClass({
|
||||
hasFiles={hasFiles}
|
||||
onClick={this.handleOnClick}
|
||||
enableLocalHashing={enableLocalHashing}
|
||||
fileClassToUpload={fileClassToUpload}/>
|
||||
fileClassToUpload={fileClassToUpload}
|
||||
location={location}/>
|
||||
<FileDragAndDropPreviewIterator
|
||||
files={filesToUpload}
|
||||
handleDeleteFile={this.handleDeleteFile}
|
||||
@ -188,12 +195,23 @@ let FileDragAndDrop = React.createClass({
|
||||
handleResumeFile={this.handleResumeFile}
|
||||
areAssetsDownloadable={areAssetsDownloadable}
|
||||
areAssetsEditable={areAssetsEditable}/>
|
||||
{/*
|
||||
Opera doesn't trigger simulated click events
|
||||
if the targeted input has `display:none` set.
|
||||
Which means we need to set its visibility to hidden
|
||||
instead of using `display:none`.
|
||||
|
||||
See:
|
||||
- http://stackoverflow.com/questions/12880604/jquery-triggerclick-not-working-on-opera-if-the-element-is-not-displayed
|
||||
*/}
|
||||
<input
|
||||
multiple={multiple}
|
||||
ref="fileinput"
|
||||
type="file"
|
||||
style={{
|
||||
display: 'none',
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
height: 0,
|
||||
width: 0
|
||||
}}
|
||||
|
@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import { getLangText } from '../../../utils/lang_utils';
|
||||
import { dragAndDropAvailable } from '../../../utils/feature_detection_utils';
|
||||
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let FileDragAndDropDialog = React.createClass({
|
||||
propTypes: {
|
||||
@ -19,13 +20,24 @@ let FileDragAndDropDialog = React.createClass({
|
||||
fileClassToUpload: React.PropTypes.shape({
|
||||
singular: 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() {
|
||||
const queryParams = this.getQuery();
|
||||
const queryParams = this.props.location.query;
|
||||
|
||||
if(this.props.hasFiles) {
|
||||
return null;
|
||||
@ -38,11 +50,13 @@ let FileDragAndDropDialog = React.createClass({
|
||||
let queryParamsUpload = Object.assign({}, queryParams);
|
||||
queryParamsUpload.method = 'upload';
|
||||
|
||||
let { location } = this.props;
|
||||
|
||||
return (
|
||||
<div className="file-drag-and-drop-dialog present-options">
|
||||
<p>{getLangText('Would you rather')}</p>
|
||||
<Link
|
||||
to={this.getPath()}
|
||||
to={location.pathname}
|
||||
query={queryParamsHash}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Hash your work')}
|
||||
@ -52,7 +66,7 @@ let FileDragAndDropDialog = React.createClass({
|
||||
<span> or </span>
|
||||
|
||||
<Link
|
||||
to={this.getPath()}
|
||||
to={location.pathname}
|
||||
query={queryParamsUpload}>
|
||||
<span className="btn btn-default btn-sm">
|
||||
{getLangText('Upload and hash your work')}
|
||||
@ -64,8 +78,7 @@ let FileDragAndDropDialog = React.createClass({
|
||||
if(this.props.multipleFiles) {
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag %s here', this.props.fileClassToUpload.plural)}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
{this.getDragDialog(this.props.fileClassToUpload.plural)}
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
@ -78,8 +91,7 @@ let FileDragAndDropDialog = React.createClass({
|
||||
|
||||
return (
|
||||
<span className="file-drag-and-drop-dialog">
|
||||
<p>{getLangText('Drag a %s here', this.props.fileClassToUpload.singular)}</p>
|
||||
<p>{getLangText('or')}</p>
|
||||
{this.getDragDialog(this.props.fileClassToUpload.singular)}
|
||||
<span
|
||||
className="btn btn-default"
|
||||
onClick={this.props.onClick}>
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import React from 'react/addons';
|
||||
import fineUploader from 'fineUploader';
|
||||
import Router from 'react-router';
|
||||
import Q from 'q';
|
||||
|
||||
import S3Fetcher from '../../fetchers/s3_fetcher';
|
||||
@ -127,10 +126,10 @@ let ReactS3FineUploader = React.createClass({
|
||||
fileInputElement: React.PropTypes.oneOfType([
|
||||
React.PropTypes.func,
|
||||
React.PropTypes.element
|
||||
])
|
||||
},
|
||||
]),
|
||||
|
||||
mixins: [Router.State],
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
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
|
||||
// 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') {
|
||||
|
||||
let convertedFilePromises = [];
|
||||
@ -830,7 +829,7 @@ let ReactS3FineUploader = React.createClass({
|
||||
|
||||
isDropzoneInactive() {
|
||||
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) {
|
||||
return true;
|
||||
@ -859,12 +858,20 @@ let ReactS3FineUploader = React.createClass({
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
validation,
|
||||
fileInputElement
|
||||
fileInputElement,
|
||||
location
|
||||
} = this.props;
|
||||
|
||||
// Here we initialize the template that has been either provided from the outside
|
||||
// or the default input that is FileDragAndDrop.
|
||||
return React.createElement(fileInputElement, {
|
||||
multiple,
|
||||
areAssetsDownloadable,
|
||||
areAssetsEditable,
|
||||
onInactive,
|
||||
enableLocalHashing,
|
||||
fileClassToUpload,
|
||||
location,
|
||||
onDrop: this.handleUploadFile,
|
||||
filesToUpload: this.state.filesToUpload,
|
||||
handleDeleteFile: this.handleDeleteFile,
|
||||
@ -872,14 +879,8 @@ let ReactS3FineUploader = React.createClass({
|
||||
handlePauseFile: this.handlePauseFile,
|
||||
handleResumeFile: this.handleResumeFile,
|
||||
handleCancelHashing: this.handleCancelHashing,
|
||||
multiple: multiple,
|
||||
areAssetsDownloadable: areAssetsDownloadable,
|
||||
areAssetsEditable: areAssetsEditable,
|
||||
onInactive: onInactive,
|
||||
dropzoneInactive: this.isDropzoneInactive(),
|
||||
hashingProgress: this.state.hashingProgress,
|
||||
enableLocalHashing: enableLocalHashing,
|
||||
fileClassToUpload: fileClassToUpload,
|
||||
allowedExtensions: this.getAllowedExtensions()
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import GlobalNotificationModel from '../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
@ -12,12 +11,13 @@ import InputTextAreaToggable from './ascribe_forms/input_textarea_toggable';
|
||||
|
||||
import ApiUrls from '../constants/api_urls';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let CoaVerifyContainer = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Verify your Certificate of Authenticity'));
|
||||
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<br/>
|
||||
@ -45,8 +45,6 @@ let CoaVerifyContainer = React.createClass({
|
||||
|
||||
|
||||
let CoaVerifyForm = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
|
||||
handleSuccess(response){
|
||||
let notification = null;
|
||||
if (response.verdict) {
|
||||
|
@ -1,15 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
|
||||
import Nav from 'react-bootstrap/lib/Nav';
|
||||
import Navbar from 'react-bootstrap/lib/Navbar';
|
||||
import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||
import NavItem from 'react-bootstrap/lib/NavItem';
|
||||
|
||||
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
|
||||
|
||||
import AclProxy from './acl_proxy';
|
||||
|
||||
@ -33,11 +33,9 @@ import { getLangText } from '../utils/lang_utils';
|
||||
let Header = React.createClass({
|
||||
propTypes: {
|
||||
showAddWork: React.PropTypes.bool,
|
||||
routes: React.PropTypes.element
|
||||
routes: React.PropTypes.arrayOf(React.PropTypes.object)
|
||||
},
|
||||
|
||||
mixins: [Router.State],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
showAddWork: true
|
||||
@ -134,38 +132,61 @@ let Header = React.createClass({
|
||||
ref='dropdownbutton'
|
||||
eventKey="1"
|
||||
title={this.state.currentUser.username}>
|
||||
<MenuItemLink
|
||||
eventKey="2"
|
||||
to="settings"
|
||||
<LinkContainer
|
||||
to="/settings"
|
||||
onClick={this.onMenuItemClick}>
|
||||
<MenuItem
|
||||
eventKey="2">
|
||||
{getLangText('Account Settings')}
|
||||
</MenuItemLink>
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
<AclProxy
|
||||
aclObject={this.state.currentUser.acl}
|
||||
aclName="acl_view_settings_contract">
|
||||
<MenuItemLink
|
||||
to="contract_settings"
|
||||
<LinkContainer
|
||||
to="/contract_settings"
|
||||
onClick={this.onMenuItemClick}>
|
||||
<MenuItem
|
||||
eventKey="2">
|
||||
{getLangText('Contract Settings')}
|
||||
</MenuItemLink>
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
</AclProxy>
|
||||
<MenuItem divider />
|
||||
<MenuItemLink eventKey="3" to="logout">{getLangText('Log out')}</MenuItemLink>
|
||||
<LinkContainer
|
||||
to="/logout">
|
||||
<MenuItem
|
||||
eventKey="3">
|
||||
{getLangText('Log out')}
|
||||
</MenuItem>
|
||||
</LinkContainer>
|
||||
</DropdownButton>
|
||||
);
|
||||
navRoutesLinks = <NavRoutesLinks routes={this.props.routes} userAcl={this.state.currentUser.acl} navbar right/>;
|
||||
}
|
||||
else {
|
||||
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
|
||||
signup = <NavItemLink to="signup">{getLangText('SIGNUP')}</NavItemLink>;
|
||||
account = (
|
||||
<LinkContainer
|
||||
to="/login">
|
||||
<NavItem>
|
||||
{getLangText('LOGIN')}
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
signup = (
|
||||
<LinkContainer
|
||||
to="/signup">
|
||||
<NavItem>
|
||||
{getLangText('SIGNUP')}
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Navbar
|
||||
brand={
|
||||
this.getLogo()
|
||||
}
|
||||
brand={this.getLogo()}
|
||||
toggleNavKey={0}
|
||||
fixedTop={true}>
|
||||
<CollapsibleNav eventKey={0}>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
@ -14,8 +14,6 @@ import NotificationStore from '../stores/notification_store';
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
|
||||
let HeaderNotifications = React.createClass({
|
||||
|
||||
@ -39,7 +37,7 @@ let HeaderNotifications = React.createClass({
|
||||
this.setState(state);
|
||||
},
|
||||
|
||||
onMenuItemClick(event) {
|
||||
onMenuItemClick() {
|
||||
/*
|
||||
This is a hack to make the dropdown close after clicking on an item
|
||||
The function just need to be defined
|
||||
@ -158,23 +156,13 @@ let NotificationListItem = React.createClass({
|
||||
},
|
||||
|
||||
getLinkData() {
|
||||
let { pieceOrEdition } = this.props;
|
||||
|
||||
if (this.isPiece()) {
|
||||
return {
|
||||
to: 'piece',
|
||||
params: {
|
||||
pieceId: this.props.pieceOrEdition.id
|
||||
}
|
||||
};
|
||||
return `/pieces/${pieceOrEdition.id}`;
|
||||
} else {
|
||||
return {
|
||||
to: 'edition',
|
||||
params: {
|
||||
editionId: this.props.pieceOrEdition.bitcoin_id
|
||||
return `/editions/${pieceOrEdition.bitcoin_id}`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onClick(event){
|
||||
@ -184,7 +172,7 @@ let NotificationListItem = React.createClass({
|
||||
getNotificationText(){
|
||||
let numNotifications = null;
|
||||
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 (
|
||||
<div className="notification-action">
|
||||
@ -196,7 +184,7 @@ let NotificationListItem = React.createClass({
|
||||
render() {
|
||||
if (this.props.pieceOrEdition) {
|
||||
return (
|
||||
<Link {...this.getLinkData()} onClick={this.onClick}>
|
||||
<Link to={this.getLinkData()} onClick={this.onClick}>
|
||||
<div className="row notification-wrapper">
|
||||
<div className="col-xs-4 clear-paddings">
|
||||
<div className="thumbnail-wrapper">
|
||||
|
@ -1,13 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import LoginForm from './ascribe_forms/form_login';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let LoginContainer = React.createClass({
|
||||
@ -15,7 +14,8 @@ let LoginContainer = React.createClass({
|
||||
message: React.PropTypes.string,
|
||||
redirectOnLoggedIn: React.PropTypes.bool,
|
||||
redirectOnLoginSuccess: React.PropTypes.bool,
|
||||
onLogin: React.PropTypes.func
|
||||
onLogin: React.PropTypes.func,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -27,16 +27,19 @@ let LoginContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Log in'));
|
||||
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<LoginForm
|
||||
redirectOnLoggedIn={this.props.redirectOnLoggedIn}
|
||||
redirectOnLoginSuccess={this.props.redirectOnLoginSuccess}
|
||||
message={this.props.message}
|
||||
onLogin={this.props.onLogin}/>
|
||||
onLogin={this.props.onLogin}
|
||||
location={this.props.location}/>
|
||||
<div className="ascribe-login-text">
|
||||
{getLangText('Not an ascribe user')}? <Link to="signup">{getLangText('Sign up')}...</Link><br/>
|
||||
{getLangText('Forgot my password')}? <Link to="password_reset">{getLangText('Rescue me')}...</Link>
|
||||
{getLangText('Not an ascribe user')}? <Link to="/signup">{getLangText('Sign up')}...</Link><br/>
|
||||
{getLangText('Forgot my password')}? <Link to="/password_reset">{getLangText('Rescue me')}...</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,38 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import UserActions from '../actions/user_actions';
|
||||
import { alt, altWhitelabel, altUser, altThirdParty } from '../alt';
|
||||
|
||||
import AppConstants from '../constants/application_constants';
|
||||
let baseUrl = AppConstants.baseUrl;
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let LogoutContainer = React.createClass({
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
componentDidMount() {
|
||||
UserActions.logoutCurrentUser()
|
||||
.then(() => {
|
||||
UserActions.logoutCurrentUser();
|
||||
alt.flush();
|
||||
altWhitelabel.flush();
|
||||
altUser.flush();
|
||||
altThirdParty.flush();
|
||||
// kill intercom (with fire)
|
||||
window.Intercom('shutdown');
|
||||
this.replaceWith(baseUrl);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.logGlobal(err);
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Log out'));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { sanitizeList } from '../utils/general_utils';
|
||||
|
||||
let NavRoutesLinks = React.createClass({
|
||||
propTypes: {
|
||||
routes: React.PropTypes.element,
|
||||
routes: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
userAcl: React.PropTypes.object
|
||||
},
|
||||
|
||||
@ -33,15 +33,15 @@ let NavRoutesLinks = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
let links = node.props.children.map((child, j) => {
|
||||
let links = node.childRoutes.map((child, j) => {
|
||||
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
|
||||
// to execute this function again with the child as the root
|
||||
//
|
||||
// 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++);
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ let NavRoutesLinks = React.createClass({
|
||||
aclObject={this.props.userAcl}>
|
||||
<NavRoutesLinksLink
|
||||
headerTitle={headerTitle}
|
||||
routeName={name}
|
||||
routePath={'/' + path}
|
||||
depth={i}
|
||||
children={childrenFn}/>
|
||||
</AclProxy>
|
||||
@ -68,7 +68,7 @@ let NavRoutesLinks = React.createClass({
|
||||
<NavRoutesLinksLink
|
||||
key={j}
|
||||
headerTitle={headerTitle}
|
||||
routeName={name}
|
||||
routePath={'/' + path}
|
||||
depth={i}
|
||||
children={childrenFn}/>
|
||||
);
|
||||
@ -88,7 +88,7 @@ let NavRoutesLinks = React.createClass({
|
||||
|
||||
return (
|
||||
<Nav {...this.props}>
|
||||
{this.extractLinksFromRoutes(routes, userAcl, 0)}
|
||||
{this.extractLinksFromRoutes(routes[0], userAcl, 0)}
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
@ -3,13 +3,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
|
||||
import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink';
|
||||
import NavItemLink from 'react-router-bootstrap/lib/NavItemLink';
|
||||
import MenuItem from 'react-bootstrap/lib/MenuItem';
|
||||
import NavItem from 'react-bootstrap/lib/NavItem';
|
||||
|
||||
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
|
||||
|
||||
|
||||
let NavRoutesLinksLink = React.createClass({
|
||||
propTypes: {
|
||||
headerTitle: React.PropTypes.string,
|
||||
routeName: React.PropTypes.string,
|
||||
routePath: React.PropTypes.string,
|
||||
|
||||
children: React.PropTypes.oneOfType([
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
@ -20,10 +23,10 @@ let NavRoutesLinksLink = React.createClass({
|
||||
},
|
||||
|
||||
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
|
||||
// with MenuItemLinks
|
||||
// with MenuItems
|
||||
if(children) {
|
||||
return (
|
||||
<DropdownButton title={headerTitle}>
|
||||
@ -33,13 +36,17 @@ let NavRoutesLinksLink = React.createClass({
|
||||
} else {
|
||||
if(depth === 1) {
|
||||
// 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 (
|
||||
<MenuItemLink to={routeName}>{headerTitle}</MenuItemLink>
|
||||
<LinkContainer to={routePath}>
|
||||
<MenuItem>{headerTitle}</MenuItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
} else if(depth === 0) {
|
||||
return (
|
||||
<NavItemLink to={routeName}>{headerTitle}</NavItemLink>
|
||||
<LinkContainer to={routePath}>
|
||||
<NavItem>{headerTitle}</NavItem>
|
||||
</LinkContainer>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Form from './ascribe_forms/form';
|
||||
import Property from './ascribe_forms/property';
|
||||
@ -10,10 +10,13 @@ import ApiUrls from '../constants/api_urls';
|
||||
import GlobalNotificationModel from '../models/global_notification_model';
|
||||
import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let PasswordResetContainer = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {isRequested: false};
|
||||
@ -24,12 +27,14 @@ let PasswordResetContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
if (this.props.query.email && this.props.query.token) {
|
||||
let { location } = this.props;
|
||||
|
||||
if (location.query.email && location.query.token) {
|
||||
return (
|
||||
<div>
|
||||
<PasswordResetForm
|
||||
email={this.props.query.email}
|
||||
token={this.props.query.token}/>
|
||||
email={location.query.email}
|
||||
token={location.query.token}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -71,6 +76,8 @@ let PasswordRequestResetForm = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Reset your password'));
|
||||
|
||||
return (
|
||||
<Form
|
||||
ref="form"
|
||||
@ -112,7 +119,7 @@ let PasswordResetForm = React.createClass({
|
||||
token: React.PropTypes.string
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getFormData() {
|
||||
return {
|
||||
@ -122,7 +129,7 @@ let PasswordResetForm = React.createClass({
|
||||
},
|
||||
|
||||
handleSuccess() {
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
let notification = new GlobalNotificationModel(getLangText('password successfully updated'), 'success', 10000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import PieceListStore from '../stores/piece_list_store';
|
||||
import PieceListActions from '../actions/piece_list_actions';
|
||||
@ -24,6 +24,7 @@ import AppConstants from '../constants/application_constants';
|
||||
|
||||
import { mergeOptions } from '../utils/general_utils';
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let PieceList = React.createClass({
|
||||
@ -33,10 +34,11 @@ let PieceList = React.createClass({
|
||||
customSubmitButton: React.PropTypes.element,
|
||||
filterParams: 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() {
|
||||
return {
|
||||
@ -60,7 +62,7 @@ let PieceList = React.createClass({
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
let page = this.getQuery().page || 1;
|
||||
let page = this.props.location.query.page || 1;
|
||||
|
||||
PieceListStore.listen(this.onChange);
|
||||
EditionListStore.listen(this.onChange);
|
||||
@ -75,7 +77,7 @@ let PieceList = React.createClass({
|
||||
componentDidUpdate() {
|
||||
if (this.props.redirectTo && this.state.unfilteredPieceListCount === 0) {
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
|
||||
@ -100,7 +102,7 @@ let PieceList = React.createClass({
|
||||
},
|
||||
|
||||
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);
|
||||
|
||||
if (this.state.pieceListCount > 10) {
|
||||
@ -116,7 +118,7 @@ let PieceList = React.createClass({
|
||||
searchFor(searchTerm) {
|
||||
PieceListActions.fetchPieceList(1, this.state.pageSize, searchTerm, this.state.orderBy,
|
||||
this.state.orderAsc, this.state.filterBy);
|
||||
this.transitionTo(this.getPathname(), {page: 1});
|
||||
this.history.pushState(null, this.props.location.pathname, {page: 1});
|
||||
},
|
||||
|
||||
applyFilterBy(filterBy){
|
||||
@ -140,7 +142,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
|
||||
// for filtered pieces
|
||||
this.transitionTo(this.getPathname(), {page: 1});
|
||||
this.history.pushState(null, this.props.location.pathname, {page: 1});
|
||||
},
|
||||
|
||||
applyOrderBy(orderBy) {
|
||||
@ -152,11 +154,14 @@ let PieceList = React.createClass({
|
||||
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
|
||||
let AccordionListItemType = this.props.accordionListItemType;
|
||||
|
||||
setDocumentTitle(getLangText('Collection'));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PieceListToolbar
|
||||
className="ascribe-piece-list-toolbar"
|
||||
searchFor={this.searchFor}
|
||||
searchQuery={this.state.search}
|
||||
filterParams={this.props.filterParams}
|
||||
orderParams={this.props.orderParams}
|
||||
filterBy={this.state.filterBy}
|
||||
@ -177,6 +182,7 @@ let PieceList = React.createClass({
|
||||
orderBy={this.state.orderBy}
|
||||
orderAsc={this.state.orderAsc}
|
||||
search={this.state.search}
|
||||
searchFor={this.searchFor}
|
||||
page={this.state.page}
|
||||
pageSize={this.state.pageSize}
|
||||
loadingElement={loadingElement}>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
import Row from 'react-bootstrap/lib/Row';
|
||||
@ -20,12 +20,9 @@ import GlobalNotificationActions from '../actions/global_notification_actions';
|
||||
import PropertyCollapsible from './ascribe_forms/property_collapsible';
|
||||
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 { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
|
||||
let RegisterPiece = React.createClass( {
|
||||
@ -37,10 +34,11 @@ let RegisterPiece = React.createClass( {
|
||||
React.PropTypes.arrayOf(React.PropTypes.element),
|
||||
React.PropTypes.element,
|
||||
React.PropTypes.string
|
||||
])
|
||||
]),
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
@ -60,10 +58,10 @@ let RegisterPiece = React.createClass( {
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
PieceListStore.listen(this.onChange);
|
||||
UserStore.listen(this.onChange);
|
||||
WhitelabelStore.listen(this.onChange);
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
},
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -98,7 +96,7 @@ let RegisterPiece = React.createClass( {
|
||||
this.state.filterBy
|
||||
);
|
||||
|
||||
this.transitionTo('piece', {pieceId: response.piece.id});
|
||||
this.history.pushState(null, `/pieces/${response.piece.id}`);
|
||||
},
|
||||
|
||||
getSpecifyEditions() {
|
||||
@ -117,53 +115,22 @@ 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() {
|
||||
setDocumentTitle(getLangText('Register a new piece'));
|
||||
|
||||
return (
|
||||
<SlidesContainer
|
||||
ref="slidesContainer"
|
||||
forwardProcess={false}>
|
||||
<div
|
||||
onClick={this.onLoggedOut}
|
||||
onFocus={this.onLoggedOut}>
|
||||
<Row className="no-margin">
|
||||
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
||||
<RegisterPieceForm
|
||||
{...this.props}
|
||||
isFineUploaderActive={this.state.isFineUploaderActive}
|
||||
handleSuccess={this.handleSuccess}
|
||||
onLoggedOut={this.onLoggedOut}>
|
||||
location={this.props.location}>
|
||||
{this.props.children}
|
||||
{this.getSpecifyEditions()}
|
||||
</RegisterPieceForm>
|
||||
</Col>
|
||||
</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
140
js/components/search_bar.js
Normal 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;
|
@ -1,15 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import SignupForm from './ascribe_forms/form_signup';
|
||||
|
||||
import { getLangText } from '../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../utils/dom_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
let SignupContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
submitted: false,
|
||||
@ -25,6 +29,8 @@ let SignupContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Sign up'));
|
||||
|
||||
if (this.state.submitted){
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
@ -37,9 +43,11 @@ let SignupContainer = React.createClass({
|
||||
}
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<SignupForm handleSuccess={this.handleSuccess} />
|
||||
<SignupForm
|
||||
handleSuccess={this.handleSuccess}
|
||||
location={this.props.location}/>
|
||||
<div className="ascribe-login-text">
|
||||
{getLangText('Already an ascribe user')}? <Link to="login">{getLangText('Log in')}...</Link><br/>
|
||||
{getLangText('Already an ascribe user')}? <Link to="/login">{getLangText('Log in')}...</Link><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import StarRating from 'react-star-rating';
|
||||
|
||||
import PieceListActions from '../../../../../actions/piece_list_actions';
|
||||
@ -21,12 +21,9 @@ import GlobalNotificationActions from '../../../../../actions/global_notificatio
|
||||
import AclProxy from '../../../../acl_proxy';
|
||||
import SubmitToPrizeButton from './../ascribe_buttons/submit_to_prize_button';
|
||||
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
|
||||
let AccordionListItemPrize = React.createClass({
|
||||
propTypes: {
|
||||
@ -85,7 +82,7 @@ let AccordionListItemPrize = React.createClass({
|
||||
|
||||
return (
|
||||
<div id="list-rating" className="pull-right">
|
||||
<Link to='piece' params={{pieceId: this.props.content.id}}>
|
||||
<Link to={`/pieces/${this.props.content.id}`}>
|
||||
<StarRating
|
||||
ref='rating'
|
||||
name="prize-rating"
|
||||
@ -108,7 +105,7 @@ let AccordionListItemPrize = React.createClass({
|
||||
// jury and no rating yet
|
||||
return (
|
||||
<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')}
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import Moment from 'moment';
|
||||
|
||||
import StarRating from 'react-star-rating';
|
||||
@ -40,8 +40,8 @@ import DetailProperty from '../../../../ascribe_detail/detail_property';
|
||||
import ApiUrls from '../../../../../constants/api_urls';
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
|
||||
/**
|
||||
* This is the component that implements resource/data specific functionality
|
||||
@ -113,12 +113,22 @@ let PieceContainer = React.createClass({
|
||||
// Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted
|
||||
let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) ||
|
||||
(this.state.currentUser.is_judge && !this.state.piece.selected )) ?
|
||||
<span className="glyphicon glyphicon-eye-close" aria-hidden="true"/> : this.state.piece.artist_name;
|
||||
null : this.state.piece.artist_name;
|
||||
|
||||
// Only show the artist email if you are a judge and the piece is shortlisted
|
||||
let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ?
|
||||
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null;
|
||||
|
||||
if (artistName === null) {
|
||||
setDocumentTitle(this.state.piece.title);
|
||||
} else {
|
||||
setDocumentTitle([artistName, this.state.piece.title].join(', '));
|
||||
}
|
||||
|
||||
if (artistName === null) {
|
||||
artistName = <span className="glyphicon glyphicon-eye-close" aria-hidden="true"/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Piece
|
||||
piece={this.state.piece}
|
||||
@ -169,12 +179,12 @@ let NavigationHeader = React.createClass({
|
||||
return (
|
||||
<div style={{marginBottom: '1em'}}>
|
||||
<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">
|
||||
{getLangText('Previous')}
|
||||
</span>
|
||||
</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">
|
||||
{getLangText('Next')}
|
||||
<span className="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
|
||||
|
@ -1,14 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import PrizeActions from '../actions/prize_actions';
|
||||
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 LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
|
||||
|
||||
import UserStore from '../../../../stores/user_store';
|
||||
import UserActions from '../../../../actions/user_actions';
|
||||
|
||||
@ -17,7 +19,7 @@ import { getLangText } from '../../../../utils/lang_utils';
|
||||
|
||||
let Landing = React.createClass({
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -44,7 +46,7 @@ let Landing = React.createClass({
|
||||
// if user is already logged in, redirect him to piece list
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
// 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){
|
||||
return (
|
||||
<ButtonGroup className="enter" bsSize="large" vertical>
|
||||
<ButtonLink to="signup">
|
||||
<LinkContainer to="/signup">
|
||||
<Button>
|
||||
{getLangText('Sign up to submit')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
|
||||
<p>
|
||||
{getLangText('or, already an ascribe user?')}
|
||||
</p>
|
||||
<ButtonLink to="login">
|
||||
<LinkContainer to="/login">
|
||||
<Button>
|
||||
{getLangText('Log in to submit')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
@ -74,9 +80,11 @@ let Landing = React.createClass({
|
||||
<p>
|
||||
{getLangText('or, already an ascribe user?')}
|
||||
</p>
|
||||
<ButtonLink to="login">
|
||||
<LinkContainer to="/login">
|
||||
<Button>
|
||||
{getLangText('Log in')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</ButtonGroup>
|
||||
);
|
||||
},
|
||||
|
@ -1,29 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
import LoginForm from '../../../ascribe_forms/form_login';
|
||||
|
||||
import { getLangText } from '../../../../utils/lang_utils';
|
||||
|
||||
let Link = Router.Link;
|
||||
import { setDocumentTitle } from '../../../../utils/dom_utils';
|
||||
|
||||
|
||||
let LoginContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Log in'));
|
||||
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
<LoginForm
|
||||
headerMessage={getLangText('Log in with ascribe')} />
|
||||
headerMessage={getLangText('Log in with ascribe')}
|
||||
location={this.props.location}/>
|
||||
<div
|
||||
className="ascribe-login-text">
|
||||
{getLangText('I\'m not a user') + ' '}
|
||||
<Link to="signup">{getLangText('Sign up...')}</Link>
|
||||
<Link to="/signup">{getLangText('Sign up...')}</Link>
|
||||
<br/>
|
||||
|
||||
{getLangText('I forgot my password') + ' '}
|
||||
<Link to="password_reset">{getLangText('Rescue me...')}</Link>
|
||||
<Link to="/password_reset">{getLangText('Rescue me...')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -9,13 +9,21 @@ import UserStore from '../../../../stores/user_store';
|
||||
import PrizeActions from '../actions/prize_actions';
|
||||
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 { mergeOptions } from '../../../../utils/general_utils';
|
||||
import { getLangText } from '../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../utils/dom_utils';
|
||||
|
||||
let PrizePieceList = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
PrizeStore.getState(),
|
||||
@ -42,9 +50,11 @@ let PrizePieceList = React.createClass({
|
||||
getButtonSubmit() {
|
||||
if (this.state.prize && this.state.prize.active && !this.state.currentUser.is_jury){
|
||||
return (
|
||||
<ButtonLink to="register_piece">
|
||||
<LinkContainer to="/register_piece">
|
||||
<Button>
|
||||
{getLangText('Submit to prize')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
);
|
||||
}
|
||||
else if (this.state.prize && this.state.currentUser.is_judge){
|
||||
@ -54,6 +64,8 @@ let PrizePieceList = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Collection'));
|
||||
|
||||
let orderParams = ['artist_name', 'title'];
|
||||
if (this.state.currentUser.is_jury) {
|
||||
orderParams = ['rating', 'title'];
|
||||
@ -65,12 +77,13 @@ let PrizePieceList = React.createClass({
|
||||
<div>
|
||||
<PieceList
|
||||
ref="list"
|
||||
redirectTo="register_piece"
|
||||
redirectTo="/register_piece"
|
||||
accordionListItemType={AccordionListItemPrize}
|
||||
orderParams={orderParams}
|
||||
orderBy={this.state.currentUser.is_jury ? 'rating' : null}
|
||||
filterParams={[]}
|
||||
customSubmitButton={this.getButtonSubmit()}/>
|
||||
customSubmitButton={this.getButtonSubmit()}
|
||||
location={this.props.location}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import InputTextAreaToggable from '../../../ascribe_forms/input_textarea_toggabl
|
||||
import InputCheckbox from '../../../ascribe_forms/input_checkbox';
|
||||
|
||||
import { getLangText } from '../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../utils/dom_utils';
|
||||
|
||||
|
||||
let PrizeRegisterPiece = React.createClass({
|
||||
@ -32,6 +33,8 @@ let PrizeRegisterPiece = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Submit to the prize'));
|
||||
|
||||
if(this.state.prize && this.state.prize.active){
|
||||
return (
|
||||
<RegisterPiece
|
||||
|
@ -24,6 +24,7 @@ import AppConstants from '../../../../constants/application_constants';
|
||||
import ApiUrls from '../../../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../utils/dom_utils';
|
||||
|
||||
|
||||
let Settings = React.createClass({
|
||||
@ -45,6 +46,8 @@ let Settings = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Account settings'));
|
||||
|
||||
let prizeSettings = null;
|
||||
if (this.state.currentUser.is_admin){
|
||||
prizeSettings = <PrizeSettings />;
|
||||
|
@ -4,8 +4,13 @@ import React from 'react';
|
||||
import SignupForm from '../../../ascribe_forms/form_signup';
|
||||
|
||||
import { getLangText } from '../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../utils/dom_utils';
|
||||
|
||||
let SignupContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
submitted: false,
|
||||
@ -21,6 +26,8 @@ let SignupContainer = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Sign up'));
|
||||
|
||||
if (this.state.submitted){
|
||||
return (
|
||||
<div className="ascribe-login-wrapper">
|
||||
@ -35,7 +42,8 @@ let SignupContainer = React.createClass({
|
||||
<SignupForm
|
||||
headerMessage={getLangText('Create account for submission')}
|
||||
submitMessage={getLangText('Sign up')}
|
||||
handleSuccess={this.handleSuccess} />
|
||||
handleSuccess={this.handleSuccess}
|
||||
location={this.props.location}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,38 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import Hero from './components/prize_hero';
|
||||
import Header from '../../header';
|
||||
import Footer from '../../footer';
|
||||
import GlobalNotification from '../../global_notification';
|
||||
|
||||
import getRoutes from './prize_routes';
|
||||
|
||||
import { getSubdomain } from '../../../utils/general_utils';
|
||||
|
||||
|
||||
let RouteHandler = Router.RouteHandler;
|
||||
|
||||
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() {
|
||||
const { history, routes } = this.props;
|
||||
let header = null;
|
||||
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 />;
|
||||
} else {
|
||||
header = <Header showAddWork={false} routes={ROUTES}/>;
|
||||
header = <Header showAddWork={false} routes={routes}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'container ascribe-prize-app client--' + subdomain}>
|
||||
{header}
|
||||
<RouteHandler />
|
||||
{this.props.children}
|
||||
<GlobalNotification />
|
||||
<div id="modal" className="container"></div>
|
||||
<Footer />
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Route, IndexRoute } from 'react-router';
|
||||
|
||||
import Landing from './components/prize_landing';
|
||||
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 App from './prize_app';
|
||||
import AppConstants from '../../../constants/application_constants';
|
||||
|
||||
let Route = Router.Route;
|
||||
let NotFoundRoute = Router.NotFoundRoute;
|
||||
let baseUrl = AppConstants.baseUrl;
|
||||
import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler';
|
||||
|
||||
|
||||
function getRoutes() {
|
||||
return (
|
||||
<Route name="app" path={baseUrl} handler={App}>
|
||||
<Route name="landing" path={baseUrl} handler={Landing} />
|
||||
<Route name="login" path="login" handler={LoginContainer} />
|
||||
<Route name="logout" path="logout" handler={LogoutContainer} />
|
||||
<Route name="signup" path="signup" handler={SignupContainer} />
|
||||
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
|
||||
<Route name="register_piece" path="register_piece" handler={PrizeRegisterPiece} headerTitle="+ NEW WORK" />
|
||||
<Route name="pieces" path="collection" handler={PrizePieceList} headerTitle="COLLECTION" />
|
||||
<Route name="piece" path="pieces/:pieceId" handler={PrizePieceContainer} />
|
||||
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
||||
<Route name="settings" path="settings" handler={SettingsContainer} />
|
||||
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
|
||||
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} />
|
||||
<Route path='/' component={App}>
|
||||
<IndexRoute component={Landing} />
|
||||
<Route
|
||||
path='login'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
|
||||
<Route
|
||||
path='logout'
|
||||
component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
|
||||
<Route
|
||||
path='signup'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
|
||||
<Route
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@ -8,9 +8,13 @@ import LicenseActions from '../../../../../actions/license_actions';
|
||||
import LicenseStore from '../../../../../stores/license_store';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
|
||||
let CCRegisterPiece = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -78,11 +82,13 @@ let CCRegisterPiece = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Register a new piece'));
|
||||
return (
|
||||
<RegisterPiece
|
||||
enableLocalHashing={false}
|
||||
headerMessage={getLangText('Register under a Creative Commons license')}
|
||||
submitMessage={getLangText('Submit')}>
|
||||
submitMessage={getLangText('Submit')}
|
||||
location={this.props.location}>
|
||||
{this.getLicenses()}
|
||||
</RegisterPiece>
|
||||
);
|
||||
|
@ -3,7 +3,9 @@
|
||||
import React from 'react';
|
||||
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 WhitelabelStore from '../../../../../../stores/whitelabel_store';
|
||||
@ -37,29 +39,21 @@ let CylandSubmitButton = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
let piece = this.props.piece;
|
||||
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;
|
||||
}
|
||||
const { piece, className } = this.props;
|
||||
|
||||
return (
|
||||
<ButtonLink
|
||||
to="register_piece"
|
||||
<LinkContainer
|
||||
to="/register_piece"
|
||||
query={{
|
||||
'slide_num': 0,
|
||||
'start_from': startFrom,
|
||||
'piece_id': this.props.piece.id
|
||||
}}
|
||||
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
|
||||
'start_from': 1,
|
||||
'piece_id': piece.id
|
||||
}}>
|
||||
<Button
|
||||
className={classNames('btn', 'btn-default', 'btn-xs', className)}>
|
||||
{getLangText('Submit to Cyland')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -18,10 +18,12 @@ import WalletPieceContainer from '../../ascribe_detail/wallet_piece_container';
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../../utils/dom_utils';
|
||||
import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||
|
||||
let CylandPieceContainer = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object,
|
||||
params: React.PropTypes.object
|
||||
},
|
||||
|
||||
@ -60,6 +62,8 @@ let CylandPieceContainer = React.createClass({
|
||||
|
||||
render() {
|
||||
if(this.state.piece && this.state.piece.title) {
|
||||
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
|
||||
|
||||
return (
|
||||
<WalletPieceContainer
|
||||
piece={this.state.piece}
|
||||
@ -72,7 +76,8 @@ let CylandPieceContainer = React.createClass({
|
||||
<CylandAdditionalDataForm
|
||||
piece={this.state.piece}
|
||||
disabled={!this.state.piece.acl.acl_edit}
|
||||
isInline={true} />
|
||||
isInline={true}
|
||||
location={this.props.location}/>
|
||||
</CollapsibleParagraph>
|
||||
</WalletPieceContainer>
|
||||
);
|
||||
|
@ -26,7 +26,8 @@ let CylandAdditionalDataForm = React.createClass({
|
||||
handleSuccess: React.PropTypes.func,
|
||||
piece: React.PropTypes.object.isRequired,
|
||||
disabled: React.PropTypes.bool,
|
||||
isInline: React.PropTypes.bool
|
||||
isInline: React.PropTypes.bool,
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
@ -140,7 +141,8 @@ let CylandAdditionalDataForm = React.createClass({
|
||||
isReadyForFormSubmission={formSubmissionValidation.fileOptional}
|
||||
pieceId={piece.id}
|
||||
otherData={piece.other_data}
|
||||
multiple={true}/>
|
||||
multiple={true}
|
||||
location={this.props.location}/>
|
||||
</Form>
|
||||
);
|
||||
} else {
|
||||
|
@ -1,22 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
|
||||
import WhitelabelActions from '../../../../../actions/whitelabel_actions';
|
||||
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 UserActions from '../../../../../actions/user_actions';
|
||||
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
|
||||
let CylandLanding = React.createClass({
|
||||
|
||||
mixins: [Router.Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -43,11 +47,13 @@ let CylandLanding = React.createClass({
|
||||
// if user is already logged in, redirect him to piece list
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
// FIXME: hack to redirect out of the dispatch cycle
|
||||
window.setTimeout(() => this.replaceWith('pieces'), 0);
|
||||
window.setTimeout(() => this.history.replaceState(null, '/collection'), 0);
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle('CYLAND MediaArtLab');
|
||||
|
||||
return (
|
||||
<div className="container ascribe-form-wrapper">
|
||||
<div className="row">
|
||||
@ -67,17 +73,21 @@ let CylandLanding = React.createClass({
|
||||
<p>
|
||||
{getLangText('Existing ascribe user?')}
|
||||
</p>
|
||||
<ButtonLink to="login">
|
||||
<LinkContainer to="/login">
|
||||
<Button>
|
||||
{getLangText('Log in')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<p>
|
||||
{getLangText('Do you need an account?')}
|
||||
</p>
|
||||
<ButtonLink to="signup">
|
||||
<LinkContainer to="/signup">
|
||||
<Button>
|
||||
{getLangText('Sign up')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,9 +9,14 @@ import UserStore from '../../../../../stores/user_store';
|
||||
import CylandAccordionListItem from './cyland_accordion_list/cyland_accordion_list_item';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
|
||||
|
||||
let CylandPieceList = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
@ -30,10 +35,12 @@ let CylandPieceList = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Collection'));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PieceList
|
||||
redirectTo="register_piece"
|
||||
redirectTo="/register_piece?slide_num=0"
|
||||
accordionListItemType={CylandAccordionListItem}
|
||||
filterParams={[{
|
||||
label: getLangText('Show works I have'),
|
||||
@ -42,7 +49,7 @@ let CylandPieceList = React.createClass({
|
||||
label: getLangText('loaned to Cyland')
|
||||
}]
|
||||
}]}
|
||||
/>
|
||||
location={this.props.location}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Moment from 'moment';
|
||||
|
||||
@ -34,13 +34,17 @@ import SlidesContainer from '../../../../ascribe_slides_container/slides_contain
|
||||
import ApiUrls from '../../../../../constants/api_urls';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
import { getAclFormMessage } from '../../../../../utils/form_utils';
|
||||
|
||||
|
||||
let CylandRegisterPiece = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState(){
|
||||
return mergeOptions(
|
||||
@ -63,7 +67,7 @@ let CylandRegisterPiece = React.createClass({
|
||||
UserActions.fetchCurrentUser();
|
||||
WhitelabelActions.fetchWhitelabel();
|
||||
|
||||
let queryParams = this.getQuery();
|
||||
let queryParams = this.props.location.query;
|
||||
|
||||
// Since every step of this register process is atomic,
|
||||
// we may need to enter the process at step 1 or 2.
|
||||
@ -101,12 +105,13 @@ let CylandRegisterPiece = React.createClass({
|
||||
|
||||
// also start loading the piece for the next step
|
||||
if(response && response.piece) {
|
||||
PieceActions.updatePiece({});
|
||||
PieceActions.updatePiece(response.piece);
|
||||
}
|
||||
|
||||
this.incrementStep();
|
||||
|
||||
this.refs.slidesContainer.nextSlide();
|
||||
this.refs.slidesContainer.nextSlide({ piece_id: response.piece.id });
|
||||
},
|
||||
|
||||
handleAdditionalDataSuccess() {
|
||||
@ -130,7 +135,8 @@ let CylandRegisterPiece = React.createClass({
|
||||
this.refreshPieceList();
|
||||
|
||||
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
|
||||
@ -163,7 +169,7 @@ let CylandRegisterPiece = React.createClass({
|
||||
|
||||
// basically redirects to the second slide (index: 1), when the user is not logged in
|
||||
onLoggedOut() {
|
||||
this.transitionTo('login');
|
||||
this.history.pushState(null, '/login');
|
||||
},
|
||||
|
||||
render() {
|
||||
@ -172,6 +178,8 @@ let CylandRegisterPiece = React.createClass({
|
||||
let datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain = new Moment();
|
||||
datetimeWhenWeAllWillBeFlyingCoolHoverboardsAndDinosaursWillLiveAgain.add(1000, 'years');
|
||||
|
||||
setDocumentTitle(getLangText('Register a new piece'));
|
||||
|
||||
return (
|
||||
<SlidesContainer
|
||||
ref="slidesContainer"
|
||||
@ -179,7 +187,8 @@ let CylandRegisterPiece = React.createClass({
|
||||
glyphiconClassNames={{
|
||||
pending: 'glyphicon glyphicon-chevron-right',
|
||||
completed: 'glyphicon glyphicon-lock'
|
||||
}}>
|
||||
}}
|
||||
location={this.props.location}>
|
||||
<div data-slide-title={getLangText('Register work')}>
|
||||
<Row className="no-margin">
|
||||
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
||||
@ -190,7 +199,8 @@ let CylandRegisterPiece = React.createClass({
|
||||
submitMessage={getLangText('Submit')}
|
||||
isFineUploaderActive={this.state.isFineUploaderActive}
|
||||
handleSuccess={this.handleRegisterSuccess}
|
||||
onLoggedOut={this.onLoggedOut} />
|
||||
onLoggedOut={this.onLoggedOut}
|
||||
location={this.props.location}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
@ -200,7 +210,8 @@ let CylandRegisterPiece = React.createClass({
|
||||
<CylandAdditionalDataForm
|
||||
disabled={this.state.step > 1}
|
||||
handleSuccess={this.handleAdditionalDataSuccess}
|
||||
piece={this.state.piece}/>
|
||||
piece={this.state.piece}
|
||||
location={this.props.location}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
@ -32,16 +35,18 @@ let IkonotvSubmitButton = React.createClass({
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonLink
|
||||
to="register_piece"
|
||||
<LinkContainer
|
||||
to="/register_piece"
|
||||
query={{
|
||||
'slide_num': 0,
|
||||
'start_from': startFrom,
|
||||
'piece_id': piece.id
|
||||
}}
|
||||
}}>
|
||||
<Button
|
||||
className={classNames('ascribe-margin-1px', this.props.className)}>
|
||||
{getLangText('Loan to IkonoTV')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
@ -27,13 +27,13 @@ import Property from '../../../../ascribe_forms/property';
|
||||
import AppConstants from '../../../../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
|
||||
let Navigation = Router.Navigation;
|
||||
|
||||
let IkonotvContractNotifications = React.createClass({
|
||||
|
||||
mixins: [Navigation],
|
||||
mixins: [History],
|
||||
|
||||
getInitialState() {
|
||||
return mergeOptions(
|
||||
@ -114,7 +114,7 @@ let IkonotvContractNotifications = React.createClass({
|
||||
handleConfirmSuccess() {
|
||||
let notification = new GlobalNotificationModel(getLangText('You have accepted the conditions'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
},
|
||||
|
||||
handleDeny() {
|
||||
@ -127,7 +127,7 @@ let IkonotvContractNotifications = React.createClass({
|
||||
handleDenySuccess() {
|
||||
let notification = new GlobalNotificationModel(getLangText('You have denied the conditions'), 'success', 5000);
|
||||
GlobalNotificationActions.appendGlobalNotification(notification);
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
},
|
||||
|
||||
getCopyrightAssociationForm(){
|
||||
@ -149,6 +149,8 @@ let IkonotvContractNotifications = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Contacts notifications'));
|
||||
|
||||
if (this.state.contractAgreementListNotifications &&
|
||||
this.state.contractAgreementListNotifications.length > 0) {
|
||||
|
||||
|
@ -19,6 +19,7 @@ import WalletPieceContainer from '../../ascribe_detail/wallet_piece_container';
|
||||
import AppConstants from '../../../../../../constants/application_constants';
|
||||
|
||||
import { getLangText } from '../../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../../utils/dom_utils';
|
||||
import { mergeOptions } from '../../../../../../utils/general_utils';
|
||||
|
||||
|
||||
@ -95,6 +96,7 @@ let IkonotvPieceContainer = React.createClass({
|
||||
}
|
||||
|
||||
if(this.state.piece && this.state.piece.title) {
|
||||
setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', '));
|
||||
return (
|
||||
<WalletPieceContainer
|
||||
piece={this.state.piece}
|
||||
|
@ -1,19 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
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 UserActions from '../../../../../actions/user_actions';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
|
||||
|
||||
let IkonotvLanding = React.createClass({
|
||||
|
||||
mixins: [Router.Navigation, Router.State],
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
@ -33,22 +36,27 @@ let IkonotvLanding = React.createClass({
|
||||
},
|
||||
|
||||
getEnterButton() {
|
||||
let redirect = 'login';
|
||||
let redirect = '/login';
|
||||
|
||||
if(this.state.currentUser && this.state.currentUser.email) {
|
||||
redirect = 'pieces';
|
||||
redirect = '/collection';
|
||||
}
|
||||
else if (this.getQuery() && this.getQuery().redirect) {
|
||||
redirect = this.getQuery().redirect;
|
||||
else if (this.props.location.query && this.props.location.query.redirect) {
|
||||
redirect = '/' + this.props.location.query.redirect;
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonLink to={redirect} query={this.getQuery()}>
|
||||
<LinkContainer to={redirect} query={this.props.location.query}>
|
||||
<Button>
|
||||
{getLangText('ENTER TO START')}
|
||||
</ButtonLink>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
);
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle('ikonoTV');
|
||||
|
||||
return (
|
||||
<div className="ikonotv-landing">
|
||||
<header>
|
||||
|
@ -9,9 +9,14 @@ import UserStore from '../../../../../stores/user_store';
|
||||
import IkonotvAccordionListItem from './ikonotv_accordion_list/ikonotv_accordion_list_item';
|
||||
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
import { setDocumentTitle } from '../../../../../utils/dom_utils';
|
||||
|
||||
|
||||
let IkonotvPieceList = React.createClass({
|
||||
propTypes: {
|
||||
location: React.PropTypes.object
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return UserStore.getState();
|
||||
},
|
||||
@ -30,10 +35,12 @@ let IkonotvPieceList = React.createClass({
|
||||
},
|
||||
|
||||
render() {
|
||||
setDocumentTitle(getLangText('Register a new piece'));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PieceList
|
||||
redirectTo="register_piece"
|
||||
redirectTo="/register_piece?slide_num=0"
|
||||
accordionListItemType={IkonotvAccordionListItem}
|
||||
filterParams={[{
|
||||
label: getLangText('Show works I have'),
|
||||
@ -47,7 +54,8 @@ let IkonotvPieceList = React.createClass({
|
||||
label: getLangText('loaned')
|
||||
}
|
||||
]
|
||||
}]}/>
|
||||
}]}
|
||||
location={this.props.location}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import Moment from 'moment';
|
||||
import Router from 'react-router';
|
||||
import { History } from 'react-router';
|
||||
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
import Row from 'react-bootstrap/lib/Row';
|
||||
@ -33,13 +33,13 @@ import { mergeOptions } from '../../../../../utils/general_utils';
|
||||
import { getLangText } from '../../../../../utils/lang_utils';
|
||||
|
||||
let IkonotvRegisterPiece = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
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(){
|
||||
return mergeOptions(
|
||||
@ -61,7 +61,7 @@ let IkonotvRegisterPiece = React.createClass({
|
||||
// not want to display to the user.
|
||||
PieceActions.updatePiece({});
|
||||
|
||||
let queryParams = this.getQuery();
|
||||
let queryParams = this.props.location.query;
|
||||
|
||||
// Since every step of this register process is atomic,
|
||||
// we may need to enter the process at step 1 or 2.
|
||||
@ -102,7 +102,7 @@ let IkonotvRegisterPiece = React.createClass({
|
||||
PieceActions.updatePiece(response.piece);
|
||||
}
|
||||
if (!this.canSubmit()) {
|
||||
this.transitionTo('pieces');
|
||||
this.history.pushState(null, '/collection');
|
||||
}
|
||||
else {
|
||||
this.incrementStep();
|
||||
@ -132,7 +132,7 @@ let IkonotvRegisterPiece = React.createClass({
|
||||
this.refreshPieceList();
|
||||
|
||||
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
|
||||
@ -165,7 +165,7 @@ let IkonotvRegisterPiece = React.createClass({
|
||||
|
||||
// basically redirects to the second slide (index: 1), when the user is not logged in
|
||||
onLoggedOut() {
|
||||
this.transitionTo('login');
|
||||
this.history.pushState(null, '/login');
|
||||
},
|
||||
|
||||
canSubmit() {
|
||||
@ -245,7 +245,8 @@ let IkonotvRegisterPiece = React.createClass({
|
||||
glyphiconClassNames={{
|
||||
pending: 'glyphicon glyphicon-chevron-right',
|
||||
completed: 'glyphicon glyphicon-lock'
|
||||
}}>
|
||||
}}
|
||||
location={this.props.location}>
|
||||
<div data-slide-title={getLangText('Register work')}>
|
||||
<Row className="no-margin">
|
||||
<Col xs={12} sm={10} md={8} smOffset={1} mdOffset={2}>
|
||||
@ -256,7 +257,8 @@ let IkonotvRegisterPiece = React.createClass({
|
||||
submitMessage={getLangText('Register')}
|
||||
isFineUploaderActive={this.state.isFineUploaderActive}
|
||||
handleSuccess={this.handleRegisterSuccess}
|
||||
onLoggedOut={this.onLoggedOut} />
|
||||
onLoggedOut={this.onLoggedOut}
|
||||
location={this.props.location}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -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;
|
@ -1,43 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import Header from '../../header';
|
||||
import Footer from '../../footer';
|
||||
|
||||
import GlobalNotification from '../../global_notification';
|
||||
|
||||
import getRoutes from './wallet_routes';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { getSubdomain } from '../../../utils/general_utils';
|
||||
|
||||
|
||||
let RouteHandler = Router.RouteHandler;
|
||||
|
||||
|
||||
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() {
|
||||
let subdomain = getSubdomain();
|
||||
let ROUTES = getRoutes(null, subdomain);
|
||||
let activeRoutes = this.getRoutes().map(elem => 'route--' + elem.name);
|
||||
|
||||
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', 'cyland']).indexOf(subdomain) > -1) {
|
||||
header = (
|
||||
<div className="hero"/>);
|
||||
header = (<div className="hero"/>);
|
||||
} 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 (
|
||||
<div className={classNames('ascribe-wallet-app', activeRoutes)}>
|
||||
<div className={classNames('ascribe-wallet-app', 'route--' + (path ? path.split('/')[0] : 'landing'))}>
|
||||
<div className='container'>
|
||||
{header}
|
||||
<RouteHandler />
|
||||
{children}
|
||||
<GlobalNotification />
|
||||
<div id="modal" className="container"></div>
|
||||
<Footer />
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Route, IndexRoute } from 'react-router';
|
||||
|
||||
// general components
|
||||
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 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 IkonotvPieceContainer from './components/ikonotv/ikonotv_detail/ikonotv_piece_container';
|
||||
import IkonotvContractNotifications from './components/ikonotv/ikonotv_contract_notifications';
|
||||
|
||||
import CCRegisterPiece from './components/cc/cc_register_piece';
|
||||
|
||||
import WalletApp from './wallet_app';
|
||||
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;
|
||||
import WalletApp from './wallet_app';
|
||||
|
||||
|
||||
let ROUTES = {
|
||||
'cyland': (
|
||||
<Route name="app" path={baseUrl} handler={WalletApp}>
|
||||
<Route name="landing" path={baseUrl} handler={CylandLanding} />
|
||||
<Route name="login" path="login" handler={LoginContainer} />
|
||||
<Route name="logout" path="logout" handler={LogoutContainer} />
|
||||
<Route name="signup" path="signup" handler={SignupContainer} />
|
||||
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
|
||||
<Route name="register_piece" path="register_piece" handler={CylandRegisterPiece} headerTitle="+ NEW WORK" />
|
||||
<Route name="pieces" path="collection" handler={CylandPieceList} headerTitle="COLLECTION" />
|
||||
<Route name="piece" path="pieces/:pieceId" handler={CylandPieceContainer} />
|
||||
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
||||
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
|
||||
<Route name="settings" path="settings" handler={SettingsContainer} />
|
||||
<Route name="contract_settings" path="contract_settings" handler={ContractSettings} />
|
||||
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} />
|
||||
<Route path='/' component={WalletApp}>
|
||||
<IndexRoute component={CylandLanding} />
|
||||
<Route
|
||||
path='login'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
|
||||
<Route
|
||||
path='logout'
|
||||
component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
|
||||
<Route
|
||||
path='signup'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
|
||||
<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='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>
|
||||
),
|
||||
'cc': (
|
||||
<Route name="app" path={baseUrl} handler={WalletApp}>
|
||||
<Redirect from={baseUrl} to="login" />
|
||||
<Redirect from={baseUrl + '/'} to="login" />
|
||||
<Route name="login" path="login" handler={LoginContainer} />
|
||||
<Route name="logout" path="logout" handler={LogoutContainer} />
|
||||
<Route name="signup" path="signup" handler={SignupContainer} />
|
||||
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
|
||||
<Route name="register_piece" path="register_piece" handler={CCRegisterPiece} headerTitle="+ NEW WORK" />
|
||||
<Route name="pieces" path="collection" handler={PieceList} headerTitle="COLLECTION" />
|
||||
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
|
||||
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
||||
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
|
||||
<Route name="settings" path="settings" handler={SettingsContainer} />
|
||||
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} />
|
||||
<Route path='/' component={WalletApp}>
|
||||
<Route
|
||||
path='login'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
|
||||
<Route
|
||||
path='logout'
|
||||
component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
|
||||
<Route
|
||||
path='signup'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
|
||||
<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='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>
|
||||
),
|
||||
'ikonotv': (
|
||||
<Route name="app" path={baseUrl} handler={WalletApp}>
|
||||
<Route name="landing" path={baseUrl} handler={IkonotvLanding} />
|
||||
<Route name="login" path="login" handler={LoginContainer} />
|
||||
<Route name="logout" path="logout" handler={LogoutContainer} />
|
||||
<Route name="signup" path="signup" handler={SignupContainer} />
|
||||
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
|
||||
<Route name="request_loan" path="request_loan" handler={IkonotvRequestLoan} headerTitle="SEND NEW CONTRACT" aclName="acl_create_contractagreement" />
|
||||
<Route name="register_piece" path="register_piece" handler={IkonotvRegisterPiece} headerTitle="+ NEW WORK" aclName="acl_create_piece"/>
|
||||
<Route name="pieces" path="collection" handler={IkonotvPieceList} headerTitle="COLLECTION"/>
|
||||
<Route name="piece" path="pieces/:pieceId" handler={IkonotvPieceContainer} />
|
||||
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
||||
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
|
||||
<Route name="settings" path="settings" handler={SettingsContainer} />
|
||||
<Route name="contract_settings" path="contract_settings" handler={ContractSettings} />
|
||||
<Route name="contract_notifications" path="contract_notifications" handler={IkonotvContractNotifications} />
|
||||
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} />
|
||||
<Route path='/' component={WalletApp}>
|
||||
<IndexRoute component={IkonotvLanding} />
|
||||
<Route
|
||||
path='login'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
|
||||
<Route
|
||||
path='logout'
|
||||
component={AuthProxyHandler({to: '/', when: 'loggedOut'})(LogoutContainer)}/>
|
||||
<Route
|
||||
path='signup'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SignupContainer)} />
|
||||
<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='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>
|
||||
)
|
||||
};
|
||||
|
@ -78,7 +78,8 @@ let constants = {
|
||||
'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA',
|
||||
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
|
||||
'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;
|
||||
|
13
js/history.js
Normal file
13
js/history.js
Normal 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
|
||||
});
|
57
js/routes.js
57
js/routes.js
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Router from 'react-router';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
import getPrizeRoutes from './components/whitelabel/prize/prize_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 AppConstants from './constants/application_constants';
|
||||
|
||||
let Route = Router.Route;
|
||||
let NotFoundRoute = Router.NotFoundRoute;
|
||||
let Redirect = Router.Redirect;
|
||||
let baseUrl = AppConstants.baseUrl;
|
||||
import AuthProxyHandler from './components/ascribe_routes/proxy_routes/auth_proxy_handler';
|
||||
|
||||
|
||||
const COMMON_ROUTES = (
|
||||
<Route name="app" path={baseUrl} handler={App}>
|
||||
<Redirect from={baseUrl} to="login" />
|
||||
<Redirect from={baseUrl + '/'} to="login" />
|
||||
<Route name="signup" path="signup" handler={SignupContainer} />
|
||||
<Route name="login" path="login" handler={LoginContainer} />
|
||||
<Route name="logout" path="logout" handler={LogoutContainer} />
|
||||
<Route name="register_piece" path="register_piece" handler={RegisterPiece} headerTitle="+ NEW WORK" />
|
||||
<Route name="pieces" path="collection" handler={PieceList} headerTitle="COLLECTION" />
|
||||
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
|
||||
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
|
||||
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
|
||||
<Route name="settings" path="settings" handler={SettingsContainer} />
|
||||
<Route name="contract_settings" path="contract_settings" handler={ContractSettings} />
|
||||
<Route name="coa_verify" path="verify" handler={CoaVerifyContainer} />
|
||||
<NotFoundRoute name="notFound" handler={ErrorNotFoundPage} />
|
||||
let COMMON_ROUTES = (
|
||||
<Route path='/' component={App}>
|
||||
<Route
|
||||
path='login'
|
||||
component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(LoginContainer)} />
|
||||
<Route
|
||||
path='register_piece'
|
||||
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(RegisterPiece)}
|
||||
headerTitle='+ NEW WORK'/>
|
||||
<Route
|
||||
path='collection'
|
||||
component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(PieceList)}
|
||||
headerTitle='COLLECTION'/>
|
||||
<Route
|
||||
path='signup'
|
||||
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>
|
||||
);
|
||||
|
||||
|
7
js/third_party/notifications.js
vendored
7
js/third_party/notifications.js
vendored
@ -1,6 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
import history from '../history';
|
||||
import { altThirdParty } from '../alt';
|
||||
|
||||
|
||||
import EventActions from '../actions/event_actions';
|
||||
|
||||
import NotificationActions from '../actions/notification_actions';
|
||||
@ -9,7 +12,6 @@ import { getSubdomain } from '../utils/general_utils';
|
||||
|
||||
|
||||
class NotificationsHandler {
|
||||
|
||||
constructor() {
|
||||
this.bindActions(EventActions);
|
||||
this.loaded = false;
|
||||
@ -19,6 +21,7 @@ class NotificationsHandler {
|
||||
if (this.loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
let subdomain = getSubdomain();
|
||||
if (subdomain === 'ikonotv') {
|
||||
NotificationActions.fetchContractAgreementListNotifications().then(
|
||||
@ -26,7 +29,7 @@ class NotificationsHandler {
|
||||
if (res.notifications && res.notifications.length > 0) {
|
||||
this.loaded = true;
|
||||
console.log('Contractagreement notifications loaded');
|
||||
setTimeout(() => window.appRouter.transitionTo('contract_notifications'), 0);
|
||||
history.pushState(null, '/contract_notifications');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -8,8 +8,8 @@ export function getSubdomainSettings(subdomain) {
|
||||
if(settings.length === 1) {
|
||||
return settings[0];
|
||||
} else if(settings.length === 0) {
|
||||
console.warn('There are no subdomain settings for the subdomain: ' + subdomain);
|
||||
return appConstants.defaultDomain;
|
||||
// throw new Error('There are no subdomain settings for the subdomain: ' + subdomain);
|
||||
} else {
|
||||
throw new Error('Matched multiple subdomains. Adjust constants file.');
|
||||
}
|
||||
|
9
js/utils/dom_utils.js
Normal file
9
js/utils/dom_utils.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Set the title in the browser window.
|
||||
*/
|
||||
export function setDocumentTitle(title) {
|
||||
document.title = title;
|
||||
}
|
26
js/utils/feature_detection_utils.js
Normal file
26
js/utils/feature_detection_utils.js
Normal 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);
|
@ -57,12 +57,14 @@ class Requests {
|
||||
});
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
handleError(url) {
|
||||
return (err) => {
|
||||
if (err instanceof TypeError) {
|
||||
throw new Error('Server did not respond to the request. (Not even displayed a 500)');
|
||||
throw new Error('For: ' + url + ' - Server did not respond to the request. (Not even displayed a 500)');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getUrl(url) {
|
||||
@ -118,7 +120,7 @@ class Requests {
|
||||
merged.method = verb;
|
||||
return fetch(url, merged)
|
||||
.then(this.unpackResponse)
|
||||
.catch(this.handleError);
|
||||
.catch(this.handleError(url));
|
||||
}
|
||||
|
||||
get(url, params) {
|
||||
|
10
package.json
10
package.json
@ -45,7 +45,7 @@
|
||||
"bootstrap-sass": "^3.3.4",
|
||||
"browser-sync": "^2.7.5",
|
||||
"browserify": "^9.0.8",
|
||||
"browserify-shim": "^3.8.9",
|
||||
"browserify-shim": "^3.8.10",
|
||||
"classnames": "^1.2.2",
|
||||
"compression": "^1.4.4",
|
||||
"envify": "^3.4.0",
|
||||
@ -64,6 +64,8 @@
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"gulp-util": "^3.0.4",
|
||||
"harmonize": "^1.4.2",
|
||||
"history": "^1.11.1",
|
||||
"invariant": "^2.1.1",
|
||||
"isomorphic-fetch": "^2.0.2",
|
||||
"jest-cli": "^0.4.0",
|
||||
"lodash": "^3.9.3",
|
||||
@ -72,11 +74,11 @@
|
||||
"opn": "^3.0.2",
|
||||
"q": "^1.4.1",
|
||||
"raven-js": "^1.1.19",
|
||||
"react": "^0.13.2",
|
||||
"react": "0.13.2",
|
||||
"react-bootstrap": "0.25.1",
|
||||
"react-datepicker": "^0.12.0",
|
||||
"react-router": "^0.13.3",
|
||||
"react-router-bootstrap": "~0.16.0",
|
||||
"react-router": "1.0.0-rc1",
|
||||
"react-router-bootstrap": "^0.19.0",
|
||||
"react-star-rating": "~1.3.2",
|
||||
"react-textarea-autosize": "^2.5.2",
|
||||
"reactify": "^1.1.0",
|
||||
|
@ -21,7 +21,6 @@
|
||||
|
||||
.notification-contract-pdf, .notification-contract-footer {
|
||||
width: 100%;
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.notification-contract-pdf {
|
||||
@ -29,7 +28,8 @@
|
||||
border: 1px solid #cccccc;
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
margin-bottom: 0;
|
||||
margin-top: .5em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.loan-form {
|
||||
margin-top: .5em;
|
||||
|
Loading…
Reference in New Issue
Block a user