1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-31 09:07:48 +01:00

Merge pull request #68 from ascribe/PR-hotfixes

PR hotfixes
This commit is contained in:
Brett Sun 2015-12-14 16:57:51 +01:00
commit 8aae923388
13 changed files with 213 additions and 171 deletions

View File

@ -85,6 +85,7 @@ let PieceContainer = React.createClass({
// store as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
this.loadPiece();
UserActions.fetchCurrentUser();
},
@ -92,7 +93,7 @@ let PieceContainer = React.createClass({
componentDidUpdate() {
const { pieceError } = this.state;
if(pieceError && pieceError.status === 404) {
if (pieceError && pieceError.status === 404) {
this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist.")));
}
},

View File

@ -182,7 +182,7 @@ let Form = React.createClass({
delete formData.password;
}
console.logGlobal(err, false, formData);
console.logGlobal(err, formData);
if(this.props.isInline) {
let notification = new GlobalNotificationModel(getLangText('Something went wrong, please try again later'), 'danger');

View File

@ -93,7 +93,6 @@ let LoginForm = React.createClass({
<input
type="email"
placeholder={getLangText('Enter your email')}
name="email"
defaultValue={email}
required/>
</Property>
@ -103,7 +102,6 @@ let LoginForm = React.createClass({
<input
type="password"
placeholder={getLangText('Enter your password')}
name="password"
required/>
</Property>
</Form>

View File

@ -71,17 +71,9 @@ export default function UploadButton({ className = 'btn btn-default btn-sm' } =
handleOnClick() {
if(!this.state.disabled) {
let evt;
const uploadingFiles = this.getUploadingFiles();
const uploadedFile = this.getUploadedFile();
this.clearSelection();
if(uploadingFiles.length) {
this.props.handleCancelFile(uploadingFiles[0].id);
} else if(uploadedFile && !uploadedFile.s3UrlSafe) {
this.props.handleCancelFile(uploadedFile.id);
} else if(uploadedFile && uploadedFile.s3UrlSafe) {
this.props.handleDeleteFile(uploadedFile.id);
}
// First, remove any currently uploading or uploaded items
this.onClickRemove();
try {
evt = new MouseEvent('click', {
@ -99,18 +91,19 @@ export default function UploadButton({ className = 'btn btn-default btn-sm' } =
}
},
onClickCancel() {
this.clearSelection();
const uploadingFile = this.getUploadingFiles()[0];
this.props.handleCancelFile(uploadingFile.id);
},
onClickRemove() {
this.clearSelection();
const uploadingFiles = this.getUploadingFiles();
const uploadedFile = this.getUploadedFile();
this.props.handleDeleteFile(uploadedFile.id);
},
this.clearSelection();
if(uploadingFiles.length) {
this.props.handleCancelFile(uploadingFiles[0].id);
} else if(uploadedFile && !uploadedFile.s3UrlSafe) {
this.props.handleCancelFile(uploadedFile.id);
} else if(uploadedFile && uploadedFile.s3UrlSafe) {
this.props.handleDeleteFile(uploadedFile.id);
}
},
getButtonLabel() {
let { filesToUpload, fileClassToUpload } = this.props;
@ -133,7 +126,7 @@ export default function UploadButton({ className = 'btn btn-default btn-sm' } =
return (
<span>
{' ' + truncateTextAtCharIndex(uploadingFiles[0].name, 40) + ' '}
[<a onClick={this.onClickCancel}>{getLangText('cancel upload')}</a>]
[<a onClick={this.onClickRemove}>{getLangText('cancel upload')}</a>]
</span>
);
} else if(uploadedFile) {
@ -193,4 +186,4 @@ export default function UploadButton({ className = 'btn btn-default btn-sm' } =
);
}
});
}
}

View File

@ -344,6 +344,7 @@ const ReactS3FineUploader = React.createClass({
// 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();
return;
}
window.fetch(createBlobRoutine.url, {
@ -439,7 +440,7 @@ const ReactS3FineUploader = React.createClass({
onComplete(id, name, res, xhr) {
// There has been an issue with the server's connection
if (xhr && xhr.status === 0 && res.success) {
console.logGlobal(new Error('Upload succeeded with a status code 0'), false, {
console.logGlobal(new Error('Upload succeeded with a status code 0'), {
files: this.state.filesToUpload,
chunks: this.state.chunks,
xhr: this.getXhrErrorComment(xhr)
@ -497,7 +498,7 @@ const ReactS3FineUploader = React.createClass({
},
onError(id, name, errorReason, xhr) {
console.logGlobal(errorReason, false, {
console.logGlobal(errorReason, {
files: this.state.filesToUpload,
chunks: this.state.chunks,
xhr: this.getXhrErrorComment(xhr)

View File

@ -16,12 +16,13 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import AclProxy from './acl_proxy';
import EventActions from '../actions/event_actions';
import UserActions from '../actions/user_actions';
import UserStore from '../stores/user_store';
import WhitelabelActions from '../actions/whitelabel_actions';
import WhitelabelStore from '../stores/whitelabel_store';
import EventActions from '../actions/event_actions';
import HeaderNotifications from './header_notification';
@ -58,6 +59,19 @@ let Header = React.createClass({
// close the mobile expanded navigation after a click by itself.
// To get rid of this, we set the state of the component ourselves.
history.listen(this.onRouteChange);
if (this.state.currentUser && this.state.currentUser.email) {
EventActions.profileDidLoad.defer(this.state.currentUser);
}
},
componentWillUpdate(nextProps, nextState) {
const { currentUser: { email: curEmail } = {} } = this.state;
const { currentUser: { email: nextEmail } = {} } = nextState;
if (nextEmail && curEmail !== nextEmail) {
EventActions.profileDidLoad.defer(nextState.currentUser);
}
},
componentWillUnmount() {
@ -105,10 +119,6 @@ let Header = React.createClass({
onChange(state) {
this.setState(state);
if(this.state.currentUser && this.state.currentUser.email) {
EventActions.profileDidLoad.defer(this.state.currentUser);
}
},
onMenuItemClick() {

View File

@ -75,7 +75,7 @@ const PRRegisterPieceForm = React.createClass({
const additionalDataFormData = additionalDataForm.getFormData();
// composing data for piece registration
let registerPieceFormData = registerPieceForm.getFormData();
const registerPieceFormData = registerPieceForm.getFormData();
registerPieceFormData.digital_work_key = digitalWorkKey.state.value;
registerPieceFormData.thumbnail_file = thumbnailKey.state.value;
registerPieceFormData.terms = true;
@ -83,33 +83,33 @@ const PRRegisterPieceForm = React.createClass({
// 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();
});
.then(({ piece, notification }) => {
this.setState({piece}, () => {
supportingMaterials.refs.input.createBlobRoutine();
proofOfPayment.refs.input.createBlobRoutine();
});
setCookie(currentUser.email, piece.id);
setCookie(currentUser.email, piece.id);
return requests.post(ApiUrls.piece_extradata, {
return requests
.post(ApiUrls.piece_extradata, {
body: {
extradata: additionalDataFormData,
piece_id: piece.id
},
piece_id: piece.id
})
.then(() => {
const notificationMessage = new GlobalNotificationModel(notification || getLangText('You have successfully submitted "%s" to Portfolio Review 2015', piece.title), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notificationMessage);
});
} 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);
.catch((err) => {
const notificationMessage = new GlobalNotificationModel(getLangText("Oops! We weren't able to send your submission. Contact: support@ascribe.io"), 'danger', 5000);
GlobalNotificationActions.appendGlobalNotification(notificationMessage);
console.logGlobal(new Error('Portfolio Review piece registration failed'), err);
});
},
@ -167,7 +167,7 @@ const PRRegisterPieceForm = React.createClass({
} else {
return (
<button
type="submit"
type="button"
className="btn btn-default btn-wide"
disabled={!(digitalWorkKeyReady && thumbnailKeyReady && proofOfPaymentReady && supportingMaterialsReady)}
onClick={this.submit}>
@ -191,7 +191,7 @@ const PRRegisterPieceForm = React.createClass({
label={getLangText('Full name')}>
<input
type="text"
placeholder="(e.g. Andy Warhol)"
placeholder={getLangText('(e.g. Andy Warhol)')}
required/>
</Property>
<Property
@ -199,7 +199,7 @@ const PRRegisterPieceForm = React.createClass({
label={getLangText('Title of the Work')}>
<input
type="text"
placeholder="(e.g. 32 Campbell's Soup Cans)"
placeholder={getLangText("(e.g. 32 Campbell's Soup Cans)")}
required/>
</Property>
<Property
@ -207,7 +207,7 @@ const PRRegisterPieceForm = React.createClass({
label={getLangText('Year of creation')}>
<input
type="number"
placeholder="(e.g. 1962)"
placeholder={getLangText('(e.g. 1962)')}
min={1}
required/>
</Property>
@ -224,25 +224,51 @@ const PRRegisterPieceForm = React.createClass({
className="ascribe-form-bordered"
ref="additionalDataForm">
<Property
name='artist_bio'
name='1-date_of_birth'
label={getLangText('Date of Birth')}>
<input
type="number"
placeholder={getLangText('(e.g. 1962)')}
min={1900}
required/>
</Property>
<Property
name='2-artist_bio'
label={getLangText('Biography')}>
<InputTextAreaToggable
rows={1}
placeholder={getLangText('Enter your biography')}/>
</Property>
<Property
name='exhibition'
name='3-exhibition'
label={getLangText('Exhibition / Publication history (optional)')}>
<InputTextAreaToggable
rows={1}
placeholder={getLangText('Enter exhibitions and publication history')}/>
</Property>
<Property
name='contact_information'
label={getLangText('Contact information')}>
<InputTextAreaToggable
rows={1}
placeholder={getLangText('Enter your contact information (phone/website)')}/>
name='4-phone_number'
label={getLangText('Phone Number')}>
<input
type="tel"
placeholder={getLangText('Enter your phone number')}
required/>
</Property>
<Property
name='5-email'
label={getLangText('Email Address')}>
<input
type="email"
placeholder={getLangText('Enter your email')}
required/>
</Property>
<Property
name='6-website'
label={getLangText('Website')}>
<input
type="url"
placeholder={getLangText('Enter your website')}
required/>
</Property>
</Form>
<Form
@ -311,7 +337,7 @@ const PRRegisterPieceForm = React.createClass({
createBlobRoutine={this.getCreateBlobRoutine()}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'other_data'
fileClass: 'otherdata'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
@ -333,7 +359,7 @@ const PRRegisterPieceForm = React.createClass({
createBlobRoutine={this.getCreateBlobRoutine()}
keyRoutine={{
url: AppConstants.serverUrl + 's3/key/',
fileClass: 'other_data'
fileClass: 'otherdata'
}}
validation={{
itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit,
@ -372,4 +398,4 @@ const PRRegisterPieceForm = React.createClass({
}
});
export default PRRegisterPieceForm;
export default PRRegisterPieceForm;

View File

@ -5,32 +5,18 @@ import { Link } from 'react-router';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import UserStore from '../../../../../stores/user_store';
import UserActions from '../../../../../actions/user_actions';
import { getLangText } from '../../../../../utils/lang_utils';
const PRHero = React.createClass({
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser.defer();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
propTypes: {
currentUser: React.PropTypes.shape({
email: React.PropTypes.object
})
},
render() {
const { currentUser } = this.state;
const { currentUser } = this.props;
return (
<div className="piece--hero">

View File

@ -6,6 +6,8 @@ import GlobalNotification from '../../../global_notification';
import Hero from './components/pr_hero';
import Header from '../../../header';
import EventActions from '../../../../actions/event_actions';
import UserStore from '../../../../stores/user_store';
import UserActions from '../../../../actions/user_actions';
@ -30,6 +32,19 @@ let PRApp = React.createClass({
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
if (this.state.currentUser && this.state.currentUser.email) {
EventActions.profileDidLoad.defer(this.state.currentUser);
}
},
componentWillUpdate(nextProps, nextState) {
const { currentUser: { email: curEmail } = {} } = this.state;
const { currentUser: { email: nextEmail } = {} } = nextState;
if (nextEmail && curEmail !== nextEmail) {
EventActions.profileDidLoad.defer(nextState.currentUser);
}
},
componentWillUnmount() {
@ -49,7 +64,7 @@ let PRApp = React.createClass({
if (currentUser && currentUser.email && history.isActive(`/pieces/${getCookie(currentUser.email)}`)) {
header = <Hero />;
header = <Hero currentUser={currentUser} />;
style = { paddingTop: '0 !important' };
} else if(currentUser && (currentUser.is_admin || currentUser.is_jury || currentUser.is_judge)) {
header = <Header routes={routes} />;

View File

@ -6,6 +6,9 @@ import Moment from 'moment';
import StarRating from 'react-star-rating';
import ReactError from '../../../../../../mixins/react_error';
import { ResourceNotFoundError } from '../../../../../../models/errors';
import PieceActions from '../../../../../../actions/piece_actions';
import PieceStore from '../../../../../../stores/piece_store';
@ -54,6 +57,8 @@ let PieceContainer = React.createClass({
params: React.PropTypes.object
},
mixins: [ReactError],
getInitialState() {
return mergeOptions(
PieceStore.getState(),
@ -63,14 +68,15 @@ let PieceContainer = React.createClass({
componentDidMount() {
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
// the piece detail a second time
PieceActions.updatePiece({});
PieceActions.fetchOne(this.props.params.pieceId);
UserActions.fetchCurrentUser();
},
// This is done to update the container when the user clicks on the prev or next
@ -82,6 +88,14 @@ let PieceContainer = React.createClass({
}
},
componentDidUpdate() {
const { pieceError } = this.state;
if (pieceError && pieceError.status === 404) {
this.throws(new ResourceNotFoundError(getLangText("Oops, the piece you're looking for doesn't exist.")));
}
},
componentWillUnmount() {
PieceStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange);
@ -92,10 +106,6 @@ let PieceContainer = React.createClass({
this.setState(state);
},
loadPiece() {
PieceActions.fetchOne(this.props.params.pieceId);
},
getActions() {
if (this.state.piece &&
this.state.piece.notifications &&
@ -112,7 +122,7 @@ let PieceContainer = React.createClass({
render() {
if(this.state.piece && this.state.piece.id) {
/*
This really needs a refactor!
- Tim
@ -122,7 +132,7 @@ let PieceContainer = React.createClass({
let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) ||
(this.state.currentUser.is_judge && !this.state.piece.selected )) ?
null : this.state.piece.artist_name;
// Only show the artist email if you are a judge and the piece is shortlisted
let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ?
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null;
@ -146,7 +156,7 @@ let PieceContainer = React.createClass({
<NavigationHeader
piece={this.state.piece}
currentUser={this.state.currentUser}/>
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
<DetailProperty label={getLangText('BY')} value={artistName} />
<DetailProperty label={getLangText('DATE')} value={Moment(this.state.piece.date_created, 'YYYY-MM-DD').year()} />
@ -429,18 +439,21 @@ let PrizePieceDetails = React.createClass({
},
render() {
if (this.props.piece
&& this.props.piece.prize
&& this.props.piece.prize.name
&& Object.keys(this.props.piece.extra_data).length !== 0){
const { piece } = this.props;
if (piece &&
piece.prize &&
piece.prize.name &&
Object.keys(piece.extra_data).length !== 0) {
return (
<CollapsibleParagraph
title={getLangText('Prize Details')}
defaultExpanded={true}>
<Form ref='form'>
{Object.keys(this.props.piece.extra_data).map((data) => {
let label = data.replace('_', ' ');
const value = this.props.piece.extra_data[data] || 'N/A';
{Object.keys(piece.extra_data).sort().map((data) => {
// Remove leading number (for sorting), if any, and underscores with spaces
let label = data.replace(/^\d-/, '').replace(/_/g, ' ');
const value = piece.extra_data[data] || 'N/A';
return (
<Property
@ -460,8 +473,8 @@ let PrizePieceDetails = React.createClass({
isReadyForFormSubmission={() => {}}
editable={false}
overrideForm={true}
pieceId={this.props.piece.id}
otherData={this.props.piece.other_data}
pieceId={piece.id}
otherData={piece.other_data}
multiple={true} />
</Form>
</CollapsibleParagraph>

View File

@ -13,8 +13,7 @@ import AppConstants from '../constants/application_constants';
* @param {boolean} ignoreSentry Defines whether or not the error should be submitted to Sentry
* @param {string} comment Will also be submitted to Sentry, but will not be logged
*/
function logGlobal(error, ignoreSentry = AppConstants.errorMessagesToIgnore.indexOf(error.message) > -1,
comment) {
function logGlobal(error, comment, ignoreSentry = AppConstants.errorMessagesToIgnore.indexOf(error.message) > -1) {
console.error(error);
if(!ignoreSentry) {
@ -24,7 +23,6 @@ function logGlobal(error, ignoreSentry = AppConstants.errorMessagesToIgnore.inde
Raven.captureException(error);
}
}
}
export function initLogging() {
@ -36,4 +34,4 @@ export function initLogging() {
window.onerror = Raven.process;
console.logGlobal = logGlobal;
}
}

View File

@ -10,71 +10,67 @@ import { argsToQueryParams } from '../utils/url_utils';
class Requests {
unpackResponse(response) {
if (response.status >= 500) {
let err = new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
return response
.text()
.then((resText) => {
const resJson = JSON.parse(resText);
err = new Error(resJson.errors.pop());
// ES6 promises don't have a .finally() clause so
// we fake that here by forcing the .catch() clause
// to run
return Promise.reject();
})
.catch(() => { throw err; });
}
return Q.Promise((resolve, reject) => {
response.text()
.then((responseText) => {
// If the responses' body does not contain any data,
// fetch will resolve responseText to the string 'None'.
// If this is the case, we can not try to parse it as JSON.
if(responseText !== 'None') {
let body = JSON.parse(responseText);
if(body && body.errors) {
let error = new Error('Form Error');
error.json = body;
reject(error);
} else if(body && body.detail) {
reject(new Error(body.detail));
} else if('success' in body && !body.success) {
let error = new Error('Client Request Error');
error.json = {
status: response.status,
statusText: response.statusText,
type: response.type,
url: response.url
};
reject(error);
} else {
resolve(body);
}
} else {
if(response.status >= 400) {
reject(new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url));
} else {
resolve({});
}
}
}).catch(reject);
});
}
handleError(url) {
return (err) => {
if (err instanceof TypeError) {
unpackResponse(url) {
return (response) => {
if (response == null) {
throw new Error('For: ' + url + ' - Server did not respond to the request. (Not even displayed a 500)');
} else {
throw err;
}
};
if (response.status >= 500) {
let err = new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url);
return response
.text()
.then((resText) => {
const resJson = JSON.parse(resText);
err = new Error(resJson.errors.pop());
// ES6 promises don't have a .finally() clause so
// we fake that here by forcing the .catch() clause
// to run
return Promise.reject();
})
.catch(() => { throw err; });
}
return Q.Promise((resolve, reject) => {
response.text()
.then((responseText) => {
// If the responses' body does not contain any data,
// fetch will resolve responseText to the string 'None'.
// If this is the case, we can not try to parse it as JSON.
if(responseText !== 'None') {
let body = JSON.parse(responseText);
if(body && body.errors) {
let error = new Error('Form Error');
error.json = body;
reject(error);
} else if(body && body.detail) {
reject(new Error(body.detail));
} else if('success' in body && !body.success) {
let error = new Error('Client Request Error');
error.json = {
status: response.status,
statusText: response.statusText,
type: response.type,
url: response.url
};
reject(error);
} else {
resolve(body);
}
} else {
if(response.status >= 400) {
reject(new Error(response.status + ' - ' + response.statusText + ' - on URL:' + response.url));
} else {
resolve({});
}
}
}).catch(reject);
});
}
}
getUrl(url) {
@ -128,8 +124,7 @@ class Requests {
}
merged.method = verb;
return fetch(url, merged)
.then(this.unpackResponse)
.catch(this.handleError(url));
.then(this.unpackResponse(url));
}
get(url, params) {

View File

@ -145,4 +145,10 @@ $pr--button-color: $pr--nav-fg-prim-color;
text-transform: capitalize;
}
}
}
// intercom
#intercom-container .intercom-launcher-button {
background-color: $pr--button-color !important;
border-color: $pr--button-color !important;
}
}