1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 09:35:10 +01:00

Merge remote-tracking branch 'remotes/origin/AD-499-whitelabel-prize-with-sluice-as-k' into AD-419-decouple-piece-registration-from-

This commit is contained in:
diminator 2015-07-14 22:53:12 +02:00
commit 73d3055329
28 changed files with 320 additions and 84 deletions

View File

@ -27,6 +27,9 @@
<link rel="stylesheet" href="//brick.a.ssl.fastly.net/Source+Sans+Pro:400,600,700,900">
<link rel="stylesheet" href="<%= BASE_URL %>static/css/main.css">
<link rel="stylesheet" href="<%= BASE_URL %>static/css/maps/main.css.map">
<!-- This is for sluice -->
<link href='http://fonts.googleapis.com/css?family=Nunito:400,700,300' rel='stylesheet' type='text/css'>
<script>
window.BASE_URL = '<%= BASE_URL %>';
window.SERVER_URL = '<%= SERVER_URL %>';
@ -37,6 +40,15 @@
</head>
<body>
<div id="main"></div>
<!-- Intercom library -->
<script>
(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;
s.src='https://widget.intercom.io/widget/{app_id}';
var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()
</script>
<!-- actual app -->
<script src="<%= BASE_URL %>static/js/app.js"></script>
</body>
</html>

View File

@ -8,6 +8,7 @@ import Router from 'react-router';
import fetch from 'isomorphic-fetch';
import ApiUrls from './constants/api_urls';
import { updateApiUrls } from './constants/api_urls';
import appConstants from './constants/application_constants';
import getRoutes from './routes';
import requests from './utils/requests';
@ -41,6 +42,7 @@ class AppGateway {
try {
settings = getSubdomainSettings(subdomain);
appConstants.whitelabel = settings;
updateApiUrls(settings.type, subdomain);
this.load(settings.type);
} catch(err) {
// if there are no matching subdomains, we're routing

View File

@ -15,11 +15,20 @@ let AclProxy = React.createClass({
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
]).isRequired,
aclObject: React.PropTypes.object.isRequired,
aclName: React.PropTypes.string.isRequired
aclObject: React.PropTypes.object,
aclName: React.PropTypes.string,
show: React.PropTypes.bool
},
render() {
if(this.props.show) {
return (
<span>
{this.props.children}
</span>
);
} else {
if(this.props.aclObject) {
if(this.props.aclObject[this.props.aclName]) {
return (
<span>
@ -33,6 +42,9 @@ let AclProxy = React.createClass({
return null;
}
}
}
return null;
}
});
export default AclProxy;

View File

@ -16,6 +16,9 @@ import EditionListActions from '../../actions/edition_list_actions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import AclProxy from '../acl_proxy';
import SubmitToPrizeButton from '../ascribe_buttons/submit_to_prize_button';
import { getLangText } from '../../utils/lang_utils';
let Link = Router.Link;
@ -125,6 +128,20 @@ let AccordionListItem = React.createClass({
piece={this.props.content}
toggleCreateEditionsDialog={this.toggleCreateEditionsDialog}
onPollingSuccess={this.onPollingSuccess}/>
<AclProxy
show={this.props.content.prize_name === null}>
<SubmitToPrizeButton
className="pull-right"
piece={this.props.content} />
</AclProxy>
<AclProxy
show={this.props.content.prize_name}>
<button
disabled
className="btn btn-default btn-xs pull-right">
{getLangText('Submitted to prize')} <span className="glyphicon glyphicon-ok" aria-hidden="true"></span>
</button>
</AclProxy>
</div>
</div>
<span style={{'clear': 'both'}}></span>

View File

@ -0,0 +1,23 @@
'use strict';
import React from 'react';
import classNames from 'classnames';
import { getLangText } from '../../utils/lang_utils';
let SubmitToPrizeButton = React.createClass({
propTypes: {
className: React.PropTypes.string
},
render() {
return (
<button
className={classNames('btn', 'btn-default', 'btn-xs', this.props.className)}>
{getLangText('Submit to prize')}
</button>
);
}
});
export default SubmitToPrizeButton;

View File

@ -56,9 +56,11 @@ let Form = React.createClass({
getFormData(){
let data = {};
console.log(this.refs);
for (let ref in this.refs){
data[this.refs[ref].props.name] = this.refs[ref].state.value;
}
console.log(data);
if ('getFormData' in this.props){
data = mergeOptionsWithDuplicates(data, this.props.getFormData());
}

View File

@ -126,7 +126,14 @@ let SignupForm = React.createClass({
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox/>
<InputCheckbox>
<span>
{' ' + getLangText('I agree to the Terms of Service') + ' '}
(<a href="/terms" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')}
</a>)
</span>
</InputCheckbox>
</Property>
</Form>
);

View File

@ -2,12 +2,13 @@
import React from 'react';
import { getLangText } from '../../utils/lang_utils';
let InputCheckbox = React.createClass({
propTypes: {
required: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
]).isRequired
},
getInitialState() {
@ -16,12 +17,22 @@ let InputCheckbox = React.createClass({
};
},
handleFocus() {
handleFocus(event) {
this.refs.checkbox.getDOMNode().checked = !this.refs.checkbox.getDOMNode().checked;
// This is calling property.js's method handleChange which
// expects an event object
// Since we don't have a valid one, we'll just manipulate the one we get and send
// it to handleChange
event.target.value = this.refs.checkbox.getDOMNode().checked;
this.props.onChange(event);
event.stopPropagation();
this.setState({
show: this.refs.checkbox.getDOMNode().checked,
value: this.refs.checkbox.getDOMNode().checked
});
},
render() {
@ -31,12 +42,7 @@ let InputCheckbox = React.createClass({
onFocus={this.handleFocus}>
<input type="checkbox" ref="checkbox"/>
<span className="checkbox">
<span>
{' ' + getLangText('I agree to the Terms of Service') + ' '}
(<a href="/terms" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')}
</a>)
</span>
{this.props.children}
</span>
</span>
);

View File

@ -47,10 +47,19 @@ let Property = React.createClass({
},
componentWillReceiveProps() {
// In order to set this.state.value from another component
// the state of value should only be set if its not undefined and
// actually references something
if(typeof this.refs.input.getDOMNode().value !== 'undefined') {
this.setState({
initialValue: this.refs.input.getDOMNode().defaultValue,
value: this.refs.input.getDOMNode().value
});
}
this.setState({
initialValue: this.refs.input.getDOMNode().defaultValue
});
},
reset(){
@ -67,11 +76,13 @@ let Property = React.createClass({
},
handleChange(event) {
this.props.handleChange(event);
if ('onChange' in this.props) {
this.props.onChange(event);
}
this.setState({value: event.target.value});
this.setState({value: true});
},
handleFocus() {

View File

@ -4,16 +4,17 @@ import React from 'react';
import Input from 'react-bootstrap/lib/Input';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import ButtonLink from 'react-router-bootstrap/lib/ButtonLink';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import { getLangText } from '../../utils/lang_utils';
let PieceListToolbar = React.createClass({
propTypes: {
className: React.PropTypes.string,
searchFor: React.PropTypes.func
searchFor: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element
])
},
searchFor() {
@ -29,6 +30,9 @@ let PieceListToolbar = React.createClass({
<div className="row">
<div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<div className="row">
<span className="pull-left">
{this.props.children}
</span>
<span className="pull-right search-bar">
<Input
type='text'

View File

@ -163,6 +163,13 @@ var ReactS3FineUploader = React.createClass({
}
},
componentWillUnmount() {
// Without this method, fineuploader will continue to try to upload artworks
// even though this component is not mounted any more.
// Therefore we cancel all uploads
this.state.uploader.cancelAll();
},
propsToConfig() {
let objectProperties = this.props.objectProperties;
objectProperties.key = this.requestKey;

View File

@ -27,8 +27,18 @@ import { getLangText } from '../utils/lang_utils';
let Header = React.createClass({
propTypes: {
showAddWork: React.PropTypes.bool
},
mixins: [Router.Navigation, Router.State],
getDefaultProps() {
return {
showAddWork: true
};
},
getInitialState() {
return mergeOptions(WhitelabelStore.getState(), UserStore.getState());
},
@ -48,6 +58,8 @@ let Header = React.createClass({
handleLogout(){
UserActions.logoutCurrentUser();
Alt.flush();
// kill intercom (with fire)
window.Intercom('shutdown');
this.transitionTo('login');
},
@ -66,22 +78,30 @@ let Header = React.createClass({
getPoweredBy(){
if (this.state.whitelabel.logo) {
return (
<div className="row ascribe-subheader">
<div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
<div className="row">
<li>
<a className="pull-right" href="https://www.ascribe.io/" target="_blank">
<span id="powered">{getLangText('powered by')} </span>
<span>ascribe </span>
<span className="glyph-ascribe-spool-chunked ascribe-color"></span>
</a>
</div>
</div>
</div>);
</li>
);
}
return null;
},
onChange(state) {
this.setState(state);
if(this.state.currentUser && this.state.currentUser.email) {
// bootup intercom if the user is logged in
window.Intercom('boot', {
app_id: 'oboxh5w1',
email: this.state.currentUser.email,
widget: {
activator: '#IntercomDefaultWidget'
}
});
}
},
render() {
@ -99,7 +119,7 @@ let Header = React.createClass({
);
collection = <NavItemLink to="pieces" query={this.getQuery()}>{getLangText('COLLECTION')}</NavItemLink>;
addNewWork = <NavItemLink to="register_piece">+ {getLangText('NEW WORK')}</NavItemLink>;
addNewWork = this.props.showAddWork ? <NavItemLink to="register_piece">+ {getLangText('NEW WORK')}</NavItemLink> : null;
}
else {
account = <NavItemLink to="login">{getLangText('LOGIN')}</NavItemLink>;
@ -115,7 +135,9 @@ let Header = React.createClass({
toggleNavKey={0}
fixedTop={true}>
<CollapsibleNav eventKey={0}>
<Nav navbar left />
<Nav navbar left>
{this.getPoweredBy()}
</Nav>
<Nav navbar right>
<HeaderNotificationDebug show={false}/>
{addNewWork}
@ -125,7 +147,6 @@ let Header = React.createClass({
</Nav>
</CollapsibleNav>
</Navbar>
{this.getPoweredBy()}
</div>
);
}

View File

@ -20,7 +20,8 @@ import AppConstants from '../constants/application_constants';
let PieceList = React.createClass({
propTypes: {
redirectTo: React.PropTypes.string
redirectTo: React.PropTypes.string,
customSubmitButton: React.PropTypes.element
},
mixins: [Router.Navigation, Router.State],
@ -62,16 +63,6 @@ let PieceList = React.createClass({
this.state.orderAsc);
},
getPieceListToolbar() {
if(this.state.pieceListCount > 10) {
return (
<PieceListToolbar
className="ascribe-piece-list-toolbar"
searchFor={this.searchFor} />
);
}
},
getPagination() {
let currentPage = parseInt(this.getQuery().page, 10) || 1;
let totalPages = Math.ceil(this.state.pieceListCount / this.state.pageSize);
@ -101,7 +92,11 @@ let PieceList = React.createClass({
return (
<div>
{this.getPieceListToolbar()}
<PieceListToolbar
className="ascribe-piece-list-toolbar"
searchFor={this.searchFor}>
{this.props.customSubmitButton}
</PieceListToolbar>
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
<AccordionList
className="ascribe-accordion-list"

View File

@ -9,21 +9,23 @@ import GlobalNotification from '../../global_notification';
let RouteHandler = Router.RouteHandler;
let PrizeApp = React.createClass({
mixins: [Router.State],
render() {
let header = null;
if (this.isActive('pieces')) {
header = null;
if (this.isActive('landing') || this.isActive('login') || this.isActive('signup')) {
header = <Hero />;
} else {
header = <Header showAddWork={false} />;
}
return (
<div className="wp">
<Hero />
<div className="ascribe-prize-app">
{header}
<div className="wp">
<RouteHandler />
</div>
<GlobalNotification />
<div id="modal" className="container"></div>
</div>

View File

@ -11,8 +11,7 @@ let Hero = React.createClass({
<img
className="logo" src={constants.whitelabel.logo}
alt="Sluice Art Prize"
height="300px"/>
<h1>Sluice Art Prize 2015</h1>
height="200px"/>
</div>
);
}

View File

@ -14,13 +14,21 @@ let Landing = React.createClass({
<div className="container">
<div className="row">
<div className="col-xs-12 wp-landing-wrapper">
<p></p>
<h1>Sluice Art Prize 2015</h1>
<p>
This is the submission page for sluice art fair price 2015.
</p>
<ButtonGroup className="enter" bsSize="large" vertical block>
<ButtonLink to="signup">
Signup to the prize
</ButtonLink>
Already a user? <Link to="login">log in</Link>
<p>
or, already an ascribe user?
</p>
<ButtonLink to="login">
Login with ascribe
</ButtonLink>
</ButtonGroup>
</div>
</div>

View File

@ -12,7 +12,7 @@ let LoginContainer = React.createClass({
render() {
return (
<div className="ascribe-login-wrapper">
<LoginForm headerMessage="Log in" />
<LoginForm headerMessage="Log in with ascribe" />
<div className="ascribe-login-text">
I'm not a user <Link to="signup">Sign up...</Link><br/>
I forgot my password <Link to="password_reset">Rescue me...</Link>

View File

@ -10,11 +10,13 @@ let PrizePieceList = React.createClass({
render() {
return (
<div>
<PieceList
redirectTo="register_piece"
customSubmitButton={
<ButtonLink to="register_piece">
Submit a new artwork to the prize
</ButtonLink>
<PieceList redirectTo="register_piece" />
}/>
</div>
);
}

View File

@ -40,7 +40,14 @@ let PrizeRegisterPiece = React.createClass({
name="terms"
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox/>
<InputCheckbox>
<span>
{' ' + getLangText('I agree to the Terms of Service the art price') + ' '}
(<a href="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/sluice/terms.pdf" target="_blank" style={{fontSize: '0.9em', color: 'rgba(0,0,0,0.7)'}}>
{getLangText('read')}
</a>)
</span>
</InputCheckbox>
</Property>
</RegisterPiece>
);

View File

@ -0,0 +1,11 @@
'use strict';
import AppConstants from '../../../../constants/application_constants';
function getApiUrls(subdomain) {
return {
'pieces_list': AppConstants.apiEndpoint + 'prize/' + subdomain + '/pieces/'
};
}
export default getApiUrls;

View File

@ -11,6 +11,7 @@ import PrizeRegisterPiece from './components/register_piece';
import PrizePieceList from './components/piece_list';
import PieceContainer from '../../ascribe_detail/piece_container';
import EditionContainer from '../../ascribe_detail/edition_container';
import SettingsContainer from '../../../components/settings_container';
import App from './app';
import AppConstants from '../../../constants/application_constants';
@ -22,7 +23,7 @@ let baseUrl = AppConstants.baseUrl;
function getRoutes(commonRoutes) {
return (
<Route name="app" path={baseUrl} handler={App}>
<Route name="landing" path="/" handler={Landing} />
<Route name="landing" path={baseUrl} handler={Landing} />
<Route name="login" path="login" handler={LoginContainer} />
<Route name="signup" path="signup" handler={SignupContainer} />
<Route name="password_reset" path="password_reset" handler={PasswordResetContainer} />
@ -30,6 +31,7 @@ function getRoutes(commonRoutes) {
<Route name="pieces" path="collection" handler={PrizePieceList} />
<Route name="piece" path="pieces/:pieceId" handler={PieceContainer} />
<Route name="edition" path="editions/:editionId" handler={EditionContainer} />
<Route name="settings" path="settings" handler={SettingsContainer} />
</Route>
);
}

View File

@ -1,6 +1,9 @@
'use strict';
import AppConstants from './application_constants';
import getPrizeApiUrls from '../components/whitelabel/prize/constants/api_urls';
import { update } from '../utils/general_utils';
let apiUrls = {
'applications': AppConstants.apiEndpoint + 'applications/',
@ -50,4 +53,14 @@ let apiUrls = {
'delete_s3_file': AppConstants.serverUrl + 's3/delete/'
};
export function updateApiUrls(type, subdomain) {
let newUrls = {};
if (type === 'prize') {
newUrls = getPrizeApiUrls(subdomain);
}
update(apiUrls, newUrls);
}
export default apiUrls;

View File

@ -10,11 +10,12 @@ let constants = {
'apiEndpoint': window.API_ENDPOINT,
'serverUrl': window.SERVER_URL,
'baseUrl': window.BASE_URL,
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_editions', 'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view', 'acl_withdraw_transfer'],
'aclList': ['acl_coa', 'acl_consign', 'acl_delete', 'acl_download', 'acl_edit', 'acl_editions',
'acl_loan', 'acl_share', 'acl_transfer', 'acl_unconsign', 'acl_unshare', 'acl_view',
'acl_withdraw_transfer'],
'subdomains': [
{
'user': 22,
'subdomain': 'cc',
'name': 'Creative Commons France',
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/public/creativecommons/cc.logo.sm.png',
@ -22,7 +23,6 @@ let constants = {
'type': 'wallet'
},
{
'user': 22,
'subdomain': 'cc-staging',
'name': 'Creative Commons France',
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/public/creativecommons/cc.logo.sm.png',
@ -30,7 +30,6 @@ let constants = {
'type': 'wallet'
},
{
'user': 1,
'subdomain': 'sluice',
'name': 'Sluice Art Fair',
'logo': 'http://sluice.info/images/logo.gif',
@ -38,7 +37,6 @@ let constants = {
'type': 'prize'
},
{
'user': 1,
'subdomain': 'sluice-staging',
'name': 'Sluice Art Fair',
'logo': 'https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/sluice/logo.jpeg',

View File

@ -211,6 +211,19 @@ const languages = {
'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.',
'REGISTREE': 'REGISTREE',
'Submit to the prize': 'Submit to the prize',
'Submit': 'Submit',
'Artist statement': 'Artist statement',
'Enter your statement': 'Enter your statement',
'Work description': 'Work description',
'Enter the description for your work': 'Enter the description for your work',
'I agree to the Terms of Service the art price': 'I agree to the Terms of Service the art price',
'Log in': 'Log in',
'Enter ascribe': 'Enter ascribe',
'Sign up to the prize': 'Sign up to the prize',
'(e.g. andy@warhol.co.uk)': '(e.g. andy@warhol.co.uk)',
'Use a combination of minimum 10 chars and numbers': 'Use a combination of minimum 10 chars and numbers',
'Log in with ascribe': 'Log in with ascribe'
},
'de': {
'ID': 'ID',
@ -422,6 +435,19 @@ const languages = {
'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'If your email address exists in our database, you will receive a password recovery link in a few minutes.',
'REGISTREE': 'REGISTREE',
'Submit to the prize': 'Submit to the prize',
'Submit': 'Submit',
'Artist statement': 'Artist statement',
'Enter your statement': 'Enter your statement',
'Work description': 'Work description',
'Enter the description for your work': 'Enter the description for your work',
'I agree to the Terms of Service the art price': 'I agree to the Terms of Service the art price',
'Log in': 'Log in',
'Enter ascribe': 'Enter ascribe',
'Sign up to the prize': 'Sign up to the prize',
'(e.g. andy@warhol.co.uk)': '(e.g. andy@warhol.co.uk)',
'Use a combination of minimum 10 chars and numbers': 'Use a combination of minimum 10 chars and numbers',
'Log in with ascribe': 'Log in with ascribe'
},
'fr': {
'ID': 'ID',
@ -633,6 +659,19 @@ const languages = {
'read': 'read',
'If your email address exists in our database, you will receive a password recovery link in a few minutes.': 'Si votre adresse électronique existe dans notre base de données, vous recevrez un lien de récupération de mot de passe dans quelques minutes.',
'REGISTREE': 'REGISTREE',
'Submit to the prize': 'Submit to the prize',
'Submit': 'Submit',
'Artist statement': 'Artist statement',
'Enter your statement': 'Enter your statement',
'Work description': 'Work description',
'Enter the description for your work': 'Enter the description for your work',
'I agree to the Terms of Service the art price': 'I agree to the Terms of Service the art price',
'Log in': 'Log in',
'Enter ascribe': 'Enter ascribe',
'Sign up to the prize': 'Sign up to the prize',
'(e.g. andy@warhol.co.uk)': '(e.g. andy@warhol.co.uk)',
'Use a combination of minimum 10 chars and numbers': 'Use a combination of minimum 10 chars and numbers',
'Log in with ascribe': 'Log in with ascribe'
}
};

View File

View File

@ -131,6 +131,19 @@ export function mergeOptionsWithDuplicates(...l) {
return newObj;
}
/**
* In place update of a dictionary
*/
export function update(a, ...l) {
for(let i = 0; i < l.length; i++) {
for (let attrname in l[i]) {
a[attrname] = l[i][attrname];
}
}
return a;
}
/**
* Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
* @param obj1

View File

@ -1 +1,7 @@
@import 'landing'
@import 'landing';
.ascribe-prize-app {
background-color: #FDFDFD;
border-radius: 0;
padding-top: 70px;
}

View File

@ -1,17 +1,34 @@
.wp {
background-color: #CCC;
height: 100%;
width: 100%;
> .hero {
/* We need this, otherwise piece list will have a scrollbar */
overflow-x: hidden;
}
.hero {
font-family: 'Nunito', sans-serif;
font-weight: 300;
overflow: hidden;
text-align: center;
> img {
margin-top: 5em;
margin-bottom: 5em;
}
}
.wp-landing-wrapper {
text-align: center;
font-family: 'Nunito', sans-serif;
font-weight: 300;
> .enter {
margin-top: 2em;
> p {
margin-top: 2em;
}
}
}