1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-23 01:39:36 +01:00

Merge remote-tracking branch 'remotes/origin/AD-456-ikonotv-branded-page-for-registra' into AD-943-add-custom-additional-fields

Conflicts:
	js/components/signup_container.js
	js/components/whitelabel/wallet/components/ikonotv/ikonotv_landing.js
This commit is contained in:
diminator 2015-09-21 10:21:44 +02:00
commit 9bfd50e3bc
21 changed files with 542 additions and 175 deletions

View File

@ -21,7 +21,7 @@ let AccordionList = React.createClass({
);
} else if(this.props.count === 0) {
return (
<div>
<div className="ascribe-accordion-list-placeholder">
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
</div>

View File

@ -137,7 +137,7 @@ let RegisterPieceForm = React.createClass({
<input
type="number"
placeholder="(e.g. 1962)"
min={0}
min={1}
required/>
</Property>
{this.props.children}

View File

@ -14,7 +14,20 @@ let PieceListToolbar = React.createClass({
propTypes: {
className: React.PropTypes.string,
searchFor: React.PropTypes.func,
filterParams: React.PropTypes.array,
filterParams: React.PropTypes.arrayOf(
React.PropTypes.shape({
label: React.PropTypes.string,
items: React.PropTypes.arrayOf(
React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.shape({
key: React.PropTypes.string,
label: React.PropTypes.string
})
])
)
})
),
filterBy: React.PropTypes.object,
applyFilterBy: React.PropTypes.func,
orderParams: React.PropTypes.array,

View File

@ -3,20 +3,26 @@
import React from 'react';
import DropdownButton from 'react-bootstrap/lib/DropdownButton';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import { getLangText } from '../../utils/lang_utils.js';
let PieceListToolbarFilterWidgetFilter = React.createClass({
propTypes: {
// An array of either strings (which represent acl enums) or objects of the form
//
// {
// key: <acl enum>,
// label: <a human readable string>
// }
//
filterParams: React.PropTypes.arrayOf(React.PropTypes.any).isRequired,
filterParams: React.PropTypes.arrayOf(
React.PropTypes.shape({
label: React.PropTypes.string,
items: React.PropTypes.arrayOf(
React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.shape({
key: React.PropTypes.string,
label: React.PropTypes.string
})
])
)
})
).isRequired,
filterBy: React.PropTypes.object,
applyFilterBy: React.PropTypes.func
},
@ -79,23 +85,38 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
<DropdownButton
title={filterIcon}
className="ascribe-piece-list-toolbar-filter-widget">
<li style={{'textAlign': 'center'}}>
<em>{getLangText('Show works I can')}:</em>
{/* We iterate over filterParams, to receive the label and then for each
label also iterate over its items, to get all filterable options */}
{this.props.filterParams.map(({ label, items }, i) => {
return (
<div>
<li
style={{'textAlign': 'center'}}
key={i}>
<em>{label}:</em>
</li>
{this.props.filterParams.map((param, i) => {
let label;
{items.map((param, j) => {
// As can be seen in the PropTypes, a param can either
// be a string or an object of the shape:
//
// {
// key: <String>,
// label: <String>
// }
//
// This is why we need to distinguish between both here.
if(typeof param !== 'string') {
label = param.label;
param = param.key;
} else {
param = param;
label = param.split('_')[1];
label = param.split('acl_')[1].replace(/_/g, ' ');
}
return (
<MenuItem
key={i}
<li
key={j}
onClick={this.filterBy(param)}
className="filter-widget-item">
<div className="checkbox-line">
@ -107,7 +128,10 @@ let PieceListToolbarFilterWidgetFilter = React.createClass({
type="checkbox"
checked={this.props.filterBy[param]} />
</div>
</MenuItem>
</li>
);
})}
</div>
);
})}
</DropdownButton>

View File

@ -2,9 +2,6 @@
import React from 'react';
import UserStore from '../../stores/user_store';
import UserActions from '../../actions/user_actions';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
@ -21,38 +18,26 @@ import AppConstants from '../../constants/application_constants';
import { getLangText } from '../../utils/lang_utils';
let AccountSettings = React.createClass({
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
onChange(state) {
this.setState(state);
propTypes: {
currentUser: React.PropTypes.object.required,
loadUser: React.PropTypes.func.required
},
handleSuccess(){
UserActions.fetchCurrentUser();
this.props.loadUser();
let notification = new GlobalNotificationModel(getLangText('Settings succesfully updated'), 'success', 5000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
getFormDataProfile(){
return {'email': this.state.currentUser.email};
return {'email': this.props.currentUser.email};
},
render() {
let content = <img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />;
let profile = null;
if (this.state.currentUser.username) {
if (this.props.currentUser.username) {
content = (
<Form
url={ApiUrls.users_username}
@ -62,7 +47,7 @@ let AccountSettings = React.createClass({
label={getLangText('Username')}>
<input
type="text"
defaultValue={this.state.currentUser.username}
defaultValue={this.props.currentUser.username}
placeholder={getLangText('Enter your username')}
required/>
</Property>
@ -72,7 +57,7 @@ let AccountSettings = React.createClass({
editable={false}>
<input
type="text"
defaultValue={this.state.currentUser.email}
defaultValue={this.props.currentUser.email}
placeholder={getLangText('Enter your username')}
required/>
</Property>
@ -89,7 +74,7 @@ let AccountSettings = React.createClass({
className="ascribe-settings-property-collapsible-toggle"
style={{paddingBottom: 0}}>
<InputCheckbox
defaultChecked={this.state.currentUser.profile.hash_locally}>
defaultChecked={this.props.currentUser.profile.hash_locally}>
<span>
{' ' + getLangText('Enable hash option, e.g. slow connections or to keep piece private')}
</span>

View File

@ -14,11 +14,14 @@ import ContractSettingsUpdateButton from './contract_settings_update_button';
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
import AclProxy from '../acl_proxy';
import { getLangText } from '../../utils/lang_utils';
let ContractSettings = React.createClass({
propTypes: {
currentUser: React.PropTypes.object,
defaultExpanded: React.PropTypes.bool
},
@ -72,21 +75,24 @@ let ContractSettings = React.createClass({
<CreateContractForm
isPublic={true}
fileClassToUpload={{
singular: 'new public contract',
plural: 'new public contracts'
singular: 'new contract',
plural: 'new contracts'
}}/>
);
}
return (
<AclProxy
aclName="acl_view_contract_settings"
aclObject={this.props.currentUser.acl}>
<CollapsibleParagraph
title={getLangText('Contracts')}
show={true}
defaultExpanded={true}>
<CollapsibleParagraph
title={getLangText('Public Contracts')}
show={true}
defaultExpanded={true}>
defaultExpanded={false}>
<AclProxy
aclName="acl_edit_public_contract"
aclObject={this.props.currentUser.acl}>
<div>
{createPublicContractForm}
{publicContracts.map((contract, i) => {
return (
@ -104,7 +110,7 @@ let ContractSettings = React.createClass({
{getLangText('PREVIEW')}
</a>
<button
className="btn btn-default btn-sm margin-left-2px"
className="btn btn-danger btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
{getLangText('REMOVE')}
</button>
@ -114,16 +120,17 @@ let ContractSettings = React.createClass({
rightColumnWidth="60%"/>
);
})}
</CollapsibleParagraph>
<CollapsibleParagraph
title={getLangText('Private Contracts')}
show={true}
defaultExpanded={true}>
</div>
</AclProxy>
<AclProxy
aclName="acl_edit_private_contract"
aclObject={this.props.currentUser.acl}>
<div>
<CreateContractForm
isPublic={false}
fileClassToUpload={{
singular: getLangText('new private contract'),
plural: getLangText('new private contracts')
singular: getLangText('new contract'),
plural: getLangText('new contracts')
}}/>
{privateContracts.map((contract, i) => {
return (
@ -141,7 +148,7 @@ let ContractSettings = React.createClass({
{getLangText('PREVIEW')}
</a>
<button
className="btn btn-default btn-sm margin-left-2px"
className="btn btn-danger btn-sm margin-left-2px"
onClick={this.removeContract(contract)}>
{getLangText('REMOVE')}
</button>
@ -151,8 +158,10 @@ let ContractSettings = React.createClass({
rightColumnWidth="60%"/>
);
})}
</div>
</AclProxy>
</CollapsibleParagraph>
</CollapsibleParagraph>
</AclProxy>
);
}
});

View File

@ -3,6 +3,9 @@
import React from 'react';
import Router from 'react-router';
import UserStore from '../../stores/user_store';
import UserActions from '../../actions/user_actions';
import AccountSettings from './account_settings';
import BitcoinWalletSettings from './bitcoin_wallet_settings';
import ContractSettings from './contract_settings';
@ -18,14 +21,35 @@ let SettingsContainer = React.createClass({
mixins: [Router.Navigation],
getInitialState() {
return UserStore.getState();
},
componentDidMount() {
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
},
componentWillUnmount() {
UserStore.unlisten(this.onChange);
},
loadUser(){
UserActions.fetchCurrentUser();
},
onChange(state) {
this.setState(state);
},
render() {
return (
<div className="settings-container">
<AccountSettings />
<AccountSettings currentUser={this.state.currentUser} loadUser={this.loadUser}/>
{this.props.children}
<APISettings />
<BitcoinWalletSettings />
<ContractSettings />
<ContractSettings currentUser={this.state.currentUser} loadUser={this.loadUser}/>
</div>
);
}

View File

@ -15,6 +15,8 @@ import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_l
import Pagination from './ascribe_pagination/pagination';
import PieceListFilterDisplay from './piece_list_filter_display';
import GlobalAction from './global_action';
import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal';
import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
@ -22,6 +24,8 @@ import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar';
import AppConstants from '../constants/application_constants';
import { mergeOptions } from '../utils/general_utils';
import { getLangText } from '../utils/lang_utils';
let PieceList = React.createClass({
propTypes: {
@ -31,7 +35,6 @@ let PieceList = React.createClass({
filterParams: React.PropTypes.array,
orderParams: React.PropTypes.array,
orderBy: React.PropTypes.string
},
mixins: [Router.Navigation, Router.State],
@ -40,12 +43,13 @@ let PieceList = React.createClass({
return {
accordionListItemType: AccordionListItemWallet,
orderParams: ['artist_name', 'title'],
filterParams: [
filterParams: [{
label: getLangText('Show works I can'),
items: [
'acl_transfer',
'acl_consign',
{
key: 'acl_create_editions',
label: 'create editions'
'acl_create_editions'
]
}]
};
},
@ -149,9 +153,6 @@ let PieceList = React.createClass({
let loadingElement = (<img src={AppConstants.baseUrl + 'static/img/ascribe_animated_medium.gif'} />);
let AccordionListItemType = this.props.accordionListItemType;
//<GlobalAction requestActions={this.state.requestActions} />
return (
<div>
<PieceListToolbar
@ -166,6 +167,9 @@ let PieceList = React.createClass({
{this.props.customSubmitButton}
</PieceListToolbar>
<PieceListBulkModal className="ascribe-piece-list-bulk-modal" />
<PieceListFilterDisplay
filterBy={this.state.filterBy}
filterParams={this.props.filterParams}/>
<AccordionList
className="ascribe-accordion-list"
changeOrder={this.accordionChangeOrder}

View File

@ -0,0 +1,118 @@
'use strict';
import React from 'react';
let PieceListFilterDisplay = React.createClass({
propTypes: {
filterBy: React.PropTypes.object,
filterParams: React.PropTypes.arrayOf(
React.PropTypes.shape({
label: React.PropTypes.string,
items: React.PropTypes.arrayOf(
React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.shape({
key: React.PropTypes.string,
label: React.PropTypes.string
})
])
)
})
)
},
/**
* Takes the above described filterParams prop,
* assigns it it's true filterBy value that is derived from the filterBy prop
* and also - if there wasn't already one defined - generates a label
* @return {object}
*/
transformFilterParamsItemsToBools() {
let { filterParams, filterBy } = this.props;
return filterParams.map((filterParam) => {
return {
label: filterParam.label,
items: filterParam.items.map((item) => {
if(typeof item !== 'string' && typeof item.key === 'string' && typeof item.label === 'string') {
return {
key: item.key,
label: item.label,
value: filterBy[item.key] || false
};
} else {
return {
key: item,
label: item.split('acl_')[1].replace(/_/g, ' '),
value: filterBy[item] || false
};
}
})
};
});
},
/**
* Takes the list of filters generated in transformFilterParamsItemsToBools and
* transforms them into human readable text.
* @param {Object} filtersWithLabel An object of the shape {key: <String>, label: <String>, value: <Bool>}
* @return {string} A human readable string
*/
getFilterText(filtersWithLabel) {
let filterTextList = filtersWithLabel
// Iterate over all provided filterLabels and generate a list
// of human readable strings
.map((filterWithLabel) => {
let activeFilterWithLabel = filterWithLabel
.items
// If the filter is active (which it is when its value is true),
// we're going to include it's label into a list,
// otherwise we'll just return nothing
.map((filter) => {
if(filter.value) {
return filter.label;
}
})
// if nothing is returned, that index is 'undefined'.
// As we only want active filter, we filter out all falsy values e.g. undefined
.filter((filterName) => !!filterName)
// and join the result to a string
.join(', ');
// If this actually didn't generate an empty string,
// we take the label and concat it to the result.
if(activeFilterWithLabel) {
return filterWithLabel.label + ': ' + activeFilterWithLabel;
}
})
// filter out strings that are undefined, as their filter's were not activated
.filter((filterText) => !!filterText)
// if there are multiple sentences, capitalize the first one and lowercase the others
.map((filterText, i) => i === 0 ? filterText.charAt(0).toUpperCase() + filterText.substr(1) : filterText.charAt(0).toLowerCase() + filterText.substr(1))
.join(' and ');
return filterTextList;
},
render() {
let { filterBy } = this.props;
let filtersWithLabel = this.transformFilterParamsItemsToBools();
// do not show the FilterDisplay if there are no filters applied
if(filterBy && Object.keys(filterBy).length === 0) {
return null;
} else {
return (
<div className="row">
<div className="ascribe-piece-list-filter-display col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
{this.getFilterText(filtersWithLabel)}
<hr />
</div>
</div>
);
}
}
});
export default PieceListFilterDisplay;

View File

@ -2,6 +2,7 @@
import React from 'react';
import Router from 'react-router';
import SignupForm from './ascribe_forms/form_signup';
import { getLangText } from '../utils/lang_utils';
@ -39,7 +40,6 @@ let SignupContainer = React.createClass({
<SignupForm handleSuccess={this.handleSuccess} />
<div className="ascribe-login-text">
{getLangText('Already an ascribe user')}&#63; <Link to="login">{getLangText('Log in')}...</Link><br/>
{getLangText('Forgot my password')}&#63; <Link to="password_reset">{getLangText('Rescue me')}...</Link>
</div>
</div>

View File

@ -27,7 +27,7 @@ let PrizeApp = React.createClass({
}
return (
<div className="container ascribe-prize-app">
<div className={'container ascribe-prize-app client--' + subdomain}>
{header}
<RouteHandler />
<GlobalNotification />

View File

@ -8,6 +8,8 @@ import UserStore from '../../../../../stores/user_store';
import CylandAccordionListItem from './ascribe_accordion_list/cyland_accordion_list_item';
import { getLangText } from '../../../../../utils/lang_utils';
let CylandPieceList = React.createClass({
getInitialState() {
@ -33,6 +35,13 @@ let CylandPieceList = React.createClass({
<PieceList
redirectTo="register_piece"
accordionListItemType={CylandAccordionListItem}
filterParams={[{
label: getLangText('Show works I have'),
items: [{
key: 'acl_loaned',
label: getLangText('loaned to Cyland')
}]
}]}
/>
</div>
);

View File

@ -41,7 +41,7 @@ let IkonotvLanding = React.createClass({
}
return (
<ButtonLink to={redirect} query={this.getQuery()}>
{getLangText('ENTER')}
{getLangText('ENTER TO START')}
</ButtonLink>
);
},
@ -53,11 +53,13 @@ let IkonotvLanding = React.createClass({
<img src="https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono_tv.png" />
<div className="tagline">
<h1>PROTECT</h1>
<img src="http://placehold.it/600x300" />
<div className="poster">
<div className="content">
</div>
</div>
<h1>&amp; SHARE</h1>
</div>
<h2>Welcome to the ikonoTV</h2>
<h2>Registration Page</h2>
<h2>Welcome to the ikonoTV<br />Registration Page</h2>
</header>
<article>
<section>

View File

@ -8,6 +8,9 @@ import UserStore from '../../../../../stores/user_store';
import IkonotvAccordionListItem from './ascribe_accordion_list/ikonotv_accordion_list_item';
import { getLangText } from '../../../../../utils/lang_utils';
let IkonotvPieceList = React.createClass({
getInitialState() {
return UserStore.getState();
@ -32,7 +35,13 @@ let IkonotvPieceList = React.createClass({
<PieceList
redirectTo="register_piece"
accordionListItemType={IkonotvAccordionListItem}
/>
filterParams={[{
label: getLangText('Show works I have'),
items: [{
key: 'acl_loaned',
label: getLangText('loaned to IkonoTV')
}]
}]}/>
</div>
);
}

View File

@ -8,15 +8,19 @@ import Footer from '../../footer';
import GlobalNotification from '../../global_notification';
import getRoutes from './wallet_routes';
import classNames from 'classnames';
let RouteHandler = Router.RouteHandler;
let WalletApp = React.createClass({
mixins: [Router.State],
render() {
let subdomain = window.location.host.split('.')[0];
let ROUTES = getRoutes(null, subdomain);
let activeRoutes = this.getRoutes().map(elem => 'route--' + elem.name);
let header = null;
if ((this.isActive('landing') || this.isActive('login') || this.isActive('signup') || this.isActive('contract_notifications'))
@ -28,13 +32,15 @@ let WalletApp = React.createClass({
}
return (
<div className="container ascribe-prize-app">
<div className={classNames('ascribe-wallet-app', 'client--' + subdomain, activeRoutes)}>
<div className='container'>
{header}
<RouteHandler />
<GlobalNotification />
<div id="modal" className="container"></div>
<Footer />
</div>
</div>
);
}
});

View File

@ -5,6 +5,10 @@ $ascribe-accordion-list-item-height: 8em;
padding-right: 15px;
}
.ascribe-accordion-list-placeholder {
margin-top: 1em;
}
.ascribe-accordion-list-item {
background-color: white;
border: 1px solid black;

View File

@ -54,8 +54,7 @@
}
.filter-widget-item {
> a {
a {
padding-left: 0;
padding-right: 0;
}
@ -64,6 +63,13 @@
.checkbox-line {
height: 25px;
position: relative;
color: #333333;
/* Fuck you react-bootstrap */
&:hover {
background-color: $dropdown-link-hover-bg;
cursor: pointer;
}
span {
cursor: pointer;

View File

@ -487,3 +487,23 @@ hr {
.ascribe-progress-bar-xs {
height: 12px;
}
.ascribe-piece-list-filter-display {
padding-left: 10px;
padding-right: 10px;
> span {
font-size: 1.1em;
font-weight: 600;
color: #616161;
padding-left: .3em;
}
> hr {
margin-top: .15em;
margin-bottom: .1em;
border-color: #ccc;
}
}

View File

@ -1,2 +1,3 @@
@import 'prize/index';
@import 'wallet/index';
@import 'wallet/ikonotv/ikonotv_landing';

View File

@ -1,11 +1,117 @@
.client--ikonotv {
font-family: 'Helvetica Neue', 'Helvetica', sans-serif;
}
.client--ikonotv.route--landing {
background-color: #c40050;
margin: 0;
width: 100%;
padding: 5em 1em;
}
.client--ikonotv.route--login,
.client--ikonotv.route--signup {
background-color: #c40050;
.ascribe-btn-login {
display: block;
margin: 50px auto 0;
width: auto;
padding: 10px 30px;
text-transform: uppercase;
font-weight: bold;
width: 180px;
}
.ascribe-btn-login-spinner {
background-color: #02b6a3;
}
.ascribe-form-header {
background-image: url(https://s3-us-west-2.amazonaws.com/ascribe0/whitelabel/ikonotv/ikono_tv.png);
background-color: transparent;
background-position: center 0;
background-repeat: no-repeat;
background-size: 300px;
margin-bottom: 30px;
height: 150px;
position: relative;
h3 {
position: absolute;
bottom: 0;
left: 0;
right: 0;
color: white;
text-align: center;
font-size: 22px;
}
}
.ascribe-settings-wrapper {
background-color: transparent;
&:hover {
border-left: 3px solid transparent;
}
&.is-focused {
border-left: 3px solid transparent !important;
}
}
.ascribe-settings-property {
border: none;
}
.ascribe-settings-property > span {
color: white;
}
.ascribe-settings-property > input {
padding: 10px;
background-color: #fff;
margin-top: .1em;
&:focus {
background-color: #ffff00;
}
}
.checkbox,
.checkbox a {
color: white !important;
}
.ascribe-login-text {
color: white;
text-align: center;
a {
color: white;
text-decoration: underline;
}
}
}
.client--ikonotv .ascribe-form-bordered {
border: none;
}
.client--ikonotv .ascribe-login-wrapper {
}
.client--ikonotv .ascribe-footer {
display: none;
}
.ikonotv-landing {
/* center all text on the page */
text-align: center;
background-color: #c40050;
color: white;
padding: 5em 5em 5em 5em;
header {
/* center all images on the page */
img {
@ -16,7 +122,7 @@
/* Ikonotv logo */
img:first-child {
width: 200px;
max-width: 200px;
}
> .tagline {
@ -29,12 +135,30 @@
margin-top: 10px;
margin-bottom: 10px;
@media only screen and (max-width: 600px) {
font-size: 4em;
}
}
> .poster {
max-width: 600px;
margin: 0 auto;
> .content {
width: 100%;
padding-bottom: 56.25%;
height: 0;
background-color: #ffff00;
}
}
}
> h2 {
font-weight: 600;
font-size: 2.75em;
@media only screen and (max-width: 600px) {
font-size: 1.75em;
}
}
> h2 + h2 {
@ -44,7 +168,8 @@
article {
> section {
width: 65%;
width: 100%;
max-width: 60em;
margin: 3em auto 1em auto;
> h1 {
@ -57,6 +182,9 @@
text-align: left;
font-size: 1.3em;
line-height: 1.8;
@media only screen and (max-width: 600px) {
font-size: 1.1em;
}
}
}
}

View File

@ -0,0 +1,5 @@
.ascribe-wallet-app {
border-radius: 0;
padding-top: 70px;
min-height: 100vh;
}