diff --git a/.eslintrc b/.eslintrc index 2312f48f..d41c0a2a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -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, @@ -58,4 +58,4 @@ "superInFunctions": 1, "templateStrings": 1 } -} \ No newline at end of file +} diff --git a/fonts/ascribe-logo.eot b/fonts/ascribe-logo.eot new file mode 100644 index 00000000..609770bc Binary files /dev/null and b/fonts/ascribe-logo.eot differ diff --git a/fonts/ascribe-logo.svg b/fonts/ascribe-logo.svg new file mode 100644 index 00000000..28ab9458 --- /dev/null +++ b/fonts/ascribe-logo.svg @@ -0,0 +1,19 @@ + + + +Generated by IcoMoon + + \ No newline at end of file diff --git a/fonts/ascribe-logo.ttf b/fonts/ascribe-logo.ttf new file mode 100644 index 00000000..79acfe89 Binary files /dev/null and b/fonts/ascribe-logo.ttf differ diff --git a/fonts/ascribe-logo.woff b/fonts/ascribe-logo.woff new file mode 100644 index 00000000..e8c8b031 Binary files /dev/null and b/fonts/ascribe-logo.woff differ diff --git a/fonts/ascribe_logo.svg b/fonts/ascribe_logo.svg deleted file mode 100644 index 3d28d0a8..00000000 --- a/fonts/ascribe_logo.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - diff --git a/gulpfile.js b/gulpfile.js index 3c92945d..f13945b0 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -111,7 +111,11 @@ gulp.task('sass:build', function () { ] }).on('error', sass.logError)) .pipe(gulpif(!argv.production, sourcemaps.write('./maps'))) - .pipe(gulpif(argv.production, minifyCss())) + // We need to set `advanced` to false, as it merges + // some of the styles wrongly + .pipe(gulpif(argv.production, minifyCss({ + advanced: false + }))) .pipe(gulp.dest('./build/css')) .pipe(browserSync.stream()); }); diff --git a/index.html b/index.html index 9b7f7c96..28b4d222 100644 --- a/index.html +++ b/index.html @@ -2,17 +2,8 @@ - - - - - - - - ascribe - <% DEBUG && print('') %> @@ -25,6 +16,12 @@ <% DEBUG && print('window.DEBUG = true'); %> <% DEBUG && print('window.CREDENTIALS = \'' + CREDENTIALS + '\''); %> + + +
diff --git a/js/actions/application_actions.js b/js/actions/application_actions.js index 733746cf..e7f96275 100644 --- a/js/actions/application_actions.js +++ b/js/actions/application_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import ApplicationFetcher from '../fetchers/application_fetcher'; diff --git a/js/actions/coa_actions.js b/js/actions/coa_actions.js index 7c6f5118..d3d13290 100644 --- a/js/actions/coa_actions.js +++ b/js/actions/coa_actions.js @@ -1,8 +1,9 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import CoaFetcher from '../fetchers/coa_fetcher'; +import Q from 'q'; class CoaActions { constructor() { @@ -12,23 +13,38 @@ class CoaActions { ); } - fetchOne(id) { - CoaFetcher.fetchOne(id) - .then((res) => { - this.actions.updateCoa(res.coa); - }) - .catch((err) => { - console.logGlobal(err); - }); + fetchOrCreate(id, bitcoinId) { + return Q.Promise((resolve, reject) => { + CoaFetcher.fetchOne(id) + .then((res) => { + if (res.coa) { + this.actions.updateCoa(res.coa); + resolve(res.coa); + } + else { + this.actions.create(bitcoinId); + } + }) + .catch((err) => { + console.logGlobal(err); + this.actions.updateCoa(null); + reject(err); + }); + }); } - create(edition) { - CoaFetcher.create(edition.bitcoin_id) - .then((res) => { - this.actions.updateCoa(res.coa); - }) - .catch((err) => { - console.logGlobal(err); - }); + + create(bitcoinId) { + return Q.Promise((resolve, reject) => { + CoaFetcher.create(bitcoinId) + .then((res) => { + this.actions.updateCoa(res.coa); + }) + .catch((err) => { + console.logGlobal(err); + this.actions.updateCoa(null); + reject(err); + }); + }); } } diff --git a/js/actions/contract_agreement_list_actions.js b/js/actions/contract_agreement_list_actions.js index 589c1f51..4993b129 100644 --- a/js/actions/contract_agreement_list_actions.js +++ b/js/actions/contract_agreement_list_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import Q from 'q'; import OwnershipFetcher from '../fetchers/ownership_fetcher'; diff --git a/js/actions/contract_list_actions.js b/js/actions/contract_list_actions.js index 5b874caf..1c5c0913 100644 --- a/js/actions/contract_list_actions.js +++ b/js/actions/contract_list_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import OwnershipFetcher from '../fetchers/ownership_fetcher'; import Q from 'q'; diff --git a/js/actions/edition_actions.js b/js/actions/edition_actions.js index 473da0e4..4bdf093a 100644 --- a/js/actions/edition_actions.js +++ b/js/actions/edition_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import EditionFetcher from '../fetchers/edition_fetcher'; diff --git a/js/actions/edition_list_actions.js b/js/actions/edition_list_actions.js index d13882cd..6f9881ee 100644 --- a/js/actions/edition_list_actions.js +++ b/js/actions/edition_list_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import Q from 'q'; import EditionListFetcher from '../fetchers/edition_list_fetcher.js'; @@ -33,6 +33,10 @@ class EditionListActions { EditionListFetcher .fetch(pieceId, page, pageSize, orderBy, orderAsc, filterBy) .then((res) => { + if(res && !res.editions) { + throw new Error('Piece has no editions to fetch.'); + } + this.actions.updateEditionList({ pieceId, page, @@ -46,6 +50,7 @@ class EditionListActions { resolve(res); }) .catch((err) => { + console.logGlobal(err); reject(err); }); }); diff --git a/js/actions/event_actions.js b/js/actions/event_actions.js index 8f1def9f..6d8ee12f 100644 --- a/js/actions/event_actions.js +++ b/js/actions/event_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { altThirdParty } from '../alt'; class EventActions { @@ -16,4 +16,4 @@ class EventActions { } } -export default alt.createActions(EventActions); +export default altThirdParty.createActions(EventActions); diff --git a/js/actions/global_notification_actions.js b/js/actions/global_notification_actions.js index b12f7906..2bb8d6e6 100644 --- a/js/actions/global_notification_actions.js +++ b/js/actions/global_notification_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; class GlobalNotificationActions { diff --git a/js/actions/license_actions.js b/js/actions/license_actions.js index bfeacd34..ad9a3d08 100644 --- a/js/actions/license_actions.js +++ b/js/actions/license_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import LicenseFetcher from '../fetchers/license_fetcher'; diff --git a/js/actions/notification_actions.js b/js/actions/notification_actions.js index 9318c922..c3a6db93 100644 --- a/js/actions/notification_actions.js +++ b/js/actions/notification_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import Q from 'q'; import NotificationFetcher from '../fetchers/notification_fetcher'; diff --git a/js/actions/ownership_actions.js b/js/actions/ownership_actions.js index 222309bb..deef2f2d 100644 --- a/js/actions/ownership_actions.js +++ b/js/actions/ownership_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import OwnershipFetcher from '../fetchers/ownership_fetcher'; diff --git a/js/actions/piece_actions.js b/js/actions/piece_actions.js index e3a41f93..7aed13fc 100644 --- a/js/actions/piece_actions.js +++ b/js/actions/piece_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import PieceFetcher from '../fetchers/piece_fetcher'; diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index ae5ac090..7ef9cb59 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -1,10 +1,11 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; 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) diff --git a/js/actions/prize_list_actions.js b/js/actions/prize_list_actions.js index fddf1a04..da2f97df 100644 --- a/js/actions/prize_list_actions.js +++ b/js/actions/prize_list_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import Q from 'q'; import PrizeListFetcher from '../fetchers/prize_list_fetcher'; diff --git a/js/actions/user_actions.js b/js/actions/user_actions.js index 2a2c3c05..3780a802 100644 --- a/js/actions/user_actions.js +++ b/js/actions/user_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { altUser } from '../alt'; import UserFetcher from '../fetchers/user_fetcher'; @@ -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(); }) @@ -34,4 +34,4 @@ class UserActions { } } -export default alt.createActions(UserActions); +export default altUser.createActions(UserActions); diff --git a/js/actions/wallet_settings_actions.js b/js/actions/wallet_settings_actions.js index 11a21631..1094c8e2 100644 --- a/js/actions/wallet_settings_actions.js +++ b/js/actions/wallet_settings_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { alt } from '../alt'; import WalletSettingsFetcher from '../fetchers/wallet_settings_fetcher'; diff --git a/js/actions/whitelabel_actions.js b/js/actions/whitelabel_actions.js index 41ab1421..a1460fb8 100644 --- a/js/actions/whitelabel_actions.js +++ b/js/actions/whitelabel_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import alt from '../alt'; +import { altWhitelabel } from '../alt'; import WhitelabelFetcher from '../fetchers/whitelabel_fetcher'; @@ -26,4 +26,4 @@ class WhitelabelActions { } } -export default alt.createActions(WhitelabelActions); +export default altWhitelabel.createActions(WhitelabelActions); diff --git a/js/alt.js b/js/alt.js index 94786185..141248c1 100644 --- a/js/alt.js +++ b/js/alt.js @@ -2,4 +2,7 @@ import Alt from 'alt'; -export default new Alt(); +export let alt = new Alt(); +export let altThirdParty = new Alt(); +export let altUser = new Alt(); +export let altWhitelabel = new Alt(); diff --git a/js/app.js b/js/app.js index 30a57d2b..c9451e47 100644 --- a/js/app.js +++ b/js/app.js @@ -3,20 +3,23 @@ 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'; /* eslint-enable */ import ApiUrls from './constants/api_urls'; -import { updateApiUrls } from './constants/api_urls'; -import appConstants from './constants/application_constants'; + +import AppConstants from './constants/application_constants'; import getRoutes from './routes'; import requests from './utils/requests'; +import { updateApiUrls } from './constants/api_urls'; import { getSubdomainSettings } from './utils/constants_utils'; import { initLogging } from './utils/error_utils'; +import { getSubdomain } from './utils/general_utils'; import EventActions from './actions/event_actions'; @@ -44,15 +47,14 @@ requests.defaults({ } }); - class AppGateway { start() { let settings; - let subdomain = window.location.host.split('.')[0]; + let subdomain = getSubdomain(); try { settings = getSubdomainSettings(subdomain); - appConstants.whitelabel = settings; + AppConstants.whitelabel = settings; updateApiUrls(settings.type, subdomain); this.load(settings); } catch(err) { @@ -66,22 +68,36 @@ class AppGateway { load(settings) { let type = 'default'; let subdomain = 'www'; + let redirectRoute = (); 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( - , - 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(( + + {redirectRoute} + {getRoutes(type, subdomain)} + + ), document.getElementById('main')); + + // Send the applicationDidBoot event to the third-party stores EventActions.applicationDidBoot(settings); } } diff --git a/js/components/ascribe_accordion_list/accordion_list.js b/js/components/ascribe_accordion_list/accordion_list.js index fe300702..1046ab7f 100644 --- a/js/components/ascribe_accordion_list/accordion_list.js +++ b/js/components/ascribe_accordion_list/accordion_list.js @@ -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 (
{this.props.children}
); - } else if(this.props.count === 0) { + } else if(this.props.count === 0 && !search) { return (
-

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

-

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

+

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

+

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

+
+ ); + } else if(this.props.count === 0 && search) { + return ( +
+

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

+

+ {getLangText('You\'re filtering by the search keyword: \'%s\' ', search)} +

+

+ +

); } else { diff --git a/js/components/ascribe_accordion_list/accordion_list_item.js b/js/components/ascribe_accordion_list/accordion_list_item.js index 6204f57d..38cb77b1 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item.js +++ b/js/components/ascribe_accordion_list/accordion_list_item.js @@ -1,7 +1,8 @@ 'use strict'; import React from 'react'; -import Router from 'react-router'; +import { Link } from 'react-router'; + let AccordionListItem = React.createClass({ propTypes: { @@ -12,39 +13,57 @@ let AccordionListItem = React.createClass({ subheading: React.PropTypes.object, subsubheading: React.PropTypes.object, buttons: React.PropTypes.object, + linkData: React.PropTypes.string, children: React.PropTypes.oneOfType([ React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.element ]) }, - mixins: [Router.Navigation], - render() { + const { linkData, + className, + thumbnail, + heading, + subheading, + subsubheading, + buttons, + badge, + children } = this.props; + return (
-
+
-
-
- {this.props.thumbnail} +
+ +
+ {thumbnail} +
+
-
-
- {this.props.heading} - {this.props.subheading} - {this.props.subsubheading} - {this.props.buttons} -
+
+ + {heading} + + + {subheading} + {subsubheading} + +
+ {buttons} +
+
+
- {this.props.badge} + {badge}
- {this.props.children} + {children}
); } diff --git a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js index 709160b9..47c0fb77 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_edition_widget.js @@ -11,6 +11,7 @@ import PieceListStore from '../../stores/piece_list_store'; import Button from 'react-bootstrap/lib/Button'; import CreateEditionsButton from '../ascribe_buttons/create_editions_button'; +import AscribeSpinner from '../ascribe_spinner'; import { mergeOptions } from '../../utils/general_utils'; import { getLangText } from '../../utils/lang_utils'; @@ -75,7 +76,10 @@ let AccordionListItemEditionWidget = React.createClass({ // PLEASE FUTURE TIM, DO NOT FUCKING REMOVE IT AGAIN! if(typeof this.state.editionList[pieceId] === 'undefined') { return ( - + ); } else { return ( @@ -98,7 +102,7 @@ let AccordionListItemEditionWidget = React.createClass({ return ( @@ -112,12 +116,12 @@ let AccordionListItemEditionWidget = React.createClass({ if(piece.first_edition === null) { // user has deleted all his editions and only the piece is showing return ( - + ); } else { let editionMapping = piece && piece.first_edition ? piece.first_edition.num_editions_available + '/' + piece.num_editions : ''; @@ -125,7 +129,7 @@ let AccordionListItemEditionWidget = React.createClass({ return ( ); diff --git a/js/components/ascribe_accordion_list/accordion_list_item_piece.js b/js/components/ascribe_accordion_list/accordion_list_item_piece.js index d0b16c9f..4547ce3b 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_piece.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_piece.js @@ -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,51 +22,54 @@ 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() { + const { className, piece, artistName, buttons, badge, children, subsubheading } = this.props; + const { url, url_safe } = piece.thumbnail; + let thumbnail; + + // Since we're going to refactor the thumbnail generation anyway at one point, + // for not use the annoying ascribe_spiral.png, we're matching the url against + // this name and replace it with a CSS version of the new logo. + if(url.match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/)) { + thumbnail = ( + + A + + ); + } else { + thumbnail = ( +
+ ); + } + return ( - - } - heading={ - -

{this.props.piece.title}

- } + className={className} + thumbnail={thumbnail} + heading={

{piece.title}

} subheading={

{getLangText('by ')} - {this.props.artistName ? this.props.artistName : this.props.piece.artist_name} + {artistName ? artistName : piece.artist_name}

} - subsubheading={this.props.subsubheading} - buttons={this.props.buttons} - badge={this.props.badge} + subsubheading={subsubheading} + buttons={buttons} + badge={badge} + linkData={this.getLinkData()} > - {this.props.children} + {children}
); } diff --git a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js index 350d61a8..7e258267 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_table_editions.js @@ -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( diff --git a/js/components/ascribe_app.js b/js/components/ascribe_app.js index 789399b0..cda5637f 100644 --- a/js/components/ascribe_app.js +++ b/js/components/ascribe_app.js @@ -1,22 +1,31 @@ '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 (
-
- +
+ {/* Routes are injected here */} +
+ {children} +