diff --git a/README.md b/README.md index 7bca8c31..36f47954 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Additionally, to work on the white labeling functionality, you need to edit your 127.0.0.1 cyland.localhost.com 127.0.0.1 ikonotv.localhost.com 127.0.0.1 sluice.localhost.com +127.0.0.1 lumenus.localhost.com +127.0.0.1 portfolioreview.localhost.com ``` @@ -41,6 +43,7 @@ For this project, we're using: * We don't use ES6's class declaration for React components because it does not support Mixins as well as Autobinding ([Blog post about it](http://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding)) * We don't use camel case for file naming but in everything Javascript related * We use `let` instead of `var`: [SA Post](http://stackoverflow.com/questions/762011/javascript-let-keyword-vs-var-keyword) +* We don't use Javascript's `Date` object, as its interface introduced bugs previously and we're including `momentjs` for other dependencies anyways Branch names ===================== diff --git a/js/app.js b/js/app.js index c9451e47..520bedbd 100644 --- a/js/app.js +++ b/js/app.js @@ -30,6 +30,7 @@ import GoogleAnalyticsHandler from './third_party/ga'; import RavenHandler from './third_party/raven'; import IntercomHandler from './third_party/intercom'; import NotificationsHandler from './third_party/notifications'; +import FacebookHandler from './third_party/facebook'; /* eslint-enable */ initLogging(); diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js index 185f6e05..da45d1e8 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import Moment from 'moment'; import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; @@ -129,7 +130,7 @@ let AccordionListItemWallet = React.createClass({ piece={this.props.content} subsubheading={
- {new Date(this.props.content.date_created).getFullYear()} + {Moment(this.props.content.date_created, 'YYYY-MM-DD').year()} {this.getLicences()}
} buttons={ diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js index 1edf8b64..6a3df7b2 100644 --- a/js/components/ascribe_buttons/acls/acl_button.js +++ b/js/components/ascribe_buttons/acls/acl_button.js @@ -11,14 +11,8 @@ import ModalWrapper from '../../ascribe_modal/modal_wrapper'; import AppConstants from '../../../constants/application_constants'; -import GlobalNotificationModel from '../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../actions/global_notification_actions'; - -import ApiUrls from '../../../constants/api_urls'; import { AclInformationText } from '../../../constants/acl_information_text'; -import { getAclFormMessage, getAclFormDataId } from '../../../utils/form_utils'; -import { getLangText } from '../../../utils/lang_utils'; export default function ({ action, displayName, title, tooltip }) { if (AppConstants.aclList.indexOf(action) < 0) { diff --git a/js/components/ascribe_collapsible/collapsible_button.js b/js/components/ascribe_collapsible/collapsible_button.js index 6fb39c71..caf89df3 100644 --- a/js/components/ascribe_collapsible/collapsible_button.js +++ b/js/components/ascribe_collapsible/collapsible_button.js @@ -21,13 +21,13 @@ let CollapsibleButton = React.createClass({ this.setState({expanded: !this.state.expanded}); }, render() { - let isVisible = (this.state.expanded) ? '' : 'invisible'; + let isHidden = (this.state.expanded) ? '' : 'hidden'; return ( {this.props.button} -
+
{this.props.panel}
diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 64cbf714..6b38ddf8 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -2,6 +2,7 @@ import React from 'react'; import { Link, History } from 'react-router'; +import Moment from 'moment'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; @@ -85,7 +86,7 @@ let Edition = React.createClass({

{this.props.edition.title}

- +
+ multiple={this.props.multiple} /> ); } }); -export default FurtherDetailsFileuploader; \ No newline at end of file +export default FurtherDetailsFileuploader; diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index 6ac2f745..c6845a44 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -7,10 +7,18 @@ import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import MediaPlayer from './../ascribe_media/media_player'; +import FacebookShareButton from '../ascribe_social_share/facebook_share_button'; +import TwitterShareButton from '../ascribe_social_share/twitter_share_button'; + import CollapsibleButton from './../ascribe_collapsible/collapsible_button'; import AclProxy from '../acl_proxy'; +import UserActions from '../../actions/user_actions'; +import UserStore from '../../stores/user_store'; + +import { mergeOptions } from '../../utils/general_utils.js'; +import { getLangText } from '../../utils/lang_utils.js'; const EMBED_IFRAME_HEIGHT = { video: 315, @@ -24,10 +32,17 @@ let MediaContainer = React.createClass({ }, getInitialState() { - return {timerId: null}; + return mergeOptions( + UserStore.getState(), + { + timerId: null + }); }, componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + if (!this.props.content.digital_work) { return; } @@ -45,19 +60,32 @@ let MediaContainer = React.createClass({ }, componentWillUnmount() { + UserStore.unlisten(this.onChange); + window.clearInterval(this.state.timerId); }, + onChange(state) { + this.setState(state); + }, + render() { - let thumbnail = this.props.content.thumbnail.thumbnail_sizes && this.props.content.thumbnail.thumbnail_sizes['600x600'] ? - this.props.content.thumbnail.thumbnail_sizes['600x600'] : this.props.content.thumbnail.url_safe; - let mimetype = this.props.content.digital_work.mime; + const { content } = this.props; + // Pieces and editions are joined to the user by a foreign key in the database, so + // the information in content will be updated if a user updates their username. + // We also force uniqueness of usernames, so this check is safe to dtermine if the + // content was registered by the current user. + const didUserRegisterContent = this.state.currentUser && (this.state.currentUser.username === content.user_registered); + + let thumbnail = content.thumbnail.thumbnail_sizes && content.thumbnail.thumbnail_sizes['600x600'] ? + content.thumbnail.thumbnail_sizes['600x600'] : content.thumbnail.url_safe; + let mimetype = content.digital_work.mime; let embed = null; let extraData = null; - let isEmbedDisabled = mimetype === 'video' && this.props.content.digital_work.isEncoding !== undefined && this.props.content.digital_work.isEncoding !== 100; + let isEmbedDisabled = mimetype === 'video' && content.digital_work.isEncoding !== undefined && content.digital_work.isEncoding !== 100; - if (this.props.content.digital_work.encoding_urls) { - extraData = this.props.content.digital_work.encoding_urls.map(e => { return { url: e.url, type: e.label }; }); + if (content.digital_work.encoding_urls) { + extraData = content.digital_work.encoding_urls.map(e => { return { url: e.url, type: e.label }; }); } if (['video', 'audio'].indexOf(mimetype) > -1) { @@ -73,7 +101,7 @@ let MediaContainer = React.createClass({ panel={
                             {''}
+                                + content.bitcoin_id + '" frameborder="0" allowfullscreen>'}
                         
}/> ); @@ -83,16 +111,22 @@ let MediaContainer = React.createClass({ + encodingStatus={content.digital_work.isEncoding} />

+ + + + + {embed} diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8a7801ec..59fbfe5c 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -2,6 +2,7 @@ import React from 'react'; import { History } from 'react-router'; +import Moment from 'moment'; import PieceActions from '../../actions/piece_actions'; import PieceStore from '../../stores/piece_store'; @@ -236,7 +237,7 @@ let PieceContainer = React.createClass({


{this.state.piece.title}

- + {this.state.piece.num_editions > 0 ? : null}
diff --git a/js/components/ascribe_forms/form.js b/js/components/ascribe_forms/form.js index fe15f537..0e3517a2 100644 --- a/js/components/ascribe_forms/form.js +++ b/js/components/ascribe_forms/form.js @@ -12,7 +12,7 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import requests from '../../utils/requests'; import { getLangText } from '../../utils/lang_utils'; -import { mergeOptionsWithDuplicates } from '../../utils/general_utils'; +import { sanitize } from '../../utils/general_utils'; let Form = React.createClass({ @@ -124,12 +124,12 @@ let Form = React.createClass({ getFormData() { let data = {}; - for(let ref in this.refs) { + for (let ref in this.refs) { data[this.refs[ref].props.name] = this.refs[ref].state.value; } - if(typeof this.props.getFormData === 'function') { - data = mergeOptionsWithDuplicates(data, this.props.getFormData()); + if (typeof this.props.getFormData === 'function') { + data = Object.assign(data, this.props.getFormData()); } return data; @@ -155,7 +155,7 @@ let Form = React.createClass({ }); }, - handleError(err){ + handleError(err) { if (err.json) { for (let input in err.json.errors){ if (this.refs && this.refs[input] && this.refs[input].state) { @@ -185,7 +185,7 @@ let Form = React.createClass({ this.setState({submitted: false}); }, - clearErrors(){ + clearErrors() { for(let ref in this.refs){ if (this.refs[ref] && typeof this.refs[ref].clearErrors === 'function'){ this.refs[ref].clearErrors(); @@ -236,12 +236,12 @@ let Form = React.createClass({ }, renderChildren() { - return ReactAddons.Children.map(this.props.children, (child) => { + return ReactAddons.Children.map(this.props.children, (child, i) => { if (child) { return ReactAddons.addons.cloneWithProps(child, { handleChange: this.handleChangeChild, ref: child.props.name, - + key: i, // We need this in order to make editable be overridable when setting it directly // on Property editable: child.props.overrideForm ? child.props.editable : !this.props.disabled @@ -269,6 +269,83 @@ let Form = React.createClass({ } }, + /** + * Validates a single ref and returns a human-readable error message + * @param {object} refToValidate A customly constructed object to check + * @return {oneOfType([arrayOf(string), bool])} Either an error message or false, saying that + * everything is valid + */ + _hasRefErrors(refToValidate) { + let errors = Object + .keys(refToValidate) + .reduce((a, constraintKey) => { + const contraintValue = refToValidate[constraintKey]; + + if(!contraintValue) { + switch(constraintKey) { + case 'min' || 'max': + a.push(getLangText('The field you defined is not in the valid range')); + break; + case 'pattern': + a.push(getLangText('The value you defined is not matching the valid pattern')); + break; + case 'required': + a.push(getLangText('This field is required')); + break; + } + } + + return a; + }, []); + + return errors.length ? errors : false; + }, + + /** + * This method validates all child inputs of the form. + * + * As of now, it only considers + * - `max` + * - `min` + * - `pattern` + * - `required` + * + * The idea is to enhance this method everytime we need more thorough validation. + * So feel free to add props that additionally should be checked, if they're present + * in the input's props. + * + * @return {[type]} [description] + */ + validate() { + this.clearErrors(); + const validatedFormInputs = {}; + + Object + .keys(this.refs) + .forEach((refName) => { + let refToValidate = {}; + const property = this.refs[refName]; + const input = property.refs.input; + const value = input.getDOMNode().value || input.state.value; + const { max, + min, + pattern, + required, + type } = input.props; + + refToValidate.required = required ? value : true; + refToValidate.pattern = pattern && typeof value === 'string' ? value.match(pattern) : true; + refToValidate.max = type === 'number' ? parseInt(value, 10) <= max : true; + refToValidate.min = type === 'number' ? parseInt(value, 10) >= min : true; + + const validatedRef = this._hasRefErrors(refToValidate); + validatedFormInputs[refName] = validatedRef; + }); + const errorMessagesForRefs = sanitize(validatedFormInputs, (val) => !val); + this.handleError({ json: { errors: errorMessagesForRefs } }); + return !Object.keys(errorMessagesForRefs).length; + }, + render() { let className = 'ascribe-form'; diff --git a/js/components/ascribe_forms/form_create_contract.js b/js/components/ascribe_forms/form_create_contract.js index fe00cebc..aac4c5ea 100644 --- a/js/components/ascribe_forms/form_create_contract.js +++ b/js/components/ascribe_forms/form_create_contract.js @@ -28,8 +28,7 @@ let CreateContractForm = React.createClass({ fileClassToUpload: React.PropTypes.shape({ singular: React.PropTypes.string, plural: React.PropTypes.string - }), - location: React.PropTypes.object + }) }, getInitialState() { @@ -87,8 +86,7 @@ let CreateContractForm = React.createClass({ areAssetsEditable={true} setIsUploadReady={this.setIsUploadReady} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} - fileClassToUpload={this.props.fileClassToUpload} - location={this.props.location}/> + fileClassToUpload={this.props.fileClassToUpload} /> + uploadMethod={this.props.location.query.method} /> + uploadMethod={this.props.uploadMethod} + fileClassToUpload={this.props.fileClassToUpload} /> ); } }); -export default InputFineUploader; \ No newline at end of file +export default InputFineUploader; diff --git a/js/components/ascribe_forms/property.js b/js/components/ascribe_forms/property.js index 793be538..ac272988 100644 --- a/js/components/ascribe_forms/property.js +++ b/js/components/ascribe_forms/property.js @@ -181,9 +181,7 @@ let Property = React.createClass({ setErrors(errors){ this.setState({ - errors: errors.map((error) => { - return {error}; - }) + errors: errors.pop() }); }, @@ -255,8 +253,10 @@ let Property = React.createClass({ placement="top" overlay={tooltip}>
- {this.state.errors} - {this.props.label} +

+ {this.props.label} + {this.state.errors} +

{this.renderChildren(style)} {footer}
diff --git a/js/components/ascribe_media/media_player.js b/js/components/ascribe_media/media_player.js index a7e56fb7..2a23f2ed 100644 --- a/js/components/ascribe_media/media_player.js +++ b/js/components/ascribe_media/media_player.js @@ -3,12 +3,13 @@ import React from 'react'; import Q from 'q'; -import { escapeHTML } from '../../utils/general_utils'; - -import InjectInHeadMixin from '../../mixins/inject_in_head_mixin'; import Panel from 'react-bootstrap/lib/Panel'; import ProgressBar from 'react-bootstrap/lib/ProgressBar'; -import AppConstants from '../../constants/application_constants.js'; + +import AppConstants from '../../constants/application_constants'; + +import { escapeHTML } from '../../utils/general_utils'; +import { InjectInHeadUtils } from '../../utils/inject_utils'; /** * This is the component that implements display-specific functionality. @@ -50,25 +51,33 @@ let Other = React.createClass({ let Image = React.createClass({ propTypes: { - url: React.PropTypes.string.isRequired, + url: React.PropTypes.string, preview: React.PropTypes.string.isRequired }, - mixins: [InjectInHeadMixin], - componentDidMount() { - this.inject('https://code.jquery.com/jquery-2.1.4.min.js') - .then(() => - Q.all([ - this.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/shmui.css'), - this.inject(AppConstants.baseUrl + 'static/thirdparty/shmui/jquery.shmui.js') - ]).then(() => { window.jQuery('.shmui-ascribe').shmui(); })); + if(this.props.url) { + InjectInHeadUtils.inject(AppConstants.jquery.sdkUrl) + .then(() => + Q.all([ + InjectInHeadUtils.inject(AppConstants.shmui.cssUrl), + InjectInHeadUtils.inject(AppConstants.shmui.sdkUrl) + ]).then(() => { window.jQuery('.shmui-ascribe').shmui(); })); + } }, render() { - return ( - - ); + const { url, preview } = this.props; + + if(url) { + return ( + + ); + } else { + return ( + + ); + } } }); @@ -77,10 +86,8 @@ let Audio = React.createClass({ url: React.PropTypes.string.isRequired }, - mixins: [InjectInHeadMixin], - componentDidMount() { - this.inject(AppConstants.baseUrl + 'static/thirdparty/audiojs/audiojs/audio.min.js').then(this.ready); + InjectInHeadUtils.inject(AppConstants.audiojs.sdkUrl).then(this.ready); }, ready() { @@ -111,7 +118,7 @@ let Video = React.createClass({ * `false` if we failed to load the external library) * 2) render the cover using the `` 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 + * the `InjectInHeadUtils`, attaching a function to `Promise.then` to change * `state.libraryLoaded` to true * 4) when the promise is succesfully resolved, we change `state.libraryLoaded` triggering * a re-render @@ -129,20 +136,22 @@ let Video = React.createClass({ encodingStatus: React.PropTypes.number }, - mixins: [InjectInHeadMixin], - getInitialState() { 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')]) + InjectInHeadUtils.inject(AppConstants.videojs.cssUrl), + InjectInHeadUtils.inject(AppConstants.videojs.sdkUrl)]) .then(() => this.setState({libraryLoaded: true})) .fail(() => this.setState({libraryLoaded: false})); }, + shouldComponentUpdate(nextProps, nextState) { + return nextState.videoMounted === false; + }, + componentDidUpdate() { if (this.state.libraryLoaded && !this.state.videoMounted) { window.videojs('#mainvideo'); @@ -168,10 +177,6 @@ let Video = React.createClass({ return html.join('\n'); }, - shouldComponentUpdate(nextProps, nextState) { - return nextState.videoMounted === false; - }, - render() { if (this.state.libraryLoaded !== null) { return ( @@ -202,26 +207,50 @@ let MediaPlayer = React.createClass({ }, render() { - if (this.props.mimetype === 'video' && this.props.encodingStatus !== undefined && this.props.encodingStatus !== 100) { + const { mimetype, + preview, + url, + extraData, + encodingStatus } = this.props; + + if (mimetype === 'video' && encodingStatus !== undefined && encodingStatus !== 100) { return (

We successfully received your video and it is now being encoded.
You can leave this page and check back on the status later.

-
); } else { - let Component = resourceMap[this.props.mimetype] || Other; + let Component = resourceMap[mimetype] || Other; + let componentProps = { + preview, + url, + extraData, + encodingStatus + }; + + // Since the launch of the portfolio whitelabel submission, + // we allow the user to specify a thumbnail upon piece-registration. + // As the `Component` is chosen according to its filetype but could potentially + // have a manually submitted thumbnail, we match if the to `Mediaplayer` submitted thumbnail + // is not the generally used fallback `url` (ascribe_spiral.png). + // + // If this is the case, we disable shmui by deleting the original `url` prop and replace + // the assigned component to `Image`. + if(!decodeURIComponent(preview).match(/https:\/\/.*\/media\/thumbnails\/ascribe_spiral.png/) && + Component === Other) { + Component = resourceMap.image; + delete componentProps.url; + } + return (
- +
); } diff --git a/js/components/ascribe_settings/contract_settings_update_button.js b/js/components/ascribe_settings/contract_settings_update_button.js index f3bab156..aa13264a 100644 --- a/js/components/ascribe_settings/contract_settings_update_button.js +++ b/js/components/ascribe_settings/contract_settings_update_button.js @@ -20,8 +20,7 @@ import { getLangText } from '../../utils/lang_utils'; let ContractSettingsUpdateButton = React.createClass({ propTypes: { - contract: React.PropTypes.object, - location: React.PropTypes.object + contract: React.PropTypes.object }, submitFile(file) { @@ -56,7 +55,6 @@ let ContractSettingsUpdateButton = React.createClass({ render() { return ( + submitFile={this.submitFile} /> ); } }); -export default ContractSettingsUpdateButton; \ No newline at end of file +export default ContractSettingsUpdateButton; diff --git a/js/components/ascribe_social_share/facebook_share_button.js b/js/components/ascribe_social_share/facebook_share_button.js new file mode 100644 index 00000000..87a2aef6 --- /dev/null +++ b/js/components/ascribe_social_share/facebook_share_button.js @@ -0,0 +1,51 @@ +'use strict'; + +import React from 'react'; + +import AppConstants from '../../constants/application_constants'; + +import { InjectInHeadUtils } from '../../utils/inject_utils'; + +let FacebookShareButton = React.createClass({ + propTypes: { + url: React.PropTypes.string, + type: React.PropTypes.string + }, + + getDefaultProps() { + return { + type: 'button' + }; + }, + + componentDidMount() { + /** + * Ideally we would only use FB.XFBML.parse() on the component that we're + * mounting, but doing this when we first load the FB sdk causes unpredictable behaviour. + * The button sometimes doesn't get initialized, likely because FB hasn't properly + * been initialized yet. + * + * To circumvent this, we always have the sdk parse the entire DOM on the initial load + * (see FacebookHandler) and then use FB.XFBML.parse() on the mounting component later. + */ + if (!InjectInHeadUtils.isPresent('script', AppConstants.facebook.sdkUrl)) { + InjectInHeadUtils.inject(AppConstants.facebook.sdkUrl); + } else { + // Parse() searches the children of the element we give it, not the element itself. + FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parentElement); + } + }, + + render() { + return ( + + + ); + } +}); + +export default FacebookShareButton; diff --git a/js/components/ascribe_social_share/twitter_share_button.js b/js/components/ascribe_social_share/twitter_share_button.js new file mode 100644 index 00000000..b2e8a7dc --- /dev/null +++ b/js/components/ascribe_social_share/twitter_share_button.js @@ -0,0 +1,55 @@ +'use strict'; + +import React from 'react'; + +import AppConstants from '../../constants/application_constants'; + +import { InjectInHeadUtils } from '../../utils/inject_utils'; + +let TwitterShareButton = React.createClass({ + propTypes: { + count: React.PropTypes.string, + counturl: React.PropTypes.string, + hashtags: React.PropTypes.string, + size: React.PropTypes.string, + text: React.PropTypes.string, + url: React.PropTypes.string, + via: React.PropTypes.string + }, + + getDefaultProps() { + return { + count: 'none', + via: 'ascribeIO' + }; + }, + + componentDidMount() { + InjectInHeadUtils.inject(AppConstants.twitter.sdkUrl).then(this.loadTwitterButton); + }, + + loadTwitterButton() { + const { count, counturl, hashtags, size, text, url, via } = this.props; + + twttr.widgets.createShareButton(url, this.refs.twitterShareButton.getDOMNode(), { + count, + counturl, + hashtags, + size, + text, + via, + dnt: true // Do not track + }); + }, + + render() { + return ( + + + ); + } +}); + +export default TwitterShareButton; diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js index 38ec459a..0cc7ff5e 100644 --- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js +++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop.js @@ -27,6 +27,7 @@ let FileDragAndDrop = React.createClass({ areAssetsEditable: React.PropTypes.bool, enableLocalHashing: React.PropTypes.bool, + uploadMethod: React.PropTypes.string, // triggers a FileDragAndDrop-global spinner hashingProgress: React.PropTypes.number, @@ -41,8 +42,11 @@ let FileDragAndDrop = React.createClass({ plural: React.PropTypes.string }), - allowedExtensions: React.PropTypes.string, - location: React.PropTypes.object + allowedExtensions: React.PropTypes.string + }, + + clearSelection() { + this.refs.fileSelector.getDOMNode().value = ''; }, handleDragOver(event) { @@ -81,30 +85,30 @@ let FileDragAndDrop = React.createClass({ }, handleDeleteFile(fileId) { - // input's value is not change the second time someone + // input's value is not changed the second time someone // inputs the same file again, therefore we need to reset its value - this.refs.fileinput.getDOMNode().value = ''; + this.clearSelection(); this.props.handleDeleteFile(fileId); }, handleCancelFile(fileId) { - // input's value is not change the second time someone + // input's value is not changed the second time someone // inputs the same file again, therefore we need to reset its value - this.refs.fileinput.getDOMNode().value = ''; + this.clearSelection(); this.props.handleCancelFile(fileId); }, handlePauseFile(fileId) { - // input's value is not change the second time someone + // input's value is not changed the second time someone // inputs the same file again, therefore we need to reset its value - this.refs.fileinput.getDOMNode().value = ''; + this.clearSelection(); this.props.handlePauseFile(fileId); }, handleResumeFile(fileId) { - // input's value is not change the second time someone + // input's value is not changed the second time someone // inputs the same file again, therefore we need to reset its value - this.refs.fileinput.getDOMNode().value = ''; + this.clearSelection(); this.props.handleResumeFile(fileId); }, @@ -133,23 +137,23 @@ let FileDragAndDrop = React.createClass({ evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 20, false, false, false, false, 0, null); } - this.refs.fileinput.getDOMNode().dispatchEvent(evt); + this.refs.fileSelector.getDOMNode().dispatchEvent(evt); }, render: function () { - let { filesToUpload, - dropzoneInactive, - className, - hashingProgress, - handleCancelHashing, - multiple, - enableLocalHashing, - fileClassToUpload, - areAssetsDownloadable, - areAssetsEditable, - allowedExtensions, - location - } = this.props; + const { + filesToUpload, + dropzoneInactive, + className, + hashingProgress, + handleCancelHashing, + multiple, + enableLocalHashing, + uploadMethod, + fileClassToUpload, + areAssetsDownloadable, + areAssetsEditable, + allowedExtensions } = this.props; // has files only is true if there are files that do not have the status deleted or canceled let hasFiles = filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1).length > 0; @@ -185,8 +189,8 @@ let FileDragAndDrop = React.createClass({ hasFiles={hasFiles} onClick={this.handleOnClick} enableLocalHashing={enableLocalHashing} - fileClassToUpload={fileClassToUpload} - location={location}/> + uploadMethod={uploadMethod} + fileClassToUpload={fileClassToUpload} /> {getLangText('Drag %s here', fileClass)}

,

{getLangText('or')}

@@ -37,26 +35,31 @@ let FileDragAndDropDialog = React.createClass({ }, render() { - const queryParams = this.props.location.query; + const { + hasFiles, + multipleFiles, + enableLocalHashing, + uploadMethod, + fileClassToUpload, + onClick } = this.props; - if(this.props.hasFiles) { + if (hasFiles) { return null; } else { - if(this.props.enableLocalHashing && !queryParams.method) { + if (enableLocalHashing && !uploadMethod) { + const currentQueryParams = getCurrentQueryParams(); - let queryParamsHash = Object.assign({}, queryParams); + const queryParamsHash = Object.assign({}, currentQueryParams); queryParamsHash.method = 'hash'; - let queryParamsUpload = Object.assign({}, queryParams); + const queryParamsUpload = Object.assign({}, currentQueryParams); queryParamsUpload.method = 'upload'; - let { location } = this.props; - return (

{getLangText('Would you rather')}

{getLangText('Hash your work')} @@ -64,9 +67,9 @@ let FileDragAndDropDialog = React.createClass({ or - + {getLangText('Upload and hash your work')} @@ -75,26 +78,27 @@ let FileDragAndDropDialog = React.createClass({
); } else { - if(this.props.multipleFiles) { + if (multipleFiles) { return ( - {this.getDragDialog(this.props.fileClassToUpload.plural)} + {this.getDragDialog(fileClassToUpload.plural)} - {getLangText('choose %s to upload', this.props.fileClassToUpload.plural)} + onClick={onClick}> + {getLangText('choose %s to upload', fileClassToUpload.plural)} ); } else { - let dialog = queryParams.method === 'hash' ? getLangText('choose a %s to hash', this.props.fileClassToUpload.singular) : getLangText('choose a %s to upload', this.props.fileClassToUpload.singular); + const dialog = uploadMethod === 'hash' ? getLangText('choose a %s to hash', fileClassToUpload.singular) + : getLangText('choose a %s to upload', fileClassToUpload.singular); return ( - {this.getDragDialog(this.props.fileClassToUpload.singular)} + {this.getDragDialog(fileClassToUpload.singular)} + onClick={onClick}> {dialog} @@ -105,4 +109,4 @@ let FileDragAndDropDialog = React.createClass({ } }); -export default FileDragAndDropDialog; \ No newline at end of file +export default FileDragAndDropDialog; diff --git a/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js b/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js index 1547272e..252adabb 100644 --- a/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js +++ b/js/components/ascribe_uploader/ascribe_upload_button/upload_button.js @@ -2,24 +2,30 @@ import React from 'react'; +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; + import { displayValidProgressFilesFilter } from '../react_s3_fine_uploader_utils'; import { getLangText } from '../../../utils/lang_utils'; +import { truncateTextAtCharIndex } from '../../../utils/general_utils'; +const { func, array, bool, shape, string } = React.PropTypes; let UploadButton = React.createClass({ propTypes: { - onDrop: React.PropTypes.func.isRequired, - filesToUpload: React.PropTypes.array, - multiple: React.PropTypes.bool, + onDrop: func.isRequired, + filesToUpload: array, + multiple: bool, // For simplification purposes we're just going to use this prop as a // label for the upload button - fileClassToUpload: React.PropTypes.shape({ - singular: React.PropTypes.string, - plural: React.PropTypes.string + fileClassToUpload: shape({ + singular: string, + plural: string }), - allowedExtensions: React.PropTypes.string + allowedExtensions: string, + + handleCancelFile: func // provided by ReactS3FineUploader }, handleDrop(event) { @@ -37,11 +43,20 @@ let UploadButton = React.createClass({ return this.props.filesToUpload.filter((file) => file.status === 'uploading'); }, - handleOnClick() { - let uploadingFiles = this.getUploadingFiles(); + getUploadedFile() { + return this.props.filesToUpload.filter((file) => file.status === 'upload successful')[0]; + }, - // We only want the button to be clickable if there are no files currently uploading + handleOnClick() { + const uploadingFiles = this.getUploadingFiles(); + const uploadedFile = this.getUploadedFile(); + + if(uploadedFile) { + this.props.handleCancelFile(uploadedFile.id); + } if(uploadingFiles.length === 0) { + // We only want the button to be clickable if there are no files currently uploading + // 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 @@ -62,40 +77,61 @@ let UploadButton = React.createClass({ // filter invalid files that might have been deleted or canceled... filesToUpload = filesToUpload.filter(displayValidProgressFilesFilter); - // Depending on wether there is an upload going on or not we - // display the progress - if(filesToUpload.length > 0) { + if(this.getUploadingFiles().length !== 0) { return getLangText('Upload progress') + ': ' + Math.ceil(filesToUpload[0].progress) + '%'; } else { return fileClassToUpload.singular; } }, - render() { - let { - multiple, - fileClassToUpload, - allowedExtensions - } = this.props; + getUploadedFileLabel() { + const uploadedFile = this.getUploadedFile(); + if(uploadedFile) { + return ( + + + {' ' + truncateTextAtCharIndex(uploadedFile.name, 40)} + + ); + } else { + return ( + {getLangText('No file chosen')} + ); + } + }, + + render() { + let { multiple, + allowedExtensions } = this.props; + + /* + * We do not want a button that submits here. + * As UploadButton could be used in forms that want to be submitted independent + * of clicking the selector. + * Therefore the wrapping component needs to be an `anchor` tag instead of a `button` + */ return ( - +
+ + {this.getButtonLabel()} + + + {this.getUploadedFileLabel()} +
); } }); diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js index e8cc8bfa..bf4250c5 100644 --- a/js/components/ascribe_uploader/react_s3_fine_uploader.js +++ b/js/components/ascribe_uploader/react_s3_fine_uploader.js @@ -18,7 +18,6 @@ import { displayValidFilesFilter, transformAllowedExtensionsToInputAcceptProp } import { getCookie } from '../../utils/fetch_api_utils'; import { getLangText } from '../../utils/lang_utils'; - let ReactS3FineUploader = React.createClass({ propTypes: { keyRoutine: React.PropTypes.shape({ @@ -107,11 +106,14 @@ let ReactS3FineUploader = React.createClass({ // One solution we found in the process of tackling this problem was to hash // the file in the browser using md5 and then uploading the resulting text document instead // of the actual file. - // This boolean essentially enables that behavior + // + // This boolean and string essentially enable that behavior. + // Right now, we determine which upload method to use by appending a query parameter, + // which should be passed into 'uploadMethod': + // 'hash': upload using the hash + // 'upload': upload full file (default if not specified) enableLocalHashing: React.PropTypes.bool, - - // automatically injected by React-Router - query: React.PropTypes.object, + uploadMethod: React.PropTypes.oneOf(['hash', 'upload']), // A class of a file the user has to upload // Needs to be defined both in singular as well as in plural @@ -126,9 +128,7 @@ let ReactS3FineUploader = React.createClass({ fileInputElement: React.PropTypes.oneOfType([ React.PropTypes.func, React.PropTypes.element - ]), - - location: React.PropTypes.object + ]) }, getDefaultProps() { @@ -192,11 +192,11 @@ let ReactS3FineUploader = React.createClass({ filesToUpload: [], uploader: new fineUploader.s3.FineUploaderBasic(this.propsToConfig()), csrfToken: getCookie(AppConstants.csrftoken), - + // -1: aborted // -2: uninitialized hashingProgress: -2, - + // this is for logging chunks: {} }; @@ -259,7 +259,7 @@ let ReactS3FineUploader = React.createClass({ // Resets the whole react fineuploader component to its initial state reset() { // Cancel all currently ongoing uploads - this.state.uploader.cancelAll(); + this.cancelUploads(); // and reset component in general this.state.uploader.reset(); @@ -271,6 +271,22 @@ let ReactS3FineUploader = React.createClass({ this.setState(this.getInitialState()); }, + // Cancel uploads and clear previously selected files on the input element + cancelUploads(id) { + !!id ? this.state.uploader.cancel(id) : this.state.uploader.cancelAll(); + + // Reset the file input element to clear the previously selected files so that + // the user can reselect them again. + this.clearFileSelection(); + }, + + clearFileSelection() { + const { fileInput } = this.refs; + if (fileInput && typeof fileInput.clearSelection === 'function') { + fileInput.clearSelection(); + } + }, + requestKey(fileId) { let filename = this.state.uploader.getName(fileId); let uuid = this.state.uploader.getUuid(fileId); @@ -298,18 +314,27 @@ let ReactS3FineUploader = React.createClass({ resolve(res.key); }) .catch((err) => { - console.logGlobal(err, false, { - files: this.state.filesToUpload, - chunks: this.state.chunks - }); + this.onErrorPromiseProxy(err); reject(err); }); }); }, createBlob(file) { + const { createBlobRoutine } = this.props; + return Q.Promise((resolve, reject) => { - window.fetch(this.props.createBlobRoutine.url, { + + // if createBlobRoutine is not defined, + // we're progressing right away without posting to S3 + // so that this can be done manually by the form + if(!createBlobRoutine) { + // still we warn the user of this component + console.warn('createBlobRoutine was not defined for ReactS3FineUploader. Continuing without creating the blob on the server.'); + resolve(); + } + + window.fetch(createBlobRoutine.url, { method: 'post', headers: { 'Accept': 'application/json', @@ -320,7 +345,7 @@ let ReactS3FineUploader = React.createClass({ body: JSON.stringify({ 'filename': file.name, 'key': file.key, - 'piece_id': this.props.createBlobRoutine.pieceId + 'piece_id': createBlobRoutine.pieceId }) }) .then((res) => { @@ -336,16 +361,16 @@ let ReactS3FineUploader = React.createClass({ } else if(res.contractblob) { file.s3Url = res.contractblob.url_safe; file.s3UrlSafe = res.contractblob.url_safe; + } else if(res.thumbnail) { + file.s3Url = res.thumbnail.url_safe; + file.s3UrlSafe = res.thumbnail.url_safe; } else { throw new Error(getLangText('Could not find a url to download.')); } resolve(res); }) .catch((err) => { - console.logGlobal(err, false, { - files: this.state.filesToUpload, - chunks: this.state.chunks - }); + this.onErrorPromiseProxy(err); reject(err); }); }); @@ -354,7 +379,6 @@ let ReactS3FineUploader = React.createClass({ /* FineUploader specific callback function handlers */ onUploadChunk(id, name, chunkData) { - let chunks = this.state.chunks; chunks[id + '-' + chunkData.startByte + '-' + chunkData.endByte] = { @@ -370,10 +394,9 @@ let ReactS3FineUploader = React.createClass({ }, onUploadChunkSuccess(id, chunkData, responseJson, xhr) { - let chunks = this.state.chunks; let chunkKey = id + '-' + chunkData.startByte + '-' + chunkData.endByte; - + if(chunks[chunkKey]) { chunks[chunkKey].completed = true; chunks[chunkKey].responseJson = responseJson; @@ -387,13 +410,15 @@ let ReactS3FineUploader = React.createClass({ }, onComplete(id, name, res, xhr) { - // there has been an issue with the server's connection - if((xhr && xhr.status === 0) || res.error) { - console.logGlobal(new Error(res.error || 'Complete was called but there wasn\t a success'), false, { + // There has been an issue with the server's connection + if (xhr && xhr.status === 0 && res.success) { + console.logGlobal(new Error('Upload succeeded with a status code 0'), false, { files: this.state.filesToUpload, - chunks: this.state.chunks + chunks: this.state.chunks, + xhr: this.getXhrErrorComment(xhr) }); - } else { + // onError will catch any errors, so we can ignore them here + } else if (!res.error || res.success) { let files = this.state.filesToUpload; // Set the state of the completed file to 'upload successful' in order to @@ -412,9 +437,9 @@ let ReactS3FineUploader = React.createClass({ if(this.props.submitFile) { this.props.submitFile(files[id]); } else { - console.warn('You didn\'t define submitFile in as a prop in react-s3-fine-uploader'); + console.warn('You didn\'t define submitFile as a prop in react-s3-fine-uploader'); } - + // for explanation, check comment of if statement above if(this.props.isReadyForFormSubmission && this.props.setIsUploadReady) { // also, lets check if after the completion of this upload, @@ -429,28 +454,46 @@ let ReactS3FineUploader = React.createClass({ console.warn('You didn\'t define the functions isReadyForFormSubmission and/or setIsUploadReady in as a prop in react-s3-fine-uploader'); } }) - .catch((err) => { - console.logGlobal(err, false, { - files: this.state.filesToUpload, - chunks: this.state.chunks - }); - let notification = new GlobalNotificationModel(err.message, 'danger', 5000); - GlobalNotificationActions.appendGlobalNotification(notification); - }); + .catch(this.onErrorPromiseProxy); } }, - onError(id, name, errorReason) { + /** + * We want to channel all errors in this component through one single method. + * As fineuploader's `onError` method cannot handle the callback parameters of + * a promise we define this proxy method to crunch them into the correct form. + * + * @param {error} err a plain Javascript error + */ + onErrorPromiseProxy(err) { + this.onError(null, null, err.message); + }, + + onError(id, name, errorReason, xhr) { console.logGlobal(errorReason, false, { files: this.state.filesToUpload, - chunks: this.state.chunks + chunks: this.state.chunks, + xhr: this.getXhrErrorComment(xhr) }); - this.state.uploader.cancelAll(); + + this.props.setIsUploadReady(true); + this.cancelUploads(); let notification = new GlobalNotificationModel(errorReason || this.props.defaultErrorMessage, 'danger', 5000); GlobalNotificationActions.appendGlobalNotification(notification); }, + getXhrErrorComment(xhr) { + if (xhr) { + return { + response: xhr.response, + url: xhr.responseURL, + status: xhr.status, + statusText: xhr.statusText + }; + } + }, + isFileValid(file) { if(file.size > this.props.validation.sizeLimit) { @@ -588,7 +631,7 @@ let ReactS3FineUploader = React.createClass({ }, handleCancelFile(fileId) { - this.state.uploader.cancel(fileId); + this.cancelUploads(fileId); }, handlePauseFile(fileId) { @@ -597,7 +640,6 @@ let ReactS3FineUploader = React.createClass({ } else { throw new Error(getLangText('File upload could not be paused.')); } - }, handleResumeFile(fileId) { @@ -609,9 +651,14 @@ let ReactS3FineUploader = React.createClass({ }, handleUploadFile(files) { + // While files are being uploaded, the form cannot be ready + // for submission + this.props.setIsUploadReady(false); + // If multiple set and user already uploaded its work, // cancel upload if(!this.props.multiple && this.state.filesToUpload.filter(displayValidFilesFilter).length > 0) { + this.clearFileSelection(); return; } @@ -647,16 +694,14 @@ let ReactS3FineUploader = React.createClass({ // md5 hash of a file locally and just upload a txt file containing that hash. // // 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.props.location.query; - if(this.props.enableLocalHashing && queryParams && queryParams.method === 'hash') { - - let convertedFilePromises = []; + // as when the correct method prop is present ('hash' and not 'upload') + if (this.props.enableLocalHashing && this.props.uploadMethod === 'hash') { + const convertedFilePromises = []; let overallFileSize = 0; + // "files" is not a classical Javascript array but a Javascript FileList, therefore // we can not use map to convert values for(let i = 0; i < files.length; i++) { - // for calculating the overall progress of all submitted files // we'll need to calculate the overall sum of all files' sizes overallFileSize += files[i].size; @@ -668,7 +713,6 @@ let ReactS3FineUploader = React.createClass({ // we're using promises to handle that let hashedFilePromise = computeHashOfFile(files[i]); convertedFilePromises.push(hashedFilePromise); - } // To react after the computation of all files, we define the resolvement @@ -676,7 +720,6 @@ let ReactS3FineUploader = React.createClass({ // with their txt representative Q.all(convertedFilePromises) .progress(({index, value: {progress, reject}}) => { - // hashing progress has been aborted from outside // To get out of the executing, we need to call reject from the // inside of the promise's execution. @@ -696,18 +739,14 @@ let ReactS3FineUploader = React.createClass({ // currently hashing files let overallHashingProgress = 0; for(let i = 0; i < files.length; i++) { - let filesSliceOfOverall = files[i].size / overallFileSize; overallHashingProgress += filesSliceOfOverall * files[i].progress; - } // Multiply by 100, since react-progressbar expects decimal numbers this.setState({ hashingProgress: overallHashingProgress * 100}); - }) .then((convertedFiles) => { - // clear hashing progress, since its done this.setState({ hashingProgress: -2}); @@ -823,20 +862,18 @@ let ReactS3FineUploader = React.createClass({ changeSet.status = { $set: status }; let filesToUpload = React.addons.update(this.state.filesToUpload, { [fileId]: changeSet }); - + this.setState({ filesToUpload }); }, isDropzoneInactive() { - let filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1); - let queryParams = this.props.location.query; + const filesToDisplay = this.state.filesToUpload.filter((file) => file.status !== 'deleted' && file.status !== 'canceled' && file.size !== -1); - if((this.props.enableLocalHashing && !queryParams.method) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) { + if ((this.props.enableLocalHashing && !this.props.uploadMethod) || !this.props.areAssetsEditable || !this.props.multiple && filesToDisplay.length > 0) { return true; } else { return false; } - }, getAllowedExtensions() { @@ -850,28 +887,24 @@ let ReactS3FineUploader = React.createClass({ }, render() { - let { + const { multiple, areAssetsDownloadable, areAssetsEditable, onInactive, enableLocalHashing, fileClassToUpload, - validation, - fileInputElement, - location - } = this.props; + fileInputElement: FileInputElement, + uploadMethod } = 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, { + const props = { multiple, areAssetsDownloadable, areAssetsEditable, onInactive, enableLocalHashing, + uploadMethod, fileClassToUpload, - location, onDrop: this.handleUploadFile, filesToUpload: this.state.filesToUpload, handleDeleteFile: this.handleDeleteFile, @@ -882,10 +915,14 @@ let ReactS3FineUploader = React.createClass({ dropzoneInactive: this.isDropzoneInactive(), hashingProgress: this.state.hashingProgress, allowedExtensions: this.getAllowedExtensions() - }); - } + }; + return ( + + ); + } }); - export default ReactS3FineUploader; diff --git a/js/components/ascribe_uploader/vendor/s3.fine-uploader.js b/js/components/ascribe_uploader/vendor/s3.fine-uploader.js index 5b90cf5a..b939d655 100644 --- a/js/components/ascribe_uploader/vendor/s3.fine-uploader.js +++ b/js/components/ascribe_uploader/vendor/s3.fine-uploader.js @@ -4399,7 +4399,9 @@ qq.UploadHandlerController = function(o, namespace) { } ) .done(function() { - handler.clearXhr(id, chunkIdx); + if (handler._getFileState(id)) { + handler.clearXhr(id, chunkIdx); + } }) ; } } @@ -8681,7 +8683,7 @@ qq.s3.RequestSigner = function(o) { options.log(errorMessage, "error"); } - promise.failure(errorMessage); + promise.failure(errorMessage, xhrOrXdr); } else { promise.success(response); @@ -8813,7 +8815,7 @@ qq.s3.RequestSigner = function(o) { credentialsProvider.get().accessKey, credentialsProvider.get().sessionToken); }, function(errorMsg) { - options.log("Attempt to update expired credentials apparently failed! Unable to sign request. ", "error"); + options.log("Attempt to update expired credentials apparently failed! Unable to sign request: " + errorMsg, "error"); signatureEffort.failure("Unable to sign request - expired credentials."); }); } @@ -9627,8 +9629,8 @@ qq.s3.XhrUploadHandler = function(spec, proxy) { }); xhr.send(chunkData.blob); - }, function() { - promise.failure({error: "Problem signing the chunk!"}, xhr); + }, function(errorMsg, xhr) { + promise.failure({error: "Problem signing the chunk: " + errorMsg}, xhr); }); return promise; @@ -9672,10 +9674,10 @@ qq.s3.XhrUploadHandler = function(spec, proxy) { uploadIdPromise.success(uploadId); promise.success(uploadId); }, - function(errorMsg) { + function(errorMsg, xhr) { handler._getPersistableData(id).uploadId = null; - promise.failure(errorMsg); - uploadIdPromise.failure(errorMsg); + promise.failure(errorMsg, xhr); + uploadIdPromise.failure(errorMsg, xhr); } ); } diff --git a/js/components/register_piece.js b/js/components/register_piece.js index f127c149..43ac7bb7 100644 --- a/js/components/register_piece.js +++ b/js/components/register_piece.js @@ -40,12 +40,6 @@ let RegisterPiece = React.createClass( { mixins: [History], - getDefaultProps() { - return { - canSpecifyEditions: true - }; - }, - getInitialState(){ return mergeOptions( UserStore.getState(), diff --git a/js/components/whitelabel/prize/actions/prize_actions.js b/js/components/whitelabel/prize/actions/prize_actions.js deleted file mode 100644 index fcd9e91e..00000000 --- a/js/components/whitelabel/prize/actions/prize_actions.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -import { alt } from '../../../../alt'; -import Q from 'q'; - -import PrizeFetcher from '../fetchers/prize_fetcher'; - -class PrizeActions { - constructor() { - this.generateActions( - 'updatePrize' - ); - } - - fetchPrize() { - return Q.Promise((resolve, reject) => { - PrizeFetcher - .fetch() - .then((res) => { - this.actions.updatePrize({ - prize: res.prize - }); - resolve(res); - }) - .catch((err) => { - console.logGlobal(err); - reject(err); - }); - }); - } -} - -export default alt.createActions(PrizeActions); \ No newline at end of file diff --git a/js/components/whitelabel/prize/components/prize_register_piece.js b/js/components/whitelabel/prize/components/prize_register_piece.js deleted file mode 100644 index 87a23591..00000000 --- a/js/components/whitelabel/prize/components/prize_register_piece.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict'; - -import React from 'react'; - -import PrizeActions from '../actions/prize_actions'; -import PrizeStore from '../stores/prize_store'; - -import RegisterPiece from '../../../register_piece'; -import Property from '../../../ascribe_forms/property'; -import InputTextAreaToggable from '../../../ascribe_forms/input_textarea_toggable'; -import InputCheckbox from '../../../ascribe_forms/input_checkbox'; - -import { getLangText } from '../../../../utils/lang_utils'; -import { setDocumentTitle } from '../../../../utils/dom_utils'; - - -let PrizeRegisterPiece = React.createClass({ - getInitialState() { - return PrizeStore.getState(); - }, - - componentDidMount() { - PrizeStore.listen(this.onChange); - PrizeActions.fetchPrize(); - }, - - componentWillUnmount() { - PrizeStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - render() { - setDocumentTitle(getLangText('Submit to the prize')); - - if(this.state.prize && this.state.prize.active){ - return ( - - - - - - - - - - - {' ' + getLangText('I agree to the Terms of Service the art price') + ' '} - ( - {getLangText('read')} - ) - - - - ); - } - else { - return ( -
-
- {getLangText('The prize is no longer active')} -
-
- ); - } - } -}); - -export default PrizeRegisterPiece; diff --git a/js/components/whitelabel/prize/constants/prize_api_urls.js b/js/components/whitelabel/prize/constants/prize_api_urls.js index 2d35cf19..cb4e2e44 100644 --- a/js/components/whitelabel/prize/constants/prize_api_urls.js +++ b/js/components/whitelabel/prize/constants/prize_api_urls.js @@ -2,6 +2,7 @@ import AppPrizeConstants from './prize_application_constants'; + function getPrizeApiUrls(subdomain) { return { 'users_login': AppPrizeConstants.prizeApiEndpoint + subdomain + '/users/login/', @@ -18,10 +19,9 @@ function getPrizeApiUrls(subdomain) { 'ratings': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/', 'rating': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/${piece_id}/', 'rating_average': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/${piece_id}/average/', - 'select_piece' : AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/${piece_id}/select/', + 'select_piece': AppPrizeConstants.prizeApiEndpoint + subdomain + '/ratings/${piece_id}/select/', 'notes': AppPrizeConstants.prizeApiEndpoint + subdomain + '/notes/', 'note': AppPrizeConstants.prizeApiEndpoint + subdomain + '/notes/${piece_id}/' - }; } diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js new file mode 100644 index 00000000..0c293b15 --- /dev/null +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_forms/pr_register_piece_form.js @@ -0,0 +1,375 @@ +'use strict'; + +import React from 'react'; +import { History } from 'react-router'; + +import Form from '../../../../../ascribe_forms/form'; +import Property from '../../../../../ascribe_forms/property'; +import InputTextAreaToggable from '../../../../../ascribe_forms/input_textarea_toggable'; + +import UploadButton from '../../../../../ascribe_uploader/ascribe_upload_button/upload_button'; +import InputFineuploader from '../../../../../ascribe_forms/input_fineuploader'; +import AscribeSpinner from '../../../../../ascribe_spinner'; + +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; + +import AppConstants from '../../../../../../constants/application_constants'; +import ApiUrls from '../../../../../../constants/api_urls'; + +import requests from '../../../../../../utils/requests'; + +import { getLangText } from '../../../../../../utils/lang_utils'; +import { setCookie } from '../../../../../../utils/fetch_api_utils'; +import { formSubmissionValidation } from '../../../../../ascribe_uploader/react_s3_fine_uploader_utils'; + + +const { object } = React.PropTypes; + +const PRRegisterPieceForm = React.createClass({ + propTypes: { + location: object, + history: object, + currentUser: object + }, + + mixins: [History], + + getInitialState(){ + return { + digitalWorkKeyReady: true, + thumbnailKeyReady: true, + + // we set this to true, as it is not required + supportingMaterialsReady: true, + proofOfPaymentReady: true, + piece: null, + submitted: false + }; + }, + + /** + * In this method, we're composing all fields on the page + * in two steps, first submitting the registration of the piece and + * second adding all the additional details + */ + submit() { + if(!this.validateForms()) { + return; + } else { + // disable the submission button right after the user + // clicks on it to avoid double submission + this.setState({ + submitted: true + }); + } + + const { currentUser } = this.props; + const { registerPieceForm, + additionalDataForm, + uploadersForm } = this.refs; + const { digitalWorkKey, + thumbnailKey, + supportingMaterials, + proofOfPayment } = uploadersForm.refs; + const additionalDataFormData = additionalDataForm.getFormData(); + + // composing data for piece registration + let registerPieceFormData = registerPieceForm.getFormData(); + registerPieceFormData.digital_work_key = digitalWorkKey.state.value; + registerPieceFormData.thumbnail_file = thumbnailKey.state.value; + registerPieceFormData.terms = true; + + // submitting the piece + requests + .post(ApiUrls.pieces_list, { body: registerPieceFormData }) + .then(({ success, piece, notification }) => { + if(success) { + this.setState({ + piece + }, () => { + supportingMaterials.refs.input.createBlobRoutine(); + proofOfPayment.refs.input.createBlobRoutine(); + }); + + setCookie(currentUser.email, piece.id); + + return requests.post(ApiUrls.piece_extradata, { + body: { + extradata: additionalDataFormData, + piece_id: piece.id + }, + piece_id: piece.id + }); + } else { + const notificationMessage = new GlobalNotificationModel(notification, 'danger', 5000); + GlobalNotificationActions.appendGlobalNotification(notificationMessage); + } + }) + .then(() => this.history.pushState(null, `/pieces/${this.state.piece.id}`)) + .catch(() => { + const notificationMessage = new GlobalNotificationModel(getLangText("Ups! We weren't able to send your submission. Contact: support@ascribe.io"), 'danger', 5000); + GlobalNotificationActions.appendGlobalNotification(notificationMessage); + }); + }, + + validateForms() { + const { registerPieceForm, + additionalDataForm, + uploadersForm } = this.refs; + + const registerPieceFormValidation = registerPieceForm.validate(); + const additionalDataFormValidation = additionalDataForm.validate(); + const uploaderFormValidation = uploadersForm.validate(); + + return registerPieceFormValidation && additionalDataFormValidation && uploaderFormValidation; + }, + + getCreateBlobRoutine() { + const { piece } = this.state; + + if(piece && piece.id) { + return { + url: ApiUrls.blob_otherdatas, + pieceId: piece.id + }; + } else { + return null; + } + }, + + /** + * This method is overloaded so that we can track the ready-state + * of each uploader in the component + * @param {string} uploaderKey Name of the uploader's key to track + */ + setIsUploadReady(uploaderKey) { + return (isUploadReady) => { + this.setState({ + [uploaderKey]: isUploadReady + }); + }; + }, + + getSubmitButton() { + const { digitalWorkKeyReady, + thumbnailKeyReady, + supportingMaterialsReady, + proofOfPaymentReady, + submitted } = this.state; + + if(submitted) { + return ( + + + + ); + } else { + return ( + + ); + } + }, + + render() { + const { location } = this.props; + + return ( +
+
+ + + + + + + + + + + + +
+
+ + + + + + + + + +
+
+ + + + + + + + + + + + +
+
+ + + {getLangText('By submitting this form, you agree to the') + ' '} + + {getLangText('Terms of Service')} + + {' of Portfolio Review.'} + + +
+ {this.getSubmitButton()} +
+ ); + } +}); + +export default PRRegisterPieceForm; \ No newline at end of file diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_hero.js b/js/components/whitelabel/prize/portfolioreview/components/pr_hero.js new file mode 100644 index 00000000..a0fa0811 --- /dev/null +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_hero.js @@ -0,0 +1,42 @@ +'use strict'; + +import React from 'react'; + +import UserStore from '../../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; + +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; + + +const PRHero = React.createClass({ + getInitialState() { + return UserStore.getState(); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + const { currentUser } = this.state; + + return ( +
+

Congratulations {currentUser.email}!

+

You have successfully submitted to Portfolio Review 2016

+

See below, your uploaded portfolio:

+
+ ); + } +}); + +export default PRHero; \ No newline at end of file diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js b/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js new file mode 100644 index 00000000..cdada68b --- /dev/null +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_landing.js @@ -0,0 +1,128 @@ +'use strict'; + +import React from 'react'; +import { History } from 'react-router'; + +import PrizeActions from '../../simple_prize/actions/prize_actions'; +import PrizeStore from '../../simple_prize/stores/prize_store'; + +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'; + +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; + + +const PRLanding = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + mixins: [History], + + getInitialState() { + return mergeOptions( + PrizeStore.getState(), + UserStore.getState() + ); + }, + + componentDidMount() { + const { location } = this.props; + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + PrizeStore.listen(this.onChange); + PrizeActions.fetchPrize(); + + if(location && location.query && location.query.redirect) { + let queryCopy = JSON.parse(JSON.stringify(location.query)); + delete queryCopy.redirect; + window.setTimeout(() => this.history.replaceState(null, `/${location.query.redirect}`, queryCopy)); + } + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + PrizeStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + getButtons() { + if (this.state.prize && this.state.prize.active){ + return ( + + + + + +

+ {getLangText('or, already an ascribe user?')} +

+ + + +
+ ); + } + return ( + + + {getLangText('Sign up to ascribe')} + + +

+ {getLangText('or, already an ascribe user?')} +

+ + + +
+ ); + }, + + getTitle() { + if (this.state.prize && this.state.prize.active){ + return ( +

+ {getLangText('This is the submission page for Portfolio Review 2016.')} +

+ ); + } + return ( +

+ {getLangText('Submissions for Portfolio Review 2016 are now closed.')} +

+ ); + }, + render() { + return ( +
+
+
+

+ {getLangText('Welcome to Portfolio Review 2016')} +

+ {this.getTitle()} + {this.getButtons()} +
+
+
+ ); + } +}); + +export default PRLanding; \ No newline at end of file diff --git a/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js b/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js new file mode 100644 index 00000000..a2a70a97 --- /dev/null +++ b/js/components/whitelabel/prize/portfolioreview/components/pr_register_piece.js @@ -0,0 +1,82 @@ +'use strict'; + +import React from 'react'; +import { Link, History } from 'react-router'; + +import Col from 'react-bootstrap/lib/Col'; +import Row from 'react-bootstrap/lib/Row'; + +import UserStore from '../../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; + +import PRRegisterPieceForm from './pr_forms/pr_register_piece_form'; + +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; +import { getCookie } from '../../../../../utils/fetch_api_utils'; + + +const { object } = React.PropTypes; + +const PRRegisterPiece = React.createClass({ + propTypes: { + location: object + }, + + mixins: [History], + + getInitialState() { + return UserStore.getState(); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + componentDidUpdate() { + const { currentUser } = this.state; + if(currentUser && currentUser.email) { + const submittedPieceId = getCookie(currentUser.email); + if(submittedPieceId) { + this.history.pushState(null, `/pieces/${submittedPieceId}`); + } + } + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + const { currentUser } = this.state; + const { location } = this.props; + + setDocumentTitle(getLangText('Submit to Portfolio Review')); + return ( + + +
+

Portfolio Review

+

{getLangText('Submission closing on %s', ' 22 Dec 2015')}

+

+ {getLangText("You're submitting as %s. ", currentUser.email)} + {getLangText('Change account?')} +

+
+ + + + +
+ ); + } +}); + +export default PRRegisterPiece; \ No newline at end of file diff --git a/js/components/whitelabel/prize/portfolioreview/pr_app.js b/js/components/whitelabel/prize/portfolioreview/pr_app.js new file mode 100644 index 00000000..7e66c43c --- /dev/null +++ b/js/components/whitelabel/prize/portfolioreview/pr_app.js @@ -0,0 +1,65 @@ +'use strict'; + +import React from 'react'; +import GlobalNotification from '../../../global_notification'; + +import Hero from './components/pr_hero'; + +import UserStore from '../../../../stores/user_store'; +import UserActions from '../../../../actions/user_actions'; + +import { getSubdomain } from '../../../../utils/general_utils'; +import { getCookie } from '../../../../utils/fetch_api_utils'; + + +let PRApp = React.createClass({ + 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) + }, + + getInitialState() { + return UserStore.getState(); + }, + + componentDidMount() { + UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); + }, + + componentWillUnmount() { + UserStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + const { history, children } = this.props; + const { currentUser } = this.state; + let subdomain = getSubdomain(); + let header; + + if (currentUser && currentUser.email && history.isActive(`/pieces/${getCookie(currentUser.email)}`)) { + header = ; + } + + return ( +
+ {header} +
+ {children} + + +
+
+ ); + } +}); + +export default PRApp; diff --git a/js/components/whitelabel/prize/prize_routes.js b/js/components/whitelabel/prize/prize_routes.js index 14d8b4d9..1f2c6374 100644 --- a/js/components/whitelabel/prize/prize_routes.js +++ b/js/components/whitelabel/prize/prize_routes.js @@ -3,58 +3,94 @@ import React from 'react'; import { Route, IndexRoute } from 'react-router'; -import Landing from './components/prize_landing'; -import LoginContainer from './components/prize_login_container'; -import LogoutContainer from '../../../components/logout_container'; -import SignupContainer from './components/prize_signup_container'; -import PasswordResetContainer from '../../../components/password_reset_container'; -import PrizeRegisterPiece from './components/prize_register_piece'; -import PrizePieceList from './components/prize_piece_list'; -import PrizePieceContainer from './components/ascribe_detail/prize_piece_container'; -import EditionContainer from '../../ascribe_detail/edition_container'; -import SettingsContainer from './components/prize_settings_container'; -import CoaVerifyContainer from '../../../components/coa_verify_container'; -import ErrorNotFoundPage from '../../../components/error_not_found_page'; +import SPLanding from './simple_prize/components/prize_landing'; +import SPLoginContainer from './simple_prize/components/prize_login_container'; +import SPSignupContainer from './simple_prize/components/prize_signup_container'; +import SPRegisterPiece from './simple_prize/components/prize_register_piece'; +import SPPieceList from './simple_prize/components/prize_piece_list'; +import SPPieceContainer from './simple_prize/components/ascribe_detail/prize_piece_container'; +import SPSettingsContainer from './simple_prize/components/prize_settings_container'; +import SPApp from './simple_prize/prize_app'; -import App from './prize_app'; +import PRApp from './portfolioreview/pr_app'; +import PRLanding from './portfolioreview/components/pr_landing'; +import PRRegisterPiece from './portfolioreview/components/pr_register_piece'; + +import EditionContainer from '../../ascribe_detail/edition_container'; +import LogoutContainer from '../../logout_container'; +import PasswordResetContainer from '../../password_reset_container'; +import CoaVerifyContainer from '../../coa_verify_container'; +import ErrorNotFoundPage from '../../error_not_found_page'; import AuthProxyHandler from '../../../components/ascribe_routes/proxy_routes/auth_proxy_handler'; -function getRoutes() { - return ( - - +const ROUTES = { + sluice: ( + + + component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SPLoginContainer)} /> + component={AuthProxyHandler({to: '/collection', when: 'loggedIn'})(SPSignupContainer)} /> + component={AuthProxyHandler({to: '/login', when: 'loggedOut'})(SPSettingsContainer)}/> - + - ); + ), + portfolioreview: ( + + + + + + + + + + + ) +}; + + +function getRoutes(commonRoutes, subdomain) { + if(subdomain in ROUTES) { + return ROUTES[subdomain]; + } else { + throw new Error('Subdomain wasn\'t specified in the wallet app.'); + } } diff --git a/js/components/whitelabel/prize/simple_prize/actions/prize_actions.js b/js/components/whitelabel/prize/simple_prize/actions/prize_actions.js new file mode 100644 index 00000000..dbca1b5d --- /dev/null +++ b/js/components/whitelabel/prize/simple_prize/actions/prize_actions.js @@ -0,0 +1,28 @@ +'use strict'; + +import { alt } from '../../../../../alt'; + +import PrizeFetcher from '../fetchers/prize_fetcher'; + +class PrizeActions { + constructor() { + this.generateActions( + 'updatePrize' + ); + } + + fetchPrize() { + PrizeFetcher + .fetch() + .then((res) => { + this.actions.updatePrize({ + prize: res.prize + }); + }) + .catch((err) => { + console.logGlobal(err); + }); + } +} + +export default alt.createActions(PrizeActions); \ No newline at end of file diff --git a/js/components/whitelabel/prize/actions/prize_jury_actions.js b/js/components/whitelabel/prize/simple_prize/actions/prize_jury_actions.js similarity index 97% rename from js/components/whitelabel/prize/actions/prize_jury_actions.js rename to js/components/whitelabel/prize/simple_prize/actions/prize_jury_actions.js index 9bd03f59..24cf08e8 100644 --- a/js/components/whitelabel/prize/actions/prize_jury_actions.js +++ b/js/components/whitelabel/prize/simple_prize/actions/prize_jury_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import { alt } from '../../../../alt'; +import { alt } from '../../../../../alt'; import Q from 'q'; import PrizeJuryFetcher from '../fetchers/prize_jury_fetcher'; diff --git a/js/components/whitelabel/prize/actions/prize_rating_actions.js b/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js similarity index 98% rename from js/components/whitelabel/prize/actions/prize_rating_actions.js rename to js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js index 184d84e7..68b5334b 100644 --- a/js/components/whitelabel/prize/actions/prize_rating_actions.js +++ b/js/components/whitelabel/prize/simple_prize/actions/prize_rating_actions.js @@ -1,6 +1,6 @@ 'use strict'; -import { alt } from '../../../../alt'; +import { alt } from '../../../../../alt'; import Q from 'q'; import PrizeRatingFetcher from '../fetchers/prize_rating_fetcher'; diff --git a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js b/js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js similarity index 87% rename from js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js rename to js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js index caef504b..965b9012 100644 --- a/js/components/whitelabel/prize/components/ascribe_accordion_list/accordion_list_item_prize.js +++ b/js/components/whitelabel/prize/simple_prize/components/ascribe_accordion_list/accordion_list_item_prize.js @@ -3,26 +3,27 @@ import React from 'react'; import { Link } from 'react-router'; import StarRating from 'react-star-rating'; +import Moment from 'moment'; -import PieceListActions from '../../../../../actions/piece_list_actions'; -import PieceListStore from '../../../../../stores/piece_list_store'; +import PieceListActions from '../../../../../../actions/piece_list_actions'; +import PieceListStore from '../../../../../../stores/piece_list_store'; import PrizeRatingActions from '../../actions/prize_rating_actions'; -import UserStore from '../../../../../stores/user_store'; +import UserStore from '../../../../../../stores/user_store'; -import InputCheckbox from '../../../../ascribe_forms/input_checkbox'; +import InputCheckbox from '../../../../../ascribe_forms/input_checkbox'; -import AccordionListItemPiece from '../../../../ascribe_accordion_list/accordion_list_item_piece'; +import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece'; -import GlobalNotificationModel from '../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; -import AclProxy from '../../../../acl_proxy'; +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'; +import { getLangText } from '../../../../../../utils/lang_utils'; +import { mergeOptions } from '../../../../../../utils/general_utils'; let AccordionListItemPrize = React.createClass({ @@ -182,7 +183,7 @@ let AccordionListItemPrize = React.createClass({ artistName={artistName} subsubheading={
- {new Date(this.props.content.date_created).getFullYear()} + {Moment(this.props.content.date_created, 'YYYY-MM-DD').year()}
} buttons={this.getPrizeButtons()} badge={this.getPrizeBadge()}> diff --git a/js/components/whitelabel/prize/components/ascribe_buttons/submit_to_prize_button.js b/js/components/whitelabel/prize/simple_prize/components/ascribe_buttons/submit_to_prize_button.js similarity index 86% rename from js/components/whitelabel/prize/components/ascribe_buttons/submit_to_prize_button.js rename to js/components/whitelabel/prize/simple_prize/components/ascribe_buttons/submit_to_prize_button.js index 409b8aa1..8ceb87ea 100644 --- a/js/components/whitelabel/prize/components/ascribe_buttons/submit_to_prize_button.js +++ b/js/components/whitelabel/prize/simple_prize/components/ascribe_buttons/submit_to_prize_button.js @@ -3,10 +3,10 @@ import React from 'react'; import classNames from 'classnames'; -import ModalWrapper from '../../../../ascribe_modal/modal_wrapper'; -import PieceSubmitToPrizeForm from '../../../../ascribe_forms/form_submit_to_prize'; +import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; +import PieceSubmitToPrizeForm from '../../../../../ascribe_forms/form_submit_to_prize'; -import { getLangText } from '../../../../../utils/lang_utils'; +import { getLangText } from '../../../../../../utils/lang_utils'; let SubmitToPrizeButton = React.createClass({ propTypes: { diff --git a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js b/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js similarity index 82% rename from js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js rename to js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js index f2e22412..93ca50f3 100644 --- a/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js +++ b/js/components/whitelabel/prize/simple_prize/components/ascribe_detail/prize_piece_container.js @@ -6,41 +6,44 @@ import Moment from 'moment'; import StarRating from 'react-star-rating'; -import PieceActions from '../../../../../actions/piece_actions'; -import PieceStore from '../../../../../stores/piece_store'; +import PieceActions from '../../../../../../actions/piece_actions'; +import PieceStore from '../../../../../../stores/piece_store'; -import PieceListStore from '../../../../../stores/piece_list_store'; -import PieceListActions from '../../../../../actions/piece_list_actions'; +import PieceListStore from '../../../../../../stores/piece_list_store'; +import PieceListActions from '../../../../../../actions/piece_list_actions'; import PrizeRatingActions from '../../actions/prize_rating_actions'; import PrizeRatingStore from '../../stores/prize_rating_store'; -import UserStore from '../../../../../stores/user_store'; +import UserStore from '../../../../../../stores/user_store'; +import UserActions from '../../../../../../actions/user_actions'; -import Piece from '../../../../../components/ascribe_detail/piece'; -import Note from '../../../../../components/ascribe_detail/note'; +import Piece from '../../../../../../components/ascribe_detail/piece'; +import Note from '../../../../../../components/ascribe_detail/note'; -import AscribeSpinner from '../../../../ascribe_spinner'; +import AscribeSpinner from '../../../../../ascribe_spinner'; -import Form from '../../../../../components/ascribe_forms/form'; -import Property from '../../../../../components/ascribe_forms/property'; -import InputTextAreaToggable from '../../../../../components/ascribe_forms/input_textarea_toggable'; -import CollapsibleParagraph from '../../../../../components/ascribe_collapsible/collapsible_paragraph'; +import Form from '../../../../../../components/ascribe_forms/form'; +import Property from '../../../../../../components/ascribe_forms/property'; +import InputTextAreaToggable from '../../../../../../components/ascribe_forms/input_textarea_toggable'; +import CollapsibleParagraph from '../../../../../../components/ascribe_collapsible/collapsible_paragraph'; -import InputCheckbox from '../../../../ascribe_forms/input_checkbox'; -import LoanForm from '../../../../ascribe_forms/form_loan'; -import ListRequestActions from '../../../../ascribe_forms/list_form_request_actions'; -import ModalWrapper from '../../../../ascribe_modal/modal_wrapper'; +import FurtherDetailsFileuploader from '../../../../../ascribe_detail/further_details_fileuploader'; -import GlobalNotificationModel from '../../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; +import InputCheckbox from '../../../../../ascribe_forms/input_checkbox'; +import LoanForm from '../../../../../ascribe_forms/form_loan'; +import ListRequestActions from '../../../../../ascribe_forms/list_form_request_actions'; +import ModalWrapper from '../../../../../ascribe_modal/modal_wrapper'; -import DetailProperty from '../../../../ascribe_detail/detail_property'; +import GlobalNotificationModel from '../../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../../actions/global_notification_actions'; -import ApiUrls from '../../../../../constants/api_urls'; -import { mergeOptions } from '../../../../../utils/general_utils'; -import { getLangText } from '../../../../../utils/lang_utils'; -import { setDocumentTitle } from '../../../../../utils/dom_utils'; +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'; /** @@ -48,7 +51,8 @@ import { setDocumentTitle } from '../../../../../utils/dom_utils'; */ let PieceContainer = React.createClass({ propTypes: { - params: React.PropTypes.object + params: React.PropTypes.object, + location: React.PropTypes.object }, getInitialState() { @@ -62,6 +66,7 @@ let PieceContainer = React.createClass({ PieceStore.listen(this.onChange); PieceActions.fetchOne(this.props.params.pieceId); UserStore.listen(this.onChange); + UserActions.fetchCurrentUser(); // Every time we enter the piece detail page, just reset the piece // store as it will otherwise display wrong/old data once the user loads @@ -142,10 +147,10 @@ let PieceContainer = React.createClass({ -
+

{this.state.piece.title}

- + {artistEmail} {this.getActions()}
@@ -157,7 +162,7 @@ let PieceContainer = React.createClass({ piece={this.state.piece} currentUser={this.state.currentUser}/> }> - + ); } else { @@ -177,24 +182,28 @@ let NavigationHeader = React.createClass({ }, render() { - if (this.props.currentUser && this.props.currentUser.email && this.props.piece && this.props.piece.navigation) { - let nav = this.props.piece.navigation; + const { currentUser, piece } = this.props; + + if (currentUser && currentUser.email && currentUser.is_judge && currentUser.is_jury && + !currentUser.is_admin && piece && piece.navigation) { + let nav = piece.navigation; return (
- + - + {getLangText('Next')}
+
); } @@ -417,7 +426,8 @@ let PrizePieceRatings = React.createClass({ let PrizePieceDetails = React.createClass({ propTypes: { - piece: React.PropTypes.object + piece: React.PropTypes.object, + location: React.PropTypes.object }, render() { @@ -432,6 +442,8 @@ let PrizePieceDetails = React.createClass({
{Object.keys(this.props.piece.extra_data).map((data) => { let label = data.replace('_', ' '); + const value = this.props.piece.extra_data[data] || 'N/A'; + return ( - ); - } - )} -
+ defaultValue={value}/> + + ); + })} + {}} + setIsUploadReady={() => {}} + isReadyForFormSubmission={() => {}} + editable={false} + overrideForm={true} + pieceId={this.props.piece.id} + otherData={this.props.piece.other_data} + multiple={true} + location={location}/> ); diff --git a/js/components/whitelabel/prize/components/prize_hero.js b/js/components/whitelabel/prize/simple_prize/components/prize_hero.js similarity index 83% rename from js/components/whitelabel/prize/components/prize_hero.js rename to js/components/whitelabel/prize/simple_prize/components/prize_hero.js index b98f407e..8842acf9 100644 --- a/js/components/whitelabel/prize/components/prize_hero.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_hero.js @@ -1,7 +1,7 @@ 'use strict'; import React from 'react'; -import constants from '../../../../constants/application_constants'; +import constants from '../../../../../constants/application_constants'; let Hero = React.createClass({ diff --git a/js/components/whitelabel/prize/components/prize_landing.js b/js/components/whitelabel/prize/simple_prize/components/prize_landing.js similarity index 93% rename from js/components/whitelabel/prize/components/prize_landing.js rename to js/components/whitelabel/prize/simple_prize/components/prize_landing.js index 355b3786..e26a05b5 100644 --- a/js/components/whitelabel/prize/components/prize_landing.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_landing.js @@ -11,11 +11,11 @@ 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'; +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 { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; let Landing = React.createClass({ diff --git a/js/components/whitelabel/prize/components/prize_login_container.js b/js/components/whitelabel/prize/simple_prize/components/prize_login_container.js similarity index 83% rename from js/components/whitelabel/prize/components/prize_login_container.js rename to js/components/whitelabel/prize/simple_prize/components/prize_login_container.js index 9a0de06d..e168ca68 100644 --- a/js/components/whitelabel/prize/components/prize_login_container.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_login_container.js @@ -3,10 +3,10 @@ import React from 'react'; import { Link } from 'react-router'; -import LoginForm from '../../../ascribe_forms/form_login'; +import LoginForm from '../../../../ascribe_forms/form_login'; -import { getLangText } from '../../../../utils/lang_utils'; -import { setDocumentTitle } from '../../../../utils/dom_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; let LoginContainer = React.createClass({ diff --git a/js/components/whitelabel/prize/components/prize_piece_list.js b/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js similarity index 86% rename from js/components/whitelabel/prize/components/prize_piece_list.js rename to js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js index 7a6a90ac..8e602012 100644 --- a/js/components/whitelabel/prize/components/prize_piece_list.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_piece_list.js @@ -1,10 +1,10 @@ 'use strict'; import React from 'react'; -import PieceList from '../../../piece_list'; +import PieceList from '../../../../piece_list'; -import UserActions from '../../../../actions/user_actions'; -import UserStore from '../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; import PrizeActions from '../actions/prize_actions'; import PrizeStore from '../stores/prize_store'; @@ -15,9 +15,9 @@ 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'; +import { mergeOptions } from '../../../../../utils/general_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; let PrizePieceList = React.createClass({ propTypes: { diff --git a/js/components/whitelabel/prize/simple_prize/components/prize_register_piece.js b/js/components/whitelabel/prize/simple_prize/components/prize_register_piece.js new file mode 100644 index 00000000..ca4e8fa9 --- /dev/null +++ b/js/components/whitelabel/prize/simple_prize/components/prize_register_piece.js @@ -0,0 +1,101 @@ +'use strict'; + +import React from 'react'; + +import PrizeActions from '../actions/prize_actions'; +import PrizeStore from '../stores/prize_store'; + +import RegisterPiece from '../../../../register_piece'; +import Property from '../../../../ascribe_forms/property'; +import InputTextAreaToggable from '../../../../ascribe_forms/input_textarea_toggable'; +import InputCheckbox from '../../../../ascribe_forms/input_checkbox'; + +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; + + +let PrizeRegisterPiece = React.createClass({ + propTypes: { + location: React.PropTypes.object + }, + + getInitialState() { + return PrizeStore.getState(); + }, + + componentDidMount() { + PrizeStore.listen(this.onChange); + PrizeActions.fetchPrize(); + }, + + componentWillUnmount() { + PrizeStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + render() { + const { location } = this.props; + + setDocumentTitle(getLangText('Submit to the prize')); + + if(this.state.prize && this.state.prize.active){ + return ( +
+ + + + + + + + + + + {' ' + getLangText('I agree to the Terms of Service the art price') + ' '} + ( + {getLangText('read')} + ) + + + + +
+ ); + } + else { + return ( +
+
+ {getLangText('The prize is no longer active')} +
+
+ ); + } + } +}); + +export default PrizeRegisterPiece; diff --git a/js/components/whitelabel/prize/components/prize_settings_container.js b/js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js similarity index 91% rename from js/components/whitelabel/prize/components/prize_settings_container.js rename to js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js index 81d62380..145a9d24 100644 --- a/js/components/whitelabel/prize/components/prize_settings_container.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_settings_container.js @@ -2,29 +2,29 @@ import React from 'react'; -import UserStore from '../../../../stores/user_store'; -import UserActions from '../../../../actions/user_actions'; +import UserStore from '../../../../../stores/user_store'; +import UserActions from '../../../../../actions/user_actions'; import PrizeActions from '../actions/prize_actions'; import PrizeStore from '../stores/prize_store'; import PrizeJuryActions from '../actions/prize_jury_actions'; import PrizeJuryStore from '../stores/prize_jury_store'; -import SettingsContainer from '../../../ascribe_settings/settings_container'; -import CollapsibleParagraph from '../../../ascribe_collapsible/collapsible_paragraph'; +import SettingsContainer from '../../../../ascribe_settings/settings_container'; +import CollapsibleParagraph from '../../../../ascribe_collapsible/collapsible_paragraph'; -import Form from '../../../ascribe_forms/form'; -import Property from '../../../ascribe_forms/property'; +import Form from '../../../../ascribe_forms/form'; +import Property from '../../../../ascribe_forms/property'; -import ActionPanel from '../../../ascribe_panel/action_panel'; +import ActionPanel from '../../../../ascribe_panel/action_panel'; -import GlobalNotificationModel from '../../../../models/global_notification_model'; -import GlobalNotificationActions from '../../../../actions/global_notification_actions'; +import GlobalNotificationModel from '../../../../../models/global_notification_model'; +import GlobalNotificationActions from '../../../../../actions/global_notification_actions'; -import AscribeSpinner from '../../../ascribe_spinner'; -import ApiUrls from '../../../../constants/api_urls'; +import AscribeSpinner from '../../../../ascribe_spinner'; +import ApiUrls from '../../../../../constants/api_urls'; -import { getLangText } from '../../../../utils/lang_utils'; -import { setDocumentTitle } from '../../../../utils/dom_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; let Settings = React.createClass({ diff --git a/js/components/whitelabel/prize/components/prize_signup_container.js b/js/components/whitelabel/prize/simple_prize/components/prize_signup_container.js similarity index 86% rename from js/components/whitelabel/prize/components/prize_signup_container.js rename to js/components/whitelabel/prize/simple_prize/components/prize_signup_container.js index 884062da..7a44d521 100644 --- a/js/components/whitelabel/prize/components/prize_signup_container.js +++ b/js/components/whitelabel/prize/simple_prize/components/prize_signup_container.js @@ -1,10 +1,10 @@ 'use strict'; import React from 'react'; -import SignupForm from '../../../ascribe_forms/form_signup'; +import SignupForm from '../../../../ascribe_forms/form_signup'; -import { getLangText } from '../../../../utils/lang_utils'; -import { setDocumentTitle } from '../../../../utils/dom_utils'; +import { getLangText } from '../../../../../utils/lang_utils'; +import { setDocumentTitle } from '../../../../../utils/dom_utils'; let SignupContainer = React.createClass({ propTypes: { diff --git a/js/components/whitelabel/prize/fetchers/prize_fetcher.js b/js/components/whitelabel/prize/simple_prize/fetchers/prize_fetcher.js similarity index 70% rename from js/components/whitelabel/prize/fetchers/prize_fetcher.js rename to js/components/whitelabel/prize/simple_prize/fetchers/prize_fetcher.js index 0bf9fc55..410d63a2 100644 --- a/js/components/whitelabel/prize/fetchers/prize_fetcher.js +++ b/js/components/whitelabel/prize/simple_prize/fetchers/prize_fetcher.js @@ -1,6 +1,6 @@ 'use strict'; -import requests from '../../../../utils/requests'; +import requests from '../../../../../utils/requests'; let PrizeFetcher = { diff --git a/js/components/whitelabel/prize/fetchers/prize_jury_fetcher.js b/js/components/whitelabel/prize/simple_prize/fetchers/prize_jury_fetcher.js similarity index 88% rename from js/components/whitelabel/prize/fetchers/prize_jury_fetcher.js rename to js/components/whitelabel/prize/simple_prize/fetchers/prize_jury_fetcher.js index 1c5b0a0d..973107b4 100644 --- a/js/components/whitelabel/prize/fetchers/prize_jury_fetcher.js +++ b/js/components/whitelabel/prize/simple_prize/fetchers/prize_jury_fetcher.js @@ -1,6 +1,6 @@ 'use strict'; -import requests from '../../../../utils/requests'; +import requests from '../../../../../utils/requests'; let PrizeJuryFetcher = { diff --git a/js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js b/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js similarity index 90% rename from js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js rename to js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js index 33450dd6..38d0576e 100644 --- a/js/components/whitelabel/prize/fetchers/prize_rating_fetcher.js +++ b/js/components/whitelabel/prize/simple_prize/fetchers/prize_rating_fetcher.js @@ -1,6 +1,6 @@ 'use strict'; -import requests from '../../../../utils/requests'; +import requests from '../../../../../utils/requests'; let PrizeRatingFetcher = { diff --git a/js/components/whitelabel/prize/prize_app.js b/js/components/whitelabel/prize/simple_prize/prize_app.js similarity index 87% rename from js/components/whitelabel/prize/prize_app.js rename to js/components/whitelabel/prize/simple_prize/prize_app.js index aadb0b05..d95d7772 100644 --- a/js/components/whitelabel/prize/prize_app.js +++ b/js/components/whitelabel/prize/simple_prize/prize_app.js @@ -2,11 +2,11 @@ import React from 'react'; import Hero from './components/prize_hero'; -import Header from '../../header'; -import Footer from '../../footer'; -import GlobalNotification from '../../global_notification'; +import Header from '../../../header'; +import Footer from '../../../footer'; +import GlobalNotification from '../../../global_notification'; -import { getSubdomain } from '../../../utils/general_utils'; +import { getSubdomain } from '../../../../utils/general_utils'; let PrizeApp = React.createClass({ diff --git a/js/components/whitelabel/prize/stores/prize_jury_store.js b/js/components/whitelabel/prize/simple_prize/stores/prize_jury_store.js similarity index 96% rename from js/components/whitelabel/prize/stores/prize_jury_store.js rename to js/components/whitelabel/prize/simple_prize/stores/prize_jury_store.js index 69d73e3a..536b8633 100644 --- a/js/components/whitelabel/prize/stores/prize_jury_store.js +++ b/js/components/whitelabel/prize/simple_prize/stores/prize_jury_store.js @@ -1,6 +1,6 @@ 'use strict'; -import { alt } from '../../../../alt'; +import { alt } from '../../../../../alt'; import PrizeJuryActions from '../actions/prize_jury_actions'; diff --git a/js/components/whitelabel/prize/stores/prize_rating_store.js b/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js similarity index 93% rename from js/components/whitelabel/prize/stores/prize_rating_store.js rename to js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js index d67fa603..9f1552bb 100644 --- a/js/components/whitelabel/prize/stores/prize_rating_store.js +++ b/js/components/whitelabel/prize/simple_prize/stores/prize_rating_store.js @@ -1,6 +1,6 @@ 'use strict'; -import { alt } from '../../../../alt'; +import { alt } from '../../../../../alt'; import PrizeRatingActions from '../actions/prize_rating_actions'; diff --git a/js/components/whitelabel/prize/stores/prize_store.js b/js/components/whitelabel/prize/simple_prize/stores/prize_store.js similarity index 87% rename from js/components/whitelabel/prize/stores/prize_store.js rename to js/components/whitelabel/prize/simple_prize/stores/prize_store.js index 68cc9264..8d9c4bbe 100644 --- a/js/components/whitelabel/prize/stores/prize_store.js +++ b/js/components/whitelabel/prize/simple_prize/stores/prize_store.js @@ -1,6 +1,6 @@ 'use strict'; -import { alt } from '../../../../alt'; +import { alt } from '../../../../../alt'; import PrizeActions from '../actions/prize_actions'; diff --git a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js index 5644b5b0..b263e517 100644 --- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js +++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_piece_container.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import Moment from 'moment'; import Piece from '../../../../../components/ascribe_detail/piece'; @@ -39,7 +40,7 @@ let WalletPieceContainer = React.createClass({

{this.props.piece.title}

- +
} diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js index 755e550b..9802e93e 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_accordion_list/cyland_accordion_list_item.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import Moment from 'moment'; import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece'; @@ -100,7 +101,7 @@ let CylandAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
- {new Date(this.props.content.date_created).getFullYear()} + {Moment(this.props.content.date_created, 'YYYY-MM-DD').year()}
} buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js index 1c61d573..0fe5a025 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_detail/cyland_piece_container.js @@ -33,7 +33,6 @@ import { mergeOptions } from '../../../../../../utils/general_utils'; let CylandPieceContainer = React.createClass({ propTypes: { - location: React.PropTypes.object, params: React.PropTypes.object }, @@ -106,8 +105,7 @@ let CylandPieceContainer = React.createClass({ + isInline={true} /> ); diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js index 63863b2d..a7631d95 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_forms/cyland_additional_data_form.js @@ -26,8 +26,7 @@ let CylandAdditionalDataForm = React.createClass({ handleSuccess: React.PropTypes.func, piece: React.PropTypes.object.isRequired, disabled: React.PropTypes.bool, - isInline: React.PropTypes.bool, - location: React.PropTypes.object + isInline: React.PropTypes.bool }, getDefaultProps() { @@ -78,7 +77,7 @@ let CylandAdditionalDataForm = React.createClass({ }, render() { - let { piece, isInline, disabled, handleSuccess } = this.props; + let { piece, isInline, disabled, handleSuccess, location } = this.props; let buttons, spinner, heading; if(!isInline) { @@ -122,29 +121,76 @@ let CylandAdditionalDataForm = React.createClass({ {heading} + label={getLangText('Artist Biography')} + hidden={disabled && !piece.extra_data.artist_bio}> + + label={getLangText('Conceptual Overview')} + hidden={disabled && !piece.extra_data.conceptual_overview}> + + + + + multiple={true} /> ); } else { @@ -157,4 +203,4 @@ let CylandAdditionalDataForm = React.createClass({ } }); -export default CylandAdditionalDataForm; \ No newline at end of file +export default CylandAdditionalDataForm; diff --git a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js index b1df4469..470da761 100644 --- a/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js +++ b/js/components/whitelabel/wallet/components/cyland/cyland_register_piece.js @@ -210,8 +210,7 @@ let CylandRegisterPiece = React.createClass({ 1} handleSuccess={this.handleAdditionalDataSuccess} - piece={this.state.piece} - location={this.props.location}/> + piece={this.state.piece} /> diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js index 7445eb36..f2f73767 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_accordion_list/ikonotv_accordion_list_item.js @@ -1,6 +1,7 @@ 'use strict'; import React from 'react'; +import Moment from 'moment'; import AccordionListItemPiece from '../../../../../ascribe_accordion_list/accordion_list_item_piece'; @@ -106,7 +107,7 @@ let IkonotvAccordionListItem = React.createClass({ piece={this.props.content} subsubheading={
- {new Date(this.props.content.date_created).getFullYear()} + {Moment(this.props.content.date_created, 'YYYY-MM-DD').year()}
} buttons={this.getSubmitButtons()}> {this.props.children} diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index 2ff689fe..a07f29b1 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -14,6 +14,7 @@ let ApiUrls = { 'blob_digitalworks': AppConstants.apiEndpoint + 'blob/digitalworks/', 'blob_otherdatas': AppConstants.apiEndpoint + 'blob/otherdatas/', 'blob_contracts': AppConstants.apiEndpoint + 'blob/contracts/', + 'blob_thumbnails': AppConstants.apiEndpoint + 'blob/thumbnails/', 'coa': AppConstants.apiEndpoint + 'coa/${id}/', 'coa_create': AppConstants.apiEndpoint + 'coa/', 'coa_verify': AppConstants.apiEndpoint + 'coa/verify_coa/', diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 84e8fd2f..a58a8cc6 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -1,15 +1,19 @@ 'use strict'; -let constants = { - //'baseUrl': 'http://localhost:8000/api/', +//const baseUrl = 'http://localhost:8000/api/'; - //FIXME: referring to a global variable in `window` is not - // super pro. What if we render stuff on the server? - // - super-bro - Senor Developer, 14th July 2015 - //'baseUrl': window.BASE_URL, - 'apiEndpoint': window.API_ENDPOINT, - 'serverUrl': window.SERVER_URL, - 'baseUrl': window.BASE_URL, +//FIXME: referring to a global variable in `window` is not +// super pro. What if we render stuff on the server? +// - super-bro - Senor Developer, 14th July 2015 +//const baseUrl = window.BASE_URL; +const apiEndpoint = window.API_ENDPOINT; +const serverUrl = window.SERVER_URL; +const baseUrl = window.BASE_URL; + +const constants = { + apiEndpoint, + serverUrl, + baseUrl, 'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions', 'acl_loan', 'acl_loan_request', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view', 'acl_withdraw_transfer', 'acl_wallet_submit'], @@ -46,6 +50,13 @@ let constants = { 'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono-logo-black.png', 'permissions': ['register', 'edit', 'share', 'del_from_collection'], 'type': 'wallet' + }, + { + 'subdomain': 'portfolioreview', + 'name': 'Portfolio Review', + 'logo': 'http://notfoundlogo.de', + 'permissions': ['register', 'edit', 'share', 'del_from_collection'], + 'type': 'prize' } ], 'defaultDomain': { @@ -70,16 +81,40 @@ let constants = { } }, - // in case of whitelabel customization, we store stuff here - 'whitelabel': {}, - 'raven': { - 'url': 'https://0955da3388c64ab29bd32c2a429f9ef4@app.getsentry.com/48351' - }, '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'], - 'searchThreshold': 500 + 'searchThreshold': 500, + + // in case of whitelabel customization, we store stuff here + 'whitelabel': {}, + + // 3rd party integrations + 'jquery': { + 'sdkUrl': 'https://code.jquery.com/jquery-2.1.4.min.js' + }, + 'shmui': { + 'sdkUrl': baseUrl + 'static/thirdparty/shmui/jquery.shmui.js', + 'cssUrl': baseUrl + 'static/thirdparty/shmui/shmui.css' + }, + 'audiojs': { + 'sdkUrl': baseUrl + 'static/thirdparty/audiojs/audiojs/audio.min.js' + }, + 'videojs': { + 'sdkUrl': '//vjs.zencdn.net/4.12/video.js', + 'cssUrl': '//vjs.zencdn.net/4.12/video-js.css' + }, + 'raven': { + 'url': 'https://0955da3388c64ab29bd32c2a429f9ef4@app.getsentry.com/48351' + }, + 'facebook': { + 'appId': '420813844732240', + 'sdkUrl': '//connect.facebook.net/en_US/sdk.js' + }, + 'twitter': { + 'sdkUrl': 'https://platform.twitter.com/widgets.js' + } }; export default constants; diff --git a/js/fetchers/edition_list_fetcher.js b/js/fetchers/edition_list_fetcher.js index b416c595..93e4553d 100644 --- a/js/fetchers/edition_list_fetcher.js +++ b/js/fetchers/edition_list_fetcher.js @@ -2,8 +2,8 @@ import requests from '../utils/requests'; -import { generateOrderingQueryParams } from '../utils/fetch_api_utils'; import { mergeOptions } from '../utils/general_utils'; +import { generateOrderingQueryParams } from '../utils/url_utils'; let EditionListFetcher = { /** diff --git a/js/fetchers/piece_list_fetcher.js b/js/fetchers/piece_list_fetcher.js index 8e58402a..6bd4eb3a 100644 --- a/js/fetchers/piece_list_fetcher.js +++ b/js/fetchers/piece_list_fetcher.js @@ -3,7 +3,7 @@ import requests from '../utils/requests'; import { mergeOptions } from '../utils/general_utils'; -import { generateOrderingQueryParams } from '../utils/fetch_api_utils'; +import { generateOrderingQueryParams } from '../utils/url_utils'; let PieceListFetcher = { /** diff --git a/js/mixins/inject_in_head_mixin.js b/js/mixins/inject_in_head_mixin.js deleted file mode 100644 index 6eacacad..00000000 --- a/js/mixins/inject_in_head_mixin.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -import Q from 'q'; - -let mapAttr = { - link: 'href', - script: 'src' -}; - -let mapTag = { - js: 'script', - css: 'link' -}; - - -let InjectInHeadMixin = { - /** - * Provide functions to inject `