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/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/acl_button.js b/js/components/ascribe_buttons/acl_button.js
deleted file mode 100644
index 67f4a430..00000000
--- a/js/components/ascribe_buttons/acl_button.js
+++ /dev/null
@@ -1,187 +0,0 @@
-'use strict';
-
-import React from 'react';
-
-import ConsignForm from '../ascribe_forms/form_consign';
-import UnConsignForm from '../ascribe_forms/form_unconsign';
-import TransferForm from '../ascribe_forms/form_transfer';
-import LoanForm from '../ascribe_forms/form_loan';
-import LoanRequestAnswerForm from '../ascribe_forms/form_loan_request_answer';
-import ShareForm from '../ascribe_forms/form_share_email';
-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 { getAclFormMessage } from '../../utils/form_utils';
-import { getLangText } from '../../utils/lang_utils';
-
-let AclButton = React.createClass({
- propTypes: {
- action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
- availableAcls: React.PropTypes.object.isRequired,
- pieceOrEditions: React.PropTypes.oneOfType([
- React.PropTypes.object,
- React.PropTypes.array
- ]).isRequired,
- currentUser: React.PropTypes.object,
- buttonAcceptName: React.PropTypes.string,
- buttonAcceptClassName: React.PropTypes.string,
- handleSuccess: React.PropTypes.func.isRequired,
- className: React.PropTypes.string
- },
-
- isPiece(){
- return this.props.pieceOrEditions.constructor !== Array;
- },
-
- actionProperties(){
-
- let message = getAclFormMessage(this.props.action, this.getTitlesString(), this.props.currentUser.username);
-
- if (this.props.action === 'acl_consign'){
- return {
- title: getLangText('Consign artwork'),
- tooltip: getLangText('Have someone else sell the artwork'),
- form: (
-
- ),
- handleSuccess: this.showNotification
- };
- }
- if (this.props.action === 'acl_unconsign'){
- return {
- title: getLangText('Unconsign artwork'),
- tooltip: getLangText('Have the owner manage his sales again'),
- form: (
-
- ),
- handleSuccess: this.showNotification
- };
- }else if (this.props.action === 'acl_transfer') {
- return {
- title: getLangText('Transfer artwork'),
- tooltip: getLangText('Transfer the ownership of the artwork'),
- form: (
-
- ),
- handleSuccess: this.showNotification
- };
- }
- else if (this.props.action === 'acl_loan'){
- return {
- title: getLangText('Loan artwork'),
- tooltip: getLangText('Loan your artwork for a limited period of time'),
- form: (
- ),
- handleSuccess: this.showNotification
- };
- }
- else if (this.props.action === 'acl_loan_request'){
- return {
- title: getLangText('Loan artwork'),
- tooltip: getLangText('Someone requested you to loan your artwork for a limited period of time'),
- form: (
- ),
- handleSuccess: this.showNotification
- };
- }
- else if (this.props.action === 'acl_share'){
- return {
- title: getLangText('Share artwork'),
- tooltip: getLangText('Share the artwork'),
- form: (
-
- ),
- handleSuccess: this.showNotification
- };
- } else {
- throw new Error('Your specified action did not match a form.');
- }
- },
-
- showNotification(response){
- this.props.handleSuccess();
- if(response.notification) {
- let notification = new GlobalNotificationModel(response.notification, 'success');
- GlobalNotificationActions.appendGlobalNotification(notification);
- }
- },
-
- // plz move to share form
- getTitlesString(){
- if (this.isPiece()){
- return '\"' + this.props.pieceOrEditions.title + '\"';
- }
- else {
- return this.props.pieceOrEditions.map(function(edition) {
- return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
- }).join('');
- }
-
- },
-
- getFormDataId(){
- if (this.isPiece()) {
- return {piece_id: this.props.pieceOrEditions.id};
- }
- else {
- return {bitcoin_id: this.props.pieceOrEditions.map(function(edition){
- return edition.bitcoin_id;
- }).join()};
- }
- },
-
- // Removes the acl_ prefix and converts to upper case
- sanitizeAction() {
- if (this.props.buttonAcceptName) {
- return this.props.buttonAcceptName;
- }
- return this.props.action.split('acl_')[1].toUpperCase();
- },
-
- render() {
- if (this.props.availableAcls){
- let shouldDisplay = this.props.availableAcls[this.props.action];
- let aclProps = this.actionProperties();
- let buttonClassName = this.props.buttonAcceptClassName ? this.props.buttonAcceptClassName : '';
- return (
-
- {this.sanitizeAction()}
-
- }
- handleSuccess={aclProps.handleSuccess}
- title={aclProps.title}>
- {aclProps.form}
-
- );
- }
- return null;
- }
-});
-
-export default AclButton;
\ No newline at end of file
diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js
index e87a6407..83495363 100644
--- a/js/components/ascribe_buttons/acl_button_list.js
+++ b/js/components/ascribe_buttons/acl_button_list.js
@@ -5,21 +5,25 @@ import React from 'react/addons';
import UserActions from '../../actions/user_actions';
import UserStore from '../../stores/user_store';
-import AclButton from '../ascribe_buttons/acl_button';
+import ConsignButton from './acls/consign_button';
+import LoanButton from './acls/loan_button';
+import LoanRequestButton from './acls/loan_request_button';
+import ShareButton from './acls/share_button';
+import TransferButton from './acls/transfer_button';
+import UnconsignButton from './acls/unconsign_button';
import { mergeOptions } from '../../utils/general_utils';
-
let AclButtonList = React.createClass({
propTypes: {
className: React.PropTypes.string,
- editions: React.PropTypes.oneOfType([
+ pieceOrEditions: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
- ]),
- availableAcls: React.PropTypes.object,
+ ]).isRequired,
+ availableAcls: React.PropTypes.object.isRequired,
buttonsStyle: React.PropTypes.object,
- handleSuccess: React.PropTypes.func,
+ handleSuccess: React.PropTypes.func.isRequired,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
@@ -78,7 +82,7 @@ let AclButtonList = React.createClass({
const { className,
buttonsStyle,
availableAcls,
- editions,
+ pieceOrEditions,
handleSuccess } = this.props;
const { currentUser } = this.state;
@@ -86,34 +90,29 @@ let AclButtonList = React.createClass({
return (
-
-
-
-
-
{this.renderChildren()}
@@ -123,4 +122,4 @@ let AclButtonList = React.createClass({
}
});
-export default AclButtonList;
\ No newline at end of file
+export default AclButtonList;
diff --git a/js/components/ascribe_buttons/acls/acl_button.js b/js/components/ascribe_buttons/acls/acl_button.js
new file mode 100644
index 00000000..c247c9ba
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/acl_button.js
@@ -0,0 +1,78 @@
+'use strict';
+
+import React from 'react';
+import classNames from 'classnames';
+
+import AclProxy from '../../acl_proxy';
+
+import AclFormFactory from '../../ascribe_forms/acl_form_factory';
+
+import ModalWrapper from '../../ascribe_modal/modal_wrapper';
+
+import AppConstants from '../../../constants/application_constants';
+
+
+export default function ({ action, displayName, title, tooltip }) {
+ if (AppConstants.aclList.indexOf(action) < 0) {
+ console.warn('Your specified aclName did not match a an acl class.');
+ }
+
+ return React.createClass({
+ displayName: displayName,
+
+ propTypes: {
+ availableAcls: React.PropTypes.object.isRequired,
+ buttonAcceptName: React.PropTypes.string,
+ buttonAcceptClassName: React.PropTypes.string,
+ currentUser: React.PropTypes.object.isRequired,
+ email: React.PropTypes.string,
+ pieceOrEditions: React.PropTypes.oneOfType([
+ React.PropTypes.object,
+ React.PropTypes.array
+ ]).isRequired,
+ handleSuccess: React.PropTypes.func.isRequired,
+ className: React.PropTypes.string
+ },
+
+ // Removes the acl_ prefix and converts to upper case
+ sanitizeAction() {
+ if (this.props.buttonAcceptName) {
+ return this.props.buttonAcceptName;
+ }
+ return action.split('acl_')[1].toUpperCase();
+ },
+
+ render() {
+ const {
+ availableAcls,
+ buttonAcceptClassName,
+ currentUser,
+ email,
+ pieceOrEditions,
+ handleSuccess } = this.props;
+
+ return (
+
+
+ {this.sanitizeAction()}
+
+ }
+ handleSuccess={handleSuccess}
+ title={title}>
+
+
+
+ );
+ }
+ });
+}
diff --git a/js/components/ascribe_buttons/acls/consign_button.js b/js/components/ascribe_buttons/acls/consign_button.js
new file mode 100644
index 00000000..88c86097
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/consign_button.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import React from 'react';
+
+import AclButton from './acl_button';
+
+import { getLangText } from '../../../utils/lang_utils';
+
+export default AclButton({
+ action: 'acl_consign',
+ displayName: 'ConsignButton',
+ title: getLangText('Consign artwork'),
+ tooltip: getLangText('Have someone else sell the artwork')
+});
diff --git a/js/components/ascribe_buttons/acls/loan_button.js b/js/components/ascribe_buttons/acls/loan_button.js
new file mode 100644
index 00000000..4b803ceb
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/loan_button.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import React from 'react';
+
+import AclButton from './acl_button';
+
+import { getLangText } from '../../../utils/lang_utils';
+
+export default AclButton({
+ action: 'acl_loan',
+ displayName: 'LoanButton',
+ title: getLangText('Loan artwork'),
+ tooltip: getLangText('Loan your artwork for a limited period of time')
+});
diff --git a/js/components/ascribe_buttons/acls/loan_request_button.js b/js/components/ascribe_buttons/acls/loan_request_button.js
new file mode 100644
index 00000000..a1ec5f3b
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/loan_request_button.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import React from 'react';
+
+import AclButton from './acl_button';
+
+import { getLangText } from '../../../utils/lang_utils';
+
+export default AclButton({
+ action: 'acl_loan_request',
+ displayName: 'LoanRequestButton',
+ title: getLangText('Loan artwork'),
+ tooltip: getLangText('Someone requested you to loan your artwork for a limited period of time')
+});
diff --git a/js/components/ascribe_buttons/acls/share_button.js b/js/components/ascribe_buttons/acls/share_button.js
new file mode 100644
index 00000000..83781aed
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/share_button.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import React from 'react';
+
+import AclButton from './acl_button';
+
+import { getLangText } from '../../../utils/lang_utils';
+
+export default AclButton({
+ action: 'acl_share',
+ displayName: 'ShareButton',
+ title: getLangText('Share artwork'),
+ tooltip: getLangText('Share the artwork')
+});
diff --git a/js/components/ascribe_buttons/acls/transfer_button.js b/js/components/ascribe_buttons/acls/transfer_button.js
new file mode 100644
index 00000000..346907ca
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/transfer_button.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import React from 'react';
+
+import AclButton from './acl_button';
+
+import { getLangText } from '../../../utils/lang_utils';
+
+export default AclButton({
+ action: 'acl_transfer',
+ displayName: 'TransferButton',
+ title: getLangText('Transfer artwork'),
+ tooltip: getLangText('Transfer the ownership of the artwork')
+});
diff --git a/js/components/ascribe_buttons/acls/unconsign_button.js b/js/components/ascribe_buttons/acls/unconsign_button.js
new file mode 100644
index 00000000..ce1ed0bc
--- /dev/null
+++ b/js/components/ascribe_buttons/acls/unconsign_button.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import React from 'react';
+
+import AclButton from './acl_button';
+
+import { getLangText } from '../../../utils/lang_utils';
+
+export default AclButton({
+ action: 'acl_unconsign',
+ displayName: 'UnconsignButton',
+ title: getLangText('Unconsign artwork'),
+ tooltip: getLangText('Have the owner manage his sales again')
+});
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..d31a205f 100644
--- a/js/components/ascribe_detail/media_container.js
+++ b/js/components/ascribe_detail/media_container.js
@@ -92,7 +92,7 @@ let MediaContainer = React.createClass({
aclObject={this.props.content.acl}
aclName="acl_download">
- Download
+ Download .{mimetype}
{embed}
diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js
index 3895b8b2..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';
@@ -201,7 +202,7 @@ let PieceContainer = React.createClass({
{this.state.piece.title}
-
+
{this.state.piece.num_editions > 0 ? : null}
diff --git a/js/components/ascribe_forms/acl_form_factory.js b/js/components/ascribe_forms/acl_form_factory.js
new file mode 100644
index 00000000..d5494c2d
--- /dev/null
+++ b/js/components/ascribe_forms/acl_form_factory.js
@@ -0,0 +1,128 @@
+'use strict';
+
+import React from 'react';
+
+import ConsignForm from '../ascribe_forms/form_consign';
+import UnConsignForm from '../ascribe_forms/form_unconsign';
+import TransferForm from '../ascribe_forms/form_transfer';
+import LoanForm from '../ascribe_forms/form_loan';
+import LoanRequestAnswerForm from '../ascribe_forms/form_loan_request_answer';
+import ShareForm from '../ascribe_forms/form_share_email';
+
+import AppConstants from '../../constants/application_constants';
+import ApiUrls from '../../constants/api_urls';
+
+import GlobalNotificationModel from '../../models/global_notification_model';
+import GlobalNotificationActions from '../../actions/global_notification_actions';
+
+import { getAclFormMessage, getAclFormDataId } from '../../utils/form_utils';
+
+let AclFormFactory = React.createClass({
+ propTypes: {
+ action: React.PropTypes.oneOf(AppConstants.aclList).isRequired,
+ currentUser: React.PropTypes.object.isRequired,
+ email: React.PropTypes.string,
+ message: React.PropTypes.string,
+ pieceOrEditions: React.PropTypes.oneOfType([
+ React.PropTypes.object,
+ React.PropTypes.array
+ ]).isRequired,
+ handleSuccess: React.PropTypes.func,
+ showNotification: React.PropTypes.bool
+ },
+
+ isPiece() {
+ return this.props.pieceOrEditions.constructor !== Array;
+ },
+
+ getFormDataId() {
+ return getAclFormDataId(this.isPiece(), this.props.pieceOrEditions);
+ },
+
+ showSuccessNotification(response) {
+ if (typeof this.props.handleSuccess === 'function') {
+ this.props.handleSuccess();
+ }
+
+ if (response.notification) {
+ const notification = new GlobalNotificationModel(response.notification, 'success');
+ GlobalNotificationActions.appendGlobalNotification(notification);
+ }
+ },
+
+ render() {
+ const {
+ action,
+ pieceOrEditions,
+ currentUser,
+ email,
+ message,
+ handleSuccess,
+ showNotification } = this.props;
+
+ const formMessage = message || getAclFormMessage({
+ aclName: action,
+ entities: pieceOrEditions,
+ isPiece: this.isPiece(),
+ senderName: currentUser.username
+ });
+
+ if (action === 'acl_consign') {
+ return (
+
+ );
+ } else if (action === 'acl_unconsign') {
+ return (
+
+ );
+ } else if (action === 'acl_transfer') {
+ return (
+
+ );
+ } else if (action === 'acl_loan') {
+ return (
+
+ );
+ } else if (action === 'acl_loan_request') {
+ return (
+
+ );
+ } else if (action === 'acl_share') {
+ return (
+
+ );
+ } else {
+ throw new Error('Your specified action did not match a form.');
+ }
+ }
+});
+
+export default AclFormFactory;
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} />
{
- let message = getLangText('You have successfully') + ' ' + option + ' the ' + action + ' request ' + getLangText('from') + ' ' + owner;
-
- let notifications = new GlobalNotificationModel(message, 'success');
+ const message = getLangText('You have successfully %s the %s request from %s', getLangText(option), getLangText(action), owner);
+ const notifications = new GlobalNotificationModel(message, 'success');
GlobalNotificationActions.appendGlobalNotification(notifications);
this.handleSuccess();
-
};
},
handleSuccess() {
- if (this.isPiece()){
+ if (this.isPiece()) {
NotificationActions.fetchPieceListNotifications();
- }
- else {
+ } else {
NotificationActions.fetchEditionListNotifications();
}
- if(this.props.handleSuccess) {
+
+ if (typeof this.props.handleSuccess === 'function') {
this.props.handleSuccess();
}
},
@@ -98,21 +92,19 @@ let RequestActionForm = React.createClass({
},
getAcceptButtonForm(urls) {
- if(this.props.notifications.action === 'unconsign') {
+ if (this.props.notifications.action === 'unconsign') {
return (
-
);
- } else if(this.props.notifications.action === 'loan_request') {
+ } else if (this.props.notifications.action === 'loan_request') {
return (
-
@@ -140,8 +132,8 @@ let RequestActionForm = React.createClass({
},
getButtonForm() {
- let urls = this.getUrls();
- let acceptButtonForm = this.getAcceptButtonForm(urls);
+ const urls = this.getUrls();
+ const acceptButtonForm = this.getAcceptButtonForm(urls);
return (
@@ -150,13 +142,13 @@ let RequestActionForm = React.createClass({
isInline={true}
getFormData={this.getFormData}
handleSuccess={
- this.showNotification(getLangText('denied'), this.props.notifications.action, this.props.notifications.by)
+ this.showNotification('denied', this.props.notifications.action, this.props.notifications.by)
}
className='inline pull-right'>
- {getLangText('REJECT')}
+ {getLangText('REJECT')}
{acceptButtonForm}
@@ -168,10 +160,10 @@ let RequestActionForm = React.createClass({
return (
+ buttons={this.getButtonForm()} />
);
}
});
-export default RequestActionForm;
\ No newline at end of file
+export default RequestActionForm;
diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js
index f0af2143..948521c0 100644
--- a/js/components/ascribe_forms/input_fineuploader.js
+++ b/js/components/ascribe_forms/input_fineuploader.js
@@ -3,64 +3,80 @@
import React from 'react';
import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader';
+import FileDragAndDrop from '../ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop';
import AppConstants from '../../constants/application_constants';
import { getCookie } from '../../utils/fetch_api_utils';
-let InputFineUploader = React.createClass({
+
+const { func, bool, object, shape, string, number, arrayOf } = React.PropTypes;
+
+const InputFineUploader = React.createClass({
propTypes: {
- setIsUploadReady: React.PropTypes.func,
- isReadyForFormSubmission: React.PropTypes.func,
- submitFileName: React.PropTypes.func,
+ setIsUploadReady: func,
+ isReadyForFormSubmission: func,
+ submitFileName: func,
+ fileInputElement: func,
- areAssetsDownloadable: React.PropTypes.bool,
+ areAssetsDownloadable: bool,
- onClick: React.PropTypes.func,
- keyRoutine: React.PropTypes.shape({
- url: React.PropTypes.string,
- fileClass: React.PropTypes.string
+ keyRoutine: shape({
+ url: string,
+ fileClass: string
}),
- createBlobRoutine: React.PropTypes.shape({
- url: React.PropTypes.string
+ createBlobRoutine: shape({
+ url: string
}),
- validation: React.PropTypes.shape({
- itemLimit: React.PropTypes.number,
- sizeLimit: React.PropTypes.string,
- allowedExtensions: React.PropTypes.arrayOf(React.PropTypes.string)
+ validation: shape({
+ itemLimit: number,
+ sizeLimit: string,
+ allowedExtensions: arrayOf(string)
}),
// isFineUploaderActive is used to lock react fine uploader in case
// a user is actually not logged in already to prevent him from droping files
// before login in
- isFineUploaderActive: React.PropTypes.bool,
- onLoggedOut: React.PropTypes.func,
+ isFineUploaderActive: bool,
+ onLoggedOut: func,
- enableLocalHashing: React.PropTypes.bool,
+ enableLocalHashing: bool,
+ uploadMethod: string,
// provided by Property
- disabled: React.PropTypes.bool,
+ disabled: bool,
// A class of a file the user has to upload
// Needs to be defined both in singular as well as in plural
- fileClassToUpload: React.PropTypes.shape({
- singular: React.PropTypes.string,
- plural: React.PropTypes.string
- }),
- location: React.PropTypes.object
+ fileClassToUpload: shape({
+ singular: string,
+ plural: string
+ })
+ },
+
+ getDefaultProps() {
+ return {
+ fileInputElement: FileDragAndDrop
+ };
},
getInitialState() {
return {
- value: null
+ value: null,
+ file: null
};
},
- submitFile(file){
+ submitFile(file) {
this.setState({
+ file,
value: file.key
});
+ if(this.state.value && typeof this.props.onChange === 'function') {
+ this.props.onChange({ target: { value: this.state.value } });
+ }
+
if(typeof this.props.submitFileName === 'function') {
this.props.submitFileName(file.originalName);
}
@@ -70,7 +86,25 @@ let InputFineUploader = React.createClass({
this.refs.fineuploader.reset();
},
+ createBlobRoutine() {
+ const { fineuploader } = this.refs;
+ const { file } = this.state;
+
+ fineuploader.createBlob(file);
+ },
+
render() {
+ const { fileInputElement,
+ keyRoutine,
+ createBlobRoutine,
+ validation,
+ setIsUploadReady,
+ isReadyForFormSubmission,
+ areAssetsDownloadable,
+ onLoggedOut,
+ enableLocalHashing,
+ fileClassToUpload,
+ location } = this.props;
let editable = this.props.isFineUploaderActive;
// if disabled is actually set by property, we want to override
@@ -82,14 +116,14 @@ let InputFineUploader = React.createClass({
return (
+ 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..19d6a1c7 100644
--- a/js/components/ascribe_media/media_player.js
+++ b/js/components/ascribe_media/media_player.js
@@ -50,25 +50,35 @@ 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) {
+ 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(); }));
+ }
},
render() {
- return (
-
- );
+ const { url, preview } = this.props;
+
+ if(url) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
}
});
@@ -202,26 +212,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_piece_list_bulk_modal/piece_list_bulk_modal.js b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js
index 3e5b6495..2eedbd4c 100644
--- a/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js
+++ b/js/components/ascribe_piece_list_bulk_modal/piece_list_bulk_modal.js
@@ -117,7 +117,7 @@ let PieceListBulkModal = React.createClass({
{
return React.createClass({
+ displayName: 'AuthProxyHandler',
+
propTypes: {
location: object
},
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_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 95ebd150..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,7 @@ let FileDragAndDrop = React.createClass({
plural: React.PropTypes.string
}),
- allowedExtensions: React.PropTypes.string,
- location: React.PropTypes.object
+ allowedExtensions: React.PropTypes.string
},
clearSelection() {
@@ -141,19 +141,19 @@ let FileDragAndDrop = React.createClass({
},
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;
@@ -189,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()}
-
-
+
);
}
});
diff --git a/js/components/ascribe_uploader/react_s3_fine_uploader.js b/js/components/ascribe_uploader/react_s3_fine_uploader.js
index e3adef58..eaa696d2 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() {
@@ -314,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',
@@ -336,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) => {
@@ -352,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);
});
});
@@ -370,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] = {
@@ -386,7 +394,6 @@ let ReactS3FineUploader = React.createClass({
},
onUploadChunkSuccess(id, chunkData, responseJson, xhr) {
-
let chunks = this.state.chunks;
let chunkKey = id + '-' + chunkData.startByte + '-' + chunkData.endByte;
@@ -428,7 +435,7 @@ 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
@@ -445,23 +452,28 @@ 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);
}
},
+ /**
+ * 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) {
console.logGlobal(errorReason, false, {
files: this.state.filesToUpload,
chunks: this.state.chunks
});
+ this.props.setIsUploadReady(true);
this.cancelUploads();
let notification = new GlobalNotificationModel(errorReason || this.props.defaultErrorMessage, 'danger', 5000);
@@ -614,7 +626,6 @@ let ReactS3FineUploader = React.createClass({
} else {
throw new Error(getLangText('File upload could not be paused.'));
}
-
},
handleResumeFile(fileId) {
@@ -626,6 +637,10 @@ 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) {
@@ -665,16 +680,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;
@@ -686,7 +699,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
@@ -694,7 +706,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.
@@ -714,18 +725,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});
@@ -846,15 +853,13 @@ let ReactS3FineUploader = React.createClass({
},
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() {
@@ -875,9 +880,8 @@ let ReactS3FineUploader = React.createClass({
onInactive,
enableLocalHashing,
fileClassToUpload,
- validation,
fileInputElement: FileInputElement,
- location } = this.props;
+ uploadMethod } = this.props;
const props = {
multiple,
@@ -885,8 +889,8 @@ let ReactS3FineUploader = React.createClass({
areAssetsEditable,
onInactive,
enableLocalHashing,
+ uploadMethod,
fileClassToUpload,
- location,
onDrop: this.handleUploadFile,
filesToUpload: this.state.filesToUpload,
handleDeleteFile: this.handleDeleteFile,
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 (
+
+ {getLangText('Submit to Portfolio Review')}
+
+ );
+ }
+ },
+
+ render() {
+ const { location } = this.props;
+
+ return (
+
+
+
+
+
+ {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('Sign up to submit')}
+
+
+
+
+ {getLangText('or, already an ascribe user?')}
+
+
+
+ {getLangText('Log in to submit')}
+
+
+
+ );
+ }
+ return (
+
+
+ {getLangText('Sign up to ascribe')}
+
+
+
+ {getLangText('or, already an ascribe user?')}
+
+
+
+ {getLangText('Log in')}
+
+
+
+ );
+ },
+
+ 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 (
+
+ );
+ }
+});
+
+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('Previous')}
-
+
{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({
);
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_action_panel.js b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js
index 178be7da..bd240126 100644
--- a/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js
+++ b/js/components/whitelabel/wallet/components/ascribe_detail/wallet_action_panel.js
@@ -47,7 +47,7 @@ let WalletActionPanel = 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 ca755cf4..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} />
@@ -220,7 +219,12 @@ let CylandRegisterPiece = React.createClass({
- {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 0fe5e210..0ada8fe3 100644
--- a/js/constants/application_constants.js
+++ b/js/constants/application_constants.js
@@ -11,7 +11,7 @@ let constants = {
'serverUrl': window.SERVER_URL,
'baseUrl': window.BASE_URL,
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_create_editions', 'acl_view_editions',
- 'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
+ 'acl_loan', 'acl_loan_request', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
'acl_withdraw_transfer', 'acl_wallet_submit'],
'version': 0.1,
@@ -46,6 +46,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': {
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/utils/fetch_api_utils.js b/js/utils/fetch_api_utils.js
index 3ed964ba..0b2d45dc 100644
--- a/js/utils/fetch_api_utils.js
+++ b/js/utils/fetch_api_utils.js
@@ -1,64 +1,12 @@
'use strict';
import Q from 'q';
+import moment from 'moment';
-import { sanitize } from './general_utils';
import AppConstants from '../constants/application_constants';
// TODO: Create Unittests that test all functions
- /**
- * Takes a key-value object of this form:
- *
- * {
- * 'page': 1,
- * 'pageSize': 10
- * }
- *
- * and converts it to a query-parameter, which you can append to your URL.
- * The return looks like this:
- *
- * ?page=1&page_size=10
- *
- * CamelCase gets converted to snake_case!
- *
- */
-export function argsToQueryParams(obj) {
-
- obj = sanitize(obj);
-
- return Object
- .keys(obj)
- .map((key, i) => {
- let s = '';
-
- if(i === 0) {
- s += '?';
- } else {
- s += '&';
- }
-
- let snakeCaseKey = key.replace(/[A-Z]/, (match) => '_' + match.toLowerCase());
-
- return s + snakeCaseKey + '=' + encodeURIComponent(obj[key]);
- })
- .join('');
-}
-
-/**
- * Takes a string and a boolean and generates a string query parameter for
- * an API call.
- */
-export function generateOrderingQueryParams(orderBy, orderAsc) {
- let interpolation = '';
-
- if(!orderAsc) {
- interpolation += '-';
- }
-
- return interpolation + orderBy;
-}
-
export function status(response) {
if (response.status >= 200 && response.status < 300) {
return response;
@@ -68,14 +16,21 @@ export function status(response) {
export function getCookie(name) {
let parts = document.cookie.split(';');
-
+
for(let i = 0; i < parts.length; i++) {
- if(parts[i].indexOf(AppConstants.csrftoken + '=') > -1) {
+ if(parts[i].indexOf(name + '=') > -1) {
return parts[i].split('=').pop();
}
}
}
+export function setCookie(key, value, days) {
+ const exdate = moment();
+ exdate.add(days, 'days');
+ value = window.escape(value) + ((days === null) ? '' : `; expires= ${exdate.utc()}`);
+ document.cookie = `${key}=${value}`;
+}
+
/*
Given a url for an image, this method fetches it and returns a promise that resolves to
@@ -111,4 +66,4 @@ export function fetchImageAsBlob(url) {
xhr.send();
});
-}
\ No newline at end of file
+}
diff --git a/js/utils/file_utils.js b/js/utils/file_utils.js
index 964ba7b8..3454404a 100644
--- a/js/utils/file_utils.js
+++ b/js/utils/file_utils.js
@@ -2,6 +2,7 @@
import Q from 'q';
import SparkMD5 from 'spark-md5';
+import Moment from 'moment';
import { getLangText } from './lang_utils';
@@ -37,7 +38,7 @@ export function computeHashOfFile(file) {
let spark = new SparkMD5.ArrayBuffer();
let fileReader = new FileReader();
- let startTime = new Date();
+ let startTime = new Moment();
// comment: We should convert this to es6 at some point, however if so please consider that
// an arrow function will get rid of the function's scope...
@@ -53,7 +54,7 @@ export function computeHashOfFile(file) {
console.info('computed hash %s (took %d s)',
fileHash,
- Math.round(((new Date() - startTime) / 1000) % 60)); // Compute hash
+ Math.round(((new Moment() - startTime) / 1000) % 60)); // Compute hash
let blobTextFile = makeTextFile(fileHash, file);
resolve(blobTextFile);
diff --git a/js/utils/form_utils.js b/js/utils/form_utils.js
index 7f9cfb07..c15eb067 100644
--- a/js/utils/form_utils.js
+++ b/js/utils/form_utils.js
@@ -3,13 +3,39 @@
import { getLangText } from './lang_utils';
/**
- * Generates a message for submitting a form
- * @param {string} aclName Enum name of a acl
- * @param {string} entities Already computed name of entities
- * @param {string} senderName Name of the sender
- * @return {string} Completed message
+ * Get the data ids of the given piece or editions.
+ * @param {boolean} isPiece Is the given entities parameter a piece? (False: array of editions)
+ * @param {(object|object[])} pieceOrEditions Piece or array of editions
+ * @return {(object|object[])} Data IDs of the pieceOrEditions for the form
*/
-export function getAclFormMessage(aclName, entities, senderName) {
+export function getAclFormDataId(isPiece, pieceOrEditions) {
+ if (isPiece) {
+ return {piece_id: pieceOrEditions.id};
+ } else {
+ return {bitcoin_id: pieceOrEditions.map(function(edition){
+ return edition.bitcoin_id;
+ }).join()};
+ }
+}
+
+/**
+ * Generates a message for submitting a form
+ * @param {object} options Options object for creating the message:
+ * @param {string} options.aclName Enum name of an acl
+ * @param {(object|object[])} options.entities Piece or array of Editions
+ * @param {boolean} options.isPiece Is the given entities parameter a piece? (False: array of editions)
+ * @param {string} [options.senderName] Name of the sender
+ * @return {string} Completed message
+ */
+export function getAclFormMessage(options) {
+ if (!options || options.aclName === undefined || options.isPiece === undefined ||
+ !(typeof options.entities === 'object' || options.entities.constructor === Array)) {
+ throw new Error('You must specify an acl class, entities in the correct format, and entity type');
+ }
+
+ let aclName = options.aclName;
+ let entityTitles = options.isPiece ? getTitlesStringOfPiece(options.entities)
+ : getTitlesStringOfEditions(options.entities);
let message = '';
message += getLangText('Hi');
@@ -32,7 +58,7 @@ export function getAclFormMessage(aclName, entities, senderName) {
}
message += ':\n';
- message += entities;
+ message += entityTitles;
if(aclName === 'acl_transfer' || aclName === 'acl_loan' || aclName === 'acl_consign') {
message += getLangText('to you');
@@ -44,10 +70,22 @@ export function getAclFormMessage(aclName, entities, senderName) {
throw new Error('Your specified aclName did not match a an acl class.');
}
- message += '\n\n';
- message += getLangText('Truly yours,');
- message += '\n';
- message += senderName;
+ if (options.senderName) {
+ message += '\n\n';
+ message += getLangText('Truly yours,');
+ message += '\n';
+ message += options.senderName;
+ }
return message;
-}
\ No newline at end of file
+}
+
+function getTitlesStringOfPiece(piece){
+ return '\"' + piece.title + '\"';
+}
+
+function getTitlesStringOfEditions(editions) {
+ return editions.map(function(edition) {
+ return '- \"' + edition.title + ', ' + getLangText('edition') + ' ' + edition.edition_number + '\"\n';
+ }).join('');
+}
diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js
index 7c13f9b5..e717fa75 100644
--- a/js/utils/general_utils.js
+++ b/js/utils/general_utils.js
@@ -1,29 +1,21 @@
'use strict';
/**
- * Takes an object and deletes all keys that are
- *
- * tagged as false by the passed in filter function
+ * Takes an object and returns a shallow copy without any keys
+ * that fail the passed in filter function.
+ * Does not modify the passed in object.
*
* @param {object} obj regular javascript object
* @return {object} regular javascript object without null values or empty strings
*/
export function sanitize(obj, filterFn) {
- if(!filterFn) {
+ if (!filterFn) {
// By matching null with a double equal, we can match undefined and null
// http://stackoverflow.com/a/15992131
filterFn = (val) => val == null || val === '';
}
- Object
- .keys(obj)
- .map((key) => {
- if(filterFn(obj[key])) {
- delete obj[key];
- }
- });
-
- return obj;
+ return omitFromObject(obj, filterFn);
}
/**
@@ -82,8 +74,8 @@ export function formatText() {
});
}
-/*
- Checks a list of objects for key duplicates and returns a boolean
+/**
+ * Checks a list of objects for key duplicates and returns a boolean
*/
function _doesObjectListHaveDuplicates(l) {
let mergedList = [];
@@ -121,35 +113,7 @@ export function mergeOptions(...l) {
throw new Error('The objects you submitted for merging have duplicates. Merge aborted.');
}
- let newObj = {};
-
- for(let i = 1; i < l.length; i++) {
- newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i]));
- }
-
- return newObj;
-}
-
-/**
- * Merges a number of objects even if there're having duplicates.
- *
- * DOES NOT RETURN AN ERROR!
- *
- * Takes a list of object and merges their keys to one object.
- * Uses mergeOptions for two objects.
- * @param {[type]} l [description]
- * @return {[type]} [description]
- */
-export function mergeOptionsWithDuplicates(...l) {
- // If the objects submitted in the list have duplicates,in their key names,
- // abort the merge and tell the function's user to check his objects.
- let newObj = {};
-
- for(let i = 1; i < l.length; i++) {
- newObj = _mergeOptions(newObj, _mergeOptions(l[i - 1], l[i]));
- }
-
- return newObj;
+ return Object.assign({}, ...l);
}
/**
@@ -165,25 +129,6 @@ export function update(a, ...l) {
return a;
}
-/**
- * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
- * @param obj1
- * @param obj2
- * @returns obj3 a new object based on obj1 and obj2
- * Taken from: http://stackoverflow.com/a/171256/1263876
- */
-function _mergeOptions(obj1, obj2) {
- let obj3 = {};
-
- for (let attrname in obj1) {
- obj3[attrname] = obj1[attrname];
- }
- for (let attrname in obj2) {
- obj3[attrname] = obj2[attrname];
- }
- return obj3;
-}
-
/**
* Escape HTML in a string so it can be injected safely using
* React's `dangerouslySetInnerHTML`
@@ -196,14 +141,41 @@ export function escapeHTML(s) {
return document.createElement('div').appendChild(document.createTextNode(s)).parentNode.innerHTML;
}
-export function excludePropFromObject(obj, propList){
- let clonedObj = mergeOptions({}, obj);
- for (let item in propList){
- if (clonedObj[propList[item]]){
- delete clonedObj[propList[item]];
+/**
+ * Returns a copy of the given object's own and inherited enumerable
+ * properties, omitting any keys that pass the given filter function.
+ */
+function filterObjOnFn(obj, filterFn) {
+ const filteredObj = {};
+
+ for (let key in obj) {
+ const val = obj[key];
+ if (filterFn == null || !filterFn(val, key)) {
+ filteredObj[key] = val;
}
}
- return clonedObj;
+
+ return filteredObj;
+}
+
+/**
+ * Similar to lodash's _.omit(), this returns a copy of the given object's
+ * own and inherited enumerable properties, omitting any keys that are
+ * in the given array or whose value pass the given filter function.
+ * @param {object} obj Source object
+ * @param {array|function} filter Array of key names to omit or function to invoke per iteration
+ * @return {object} The new object
+*/
+export function omitFromObject(obj, filter) {
+ if (filter && filter.constructor === Array) {
+ return filterObjOnFn(obj, (_, key) => {
+ return filter.indexOf(key) >= 0;
+ });
+ } else if (filter && typeof filter === 'function') {
+ return filterObjOnFn(obj, filter);
+ } else {
+ throw new Error('The given filter is not an array or function. Exclude aborted');
+ }
}
/**
diff --git a/js/utils/lang_utils.js b/js/utils/lang_utils.js
index f2e2fa14..ee2d292c 100644
--- a/js/utils/lang_utils.js
+++ b/js/utils/lang_utils.js
@@ -22,15 +22,15 @@ export function getLangText(s, ...args) {
let lang = getLang();
try {
if(lang in languages) {
- return formatText(languages[lang][s], args);
+ return formatText(languages[lang][s], ...args);
} else {
// just use the english language
- return formatText(languages['en-US'][s], args);
+ return formatText(languages['en-US'][s], ...args);
}
} catch(err) {
//if(!(s in languages[lang])) {
//console.warn('Language-string is not in constants file. Add: "' + s + '" to the "' + lang + '" language file. Defaulting to keyname');
- return formatText(s, args);
+ return formatText(s, ...args);
//} else {
// console.error(err);
//}
diff --git a/js/utils/requests.js b/js/utils/requests.js
index 7e9c9a58..a7300634 100644
--- a/js/utils/requests.js
+++ b/js/utils/requests.js
@@ -2,24 +2,14 @@
import Q from 'q';
-import { argsToQueryParams, getCookie } from '../utils/fetch_api_utils';
-
import AppConstants from '../constants/application_constants';
-import {excludePropFromObject} from '../utils/general_utils';
+import { getCookie } from '../utils/fetch_api_utils';
+import { omitFromObject } from '../utils/general_utils';
+import { argsToQueryParams } from '../utils/url_utils';
+
class Requests {
- _merge(defaults, options) {
- let merged = {};
- for (let key in defaults) {
- merged[key] = defaults[key];
- }
- for (let key in options) {
- merged[key] = options[key];
- }
- return merged;
- }
-
unpackResponse(response) {
if (response.status >= 500) {
throw new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
@@ -112,7 +102,7 @@ class Requests {
request(verb, url, options) {
options = options || {};
- let merged = this._merge(this.httpOptions, options);
+ let merged = Object.assign({}, this.httpOptions, options);
let csrftoken = getCookie(AppConstants.csrftoken);
if (csrftoken) {
merged.headers['X-CSRFToken'] = csrftoken;
@@ -124,23 +114,23 @@ class Requests {
}
get(url, params) {
- if (url === undefined){
+ if (url === undefined) {
throw new Error('Url undefined');
}
- let paramsCopy = this._merge(params);
+ let paramsCopy = Object.assign({}, params);
let newUrl = this.prepareUrl(url, paramsCopy, true);
return this.request('get', newUrl);
}
delete(url, params) {
- let paramsCopy = this._merge(params);
+ let paramsCopy = Object.assign({}, params);
let newUrl = this.prepareUrl(url, paramsCopy, true);
return this.request('delete', newUrl);
}
- _putOrPost(url, paramsAndBody, method){
- let paramsCopy = this._merge(paramsAndBody);
- let params = excludePropFromObject(paramsAndBody, ['body']);
+ _putOrPost(url, paramsAndBody, method) {
+ let paramsCopy = Object.assign({}, paramsAndBody);
+ let params = omitFromObject(paramsAndBody, ['body']);
let newUrl = this.prepareUrl(url, params);
let body = null;
if (paramsCopy && paramsCopy.body) {
@@ -153,11 +143,11 @@ class Requests {
return this._putOrPost(url, params, 'post');
}
- put(url, params){
+ put(url, params) {
return this._putOrPost(url, params, 'put');
}
- patch(url, params){
+ patch(url, params) {
return this._putOrPost(url, params, 'patch');
}
diff --git a/js/utils/url_utils.js b/js/utils/url_utils.js
new file mode 100644
index 00000000..86c8dfc5
--- /dev/null
+++ b/js/utils/url_utils.js
@@ -0,0 +1,83 @@
+'use strict'
+
+import camelCase from 'camelcase';
+import decamelize from 'decamelize';
+import qs from 'qs';
+
+import { sanitize } from './general_utils';
+
+// TODO: Create Unittests that test all functions
+
+/**
+ * Takes a key-value dictionary of this form:
+ *
+ * {
+ * 'page': 1,
+ * 'pageSize': 10
+ * }
+ *
+ * and converts it to a query-parameter, which you can append to your URL.
+ * The return looks like this:
+ *
+ * ?page=1&page_size=10
+ *
+ * CamelCase gets converted to snake_case!
+ *
+ * @param {object} obj Query params dictionary
+ * @return {string} Query params string
+ */
+export function argsToQueryParams(obj) {
+ const sanitizedObj = sanitize(obj);
+ const queryParamObj = {};
+
+ Object
+ .keys(sanitizedObj)
+ .forEach((key) => {
+ queryParamObj[decamelize(key)] = sanitizedObj[key];
+ });
+
+ // Use bracket arrayFormat as history.js and react-router use it
+ return '?' + qs.stringify(queryParamObj, { arrayFormat: 'brackets' });
+}
+
+/**
+ * Get the current url's query params as an key-val dictionary.
+ * snake_case gets converted to CamelCase!
+ * @return {object} Query params dictionary
+ */
+export function getCurrentQueryParams() {
+ return queryParamsToArgs(window.location.search.substring(1));
+}
+
+/**
+ * Convert the given query param string into a key-val dictionary.
+ * snake_case gets converted to CamelCase!
+ * @param {string} queryParamString Query params string
+ * @return {object} Query params dictionary
+ */
+export function queryParamsToArgs(queryParamString) {
+ const qsQueryParamObj = qs.parse(queryParamString);
+ const camelCaseParamObj = {};
+
+ Object
+ .keys(qsQueryParamObj)
+ .forEach((key) => {
+ camelCaseParamObj[camelCase(key)] = qsQueryParamObj[key];
+ });
+
+ return camelCaseParamObj;
+}
+
+/**
+ * Takes a string and a boolean and generates a string query parameter for
+ * an API call.
+ */
+export function generateOrderingQueryParams(orderBy, orderAsc) {
+ let interpolation = '';
+
+ if(!orderAsc) {
+ interpolation += '-';
+ }
+
+ return interpolation + orderBy;
+}
diff --git a/package.json b/package.json
index 2b770fd3..63c6d1e0 100644
--- a/package.json
+++ b/package.json
@@ -46,8 +46,10 @@
"browser-sync": "^2.7.5",
"browserify": "^9.0.8",
"browserify-shim": "^3.8.10",
+ "camelcase": "^1.2.1",
"classnames": "^1.2.2",
"compression": "^1.4.4",
+ "decamelize": "^1.1.1",
"envify": "^3.4.0",
"eslint": "^0.22.1",
"eslint-plugin-react": "^2.5.0",
@@ -73,6 +75,7 @@
"object-assign": "^2.0.0",
"opn": "^3.0.2",
"q": "^1.4.1",
+ "qs": "^4.0.0",
"raven-js": "^1.1.19",
"react": "0.13.2",
"react-bootstrap": "0.25.1",
diff --git a/sass/ascribe_custom_style.scss b/sass/ascribe_custom_style.scss
index 05d39027..98cce937 100644
--- a/sass/ascribe_custom_style.scss
+++ b/sass/ascribe_custom_style.scss
@@ -508,7 +508,10 @@ fieldset[disabled] .btn-secondary.active {
> pre,
> select,
> span:not(.glyphicon),
+ > p,
+ > p > span,
> textarea {
+ color: $ascribe-dark-blue;
font-family: $ascribe--font;
font-weight: $ascribe--font-weight-light;
}
diff --git a/sass/ascribe_form.scss b/sass/ascribe_form.scss
index 41e17de1..1b265e91 100644
--- a/sass/ascribe_form.scss
+++ b/sass/ascribe_form.scss
@@ -23,4 +23,4 @@
@media (max-width: 550px) {
width: 100%;
}
-}
+}
\ No newline at end of file
diff --git a/sass/ascribe_property.scss b/sass/ascribe_property.scss
index e938a7c3..0bfd5496 100644
--- a/sass/ascribe_property.scss
+++ b/sass/ascribe_property.scss
@@ -30,11 +30,14 @@ $ascribe-red-error: rgb(169, 68, 66);
border-left: 3px solid rgba($ascribe-red-error, 1);
> div {
- > span {
- color: rgba($ascribe-red-error, 1);
- font-size: .9em;
- margin-right: 1em;
+ > p {
+ > span {
+ color: rgba($ascribe-red-error, 1);
+ font-size: .9em;
+ margin-right: 1em;
+ }
}
+
> input,
> textarea {
@@ -86,10 +89,13 @@ $ascribe-red-error: rgb(169, 68, 66);
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
- > span {
- color: rgba(0, 0, 0, .5);
- font-size: .9em;
- font-weight: normal;
+ > p {
+ height: 20px;
+ margin-bottom: 0;
+ > span {
+ font-size: .9em;
+ font-weight: normal;
+ }
}
> div {
@@ -107,6 +113,11 @@ $ascribe-red-error: rgb(169, 68, 66);
margin-top: 0 !important;
}
+ > .upload-button-wrapper {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ }
+
> input,
> pre,
> textarea,
@@ -139,7 +150,7 @@ $ascribe-red-error: rgb(169, 68, 66);
padding: 0;
}
- > textarea{
+ > textarea {
color: #666;
margin-top: 1em;
padding: 0;
@@ -218,4 +229,4 @@ $ascribe-red-error: rgb(169, 68, 66);
> span > span {
margin-top: 0;
}
-}
+}
\ No newline at end of file
diff --git a/sass/ascribe_uploader.scss b/sass/ascribe_uploader.scss
index 7819eda0..49bc70e9 100644
--- a/sass/ascribe_uploader.scss
+++ b/sass/ascribe_uploader.scss
@@ -177,3 +177,15 @@
height: 12px;
}
+.upload-button-wrapper {
+ text-align: left;
+
+ .btn {
+ font-size: 1em;
+ margin-right: 1em;
+ }
+
+ span + .btn {
+ margin-left: 1em;
+ }
+}
\ No newline at end of file
diff --git a/sass/whitelabel/prize/index.scss b/sass/whitelabel/prize/index.scss
index 5bc75746..24645c8a 100644
--- a/sass/whitelabel/prize/index.scss
+++ b/sass/whitelabel/prize/index.scss
@@ -1,4 +1,5 @@
-@import 'sluice/sluice_custom_style';
+@import 'simple_prize/simple_prize_custom_style';
+@import 'portfolioreview/portfolioreview_custom_style';
.ascribe-prize-app {
border-radius: 0;
diff --git a/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss b/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss
new file mode 100644
index 00000000..d8ccd858
--- /dev/null
+++ b/sass/whitelabel/prize/portfolioreview/portfolioreview_custom_style.scss
@@ -0,0 +1,100 @@
+$pr--nav-fg-prim-color: black;
+$pr--button-color: $pr--nav-fg-prim-color;
+
+.client--portfolioreview {
+ padding-top: 0 !important;
+
+ .btn-wide,
+ .btn-default {
+ background-color: $pr--button-color;
+ border-color: $pr--button-color;
+
+ &:hover,
+ &:active,
+ &:focus,
+ &:active:hover,
+ &:active:focus,
+ &:active.focus,
+ &.active:hover,
+ &.active:focus,
+ &.active.focus {
+ background-color: lighten($pr--button-color, 20%);
+ border-color: lighten($pr--button-color, 20%);
+ }
+ }
+
+ .ascribe-property {
+ > p > span:not(> .span),
+ > textarea,
+ > input {
+ color: $pr--nav-fg-prim-color;
+ }
+ }
+
+ .ascribe-property-wrapper:hover {
+ border-left-color: lighten($pr--nav-fg-prim-color, 60%);
+ }
+
+ .is-focused {
+ border-left-color: $pr--nav-fg-prim-color !important;
+ background-color: lighten($pr--nav-fg-prim-color, 95%);
+ }
+
+ .register-piece--info {
+ text-align: center;
+
+ h1, h2 {
+ font-variant: small-caps;
+ }
+
+ h1 {
+ font-size: 5em;
+ color: #757575;
+ }
+
+ h2 {
+ font-size: 1.25em;
+ }
+
+ p {
+ margin-bottom: 0;
+ }
+
+ p + p {
+ margin-top: 0;
+ }
+
+ p:last-child {
+ margin-bottom: 1.5em;
+ }
+ }
+
+ .register-piece--form {
+ margin-top: 2em;
+ margin-bottom: 3em;
+
+ form {
+ border-top: none;
+ border-bottom: none;
+ }
+ }
+
+ .piece--hero {
+ text-align: center;
+ padding: 1em 0 1em 0;
+ margin-bottom: 3em;
+ border-bottom: 1px solid rgba(0, 0, 0, .1);
+
+ background-color: white;
+
+ h2 {
+ margin-top: 0;
+ }
+ }
+
+ .ascribe-property {
+ > p > span:not(> .span) {
+ text-transform: capitalize;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sass/whitelabel/prize/sluice/sluice_custom_style.scss b/sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss
similarity index 100%
rename from sass/whitelabel/prize/sluice/sluice_custom_style.scss
rename to sass/whitelabel/prize/simple_prize/simple_prize_custom_style.scss
diff --git a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss
index 52affdaf..70a5cd18 100644
--- a/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss
+++ b/sass/whitelabel/wallet/ikonotv/ikonotv_custom_style.scss
@@ -178,7 +178,7 @@ $ikono--font: 'Helvetica Neue', 'Helvetica', sans-serif !important;
border: none;
}
- .ascribe-property > span {
+ .ascribe-property > p > span {
color: white;
}