This commit is contained in:
Stuart Colville 2015-07-24 14:40:04 +01:00
Родитель c1424a05c0
Коммит aab26f1680
41 изменённых файлов: 573 добавлений и 674 удалений

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import * as actionTypes from 'constants/action-types';
export function error(debugMessage) {

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import $ from 'jquery';
import * as actionTypes from 'constants/action-types';

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import * as actionTypes from 'constants/action-types';
// TODO: expand these actions to encapsulate the Ajax

Просмотреть файл

@ -1,14 +1,12 @@
'use strict';
import $ from 'jquery';
var $ = require('jquery');
var appActions = require('./app');
var actionTypes = require('constants/action-types');
import * as appActions from './app';
import * as actionTypes from 'constants/action-types';
export function signIn(accessToken) {
export function signIn(accessToken, jquery=$) {
return function(dispatch) {
$.ajax({
jquery.ajax({
data: {
access_token: accessToken,
},
@ -19,7 +17,7 @@ export function signIn(accessToken) {
console.log('setting CSRF token for subsequent requests:',
data.csrf_token);
$.ajaxSetup({
jquery.ajaxSetup({
headers: {
'X-CSRFToken': data.csrf_token,
},

Просмотреть файл

@ -1,35 +1,28 @@
'use strict';
import 'shims';
require('shims');
import React, { Component } from 'react';
import { Provider, Connector } from 'redux/react';
import { bindActionCreators } from 'redux';
var React = require('react');
var Provider = require('redux/react').Provider;
var Connector = require('redux/react').Connector;
var bindActionCreators = require('redux').bindActionCreators;
import reduxConfig from 'redux-config';
import * as managementActions from 'actions/management';
var reduxConfig = require('redux-config');
var managementActions = require('actions/management');
var ModalError = require('views/modal-error');
var Management = require('views/management');
var ManageCards = require('views/manage-cards');
import ModalError from 'views/modal-error';
import Management from 'views/management';
import ManageCards from 'views/manage-cards';
export default class ManagementApp extends Component {
var App = React.createClass({
displayName: 'ManagementApp',
selectData: function(state) {
selectData(state) {
return {
management: state.management,
};
},
}
renderChild(connector) {
var actions = bindActionCreators(managementActions, connector.dispatch);
var children = [];
if (connector.management.error) {
children.push(
<ModalError {...actions} error={connector.management.error} />
@ -42,9 +35,9 @@ var App = React.createClass({
}
children.push(<Management {...actions} />);
return <div>{children}</div>;
},
}
render () {
render() {
return (
<main>
<Connector select={this.selectData}>
@ -52,19 +45,16 @@ var App = React.createClass({
</Connector>
</main>
);
},
});
}
}
module.exports = {
component: App,
init: function() {
export function init() {
React.render((
<Provider redux={reduxConfig.default}>
<Provider redux={reduxConfig}>
{function() {
return <App/>;
return <ManagementApp/>;
}}
</Provider>
), document.body);
},
};
}

Просмотреть файл

@ -1,11 +1,9 @@
'use strict';
var $ = require('jquery');
var App = require('./app');
import $ from 'jquery';
import { init as appInit } from './app';
// Common ajax settings.
$.ajaxSetup({
dataType: 'json',
});
App.init();
appInit();

Просмотреть файл

@ -1,42 +1,54 @@
'use strict';
import 'shims';
require('shims');
import React, { Component, PropTypes } from 'react';
var React = require('react');
var Provider = require('redux/react').Provider;
var Connector = require('redux/react').Connector;
var bindActionCreators = require('redux').bindActionCreators;
import { Connector, Provider } from 'redux/react';
import { bindActionCreators } from 'redux';
var reduxConfig = require('redux-config');
var ErrorMessage = require('components/error');
var Login = require('views/login');
var Purchase = require('views/purchase');
var userActions = require('actions/user');
var utils = require('utils');
import reduxConfig from 'redux-config';
import ErrorMessage from 'components/error';
import Login from 'views/login';
import Purchase from 'views/purchase';
import * as userActions from 'actions/user';
import { parseQuery } from 'utils';
var App = React.createClass({
export default class PaymentApp extends Component {
displayName: 'PaymentApp',
static propTypes = {
Login: PropTypes.object,
Purchase: PropTypes.object,
win: PropTypes.object,
}
getInitialState: function() {
var qs = utils.parseQuery(window.location.href);
static defaultProps = {
Login: Login,
Purchase: Purchase,
win: window,
}
constructor(props) {
super(props);
var qs = parseQuery(props.win.location.href);
// TODO: we should validate/clean this input to raise early errors.
return {
this.state = {
accessToken: qs.access_token,
productId: qs.product,
};
},
}
selectData: function(state) {
selectData(state) {
return {
app: state.app,
user: state.user,
};
},
}
render () {
render() {
var state = this.state;
var Login_ = this.props.Login;
var Purchase_ = this.props.Purchase;
return (
<main>
<Connector select={this.selectData}>
@ -47,7 +59,7 @@ var App = React.createClass({
} else if (!result.user.signedIn) {
console.log('rendering login');
return (
<Login
<Login_
accessToken={state.accessToken}
{...bindActionCreators(userActions, result.dispatch) }
/>
@ -55,26 +67,23 @@ var App = React.createClass({
} else {
console.log('rendering purchase flow');
return (
<Purchase user={result.user} productId={state.productId} />
<Purchase_ user={result.user} productId={state.productId} />
);
}
}}
</Connector>
</main>
);
},
});
}
}
module.exports = {
component: App,
init: function() {
export function init() {
React.render((
<Provider redux={reduxConfig.default}>
<Provider redux={reduxConfig}>
{function() {
return <App/>;
return <PaymentApp/>;
}}
</Provider>
), document.body);
},
};
}

Просмотреть файл

@ -1,8 +1,6 @@
'use strict';
var $ = require('jquery');
var tracking = require('tracking');
var App = require('./app');
import $ from 'jquery';
import tracking from 'tracking';
import { init as appInit } from './app';
// Common ajax settings.
$.ajaxSetup({
@ -10,4 +8,4 @@ $.ajaxSetup({
});
tracking.init();
App.init();
appInit();

Просмотреть файл

@ -1,31 +1,27 @@
'use strict';
/*eslint react/no-multi-comp: 0 */
var React = require('react');
var cx = require('classnames');
import React, { Component, PropTypes } from 'react';
import cx from 'classnames';
var Accordion = React.createClass({
class Accordion extends Component {
displayName: 'Accordion',
static propTypes = {
children: PropTypes.node.isRequired,
};
propTypes: {
children: React.PropTypes.node.isRequired,
},
getInitialState: function() {
constructor(props) {
super(props);
var sections = [];
React.Children.forEach(this.props.children, function() {
sections.push({isActive: false});
});
sections[0].isActive = true;
return {
this.state = {
sections: sections,
};
},
}
activate: function(sectionIdx, e) {
activate(sectionIdx, e) {
if (e.target.getAttribute('data-activate') !== null) {
e.preventDefault();
var sections = this.state.sections;
@ -34,14 +30,13 @@ var Accordion = React.createClass({
});
this.setState({sections: sections});
}
},
}
render: function() {
var that = this;
var children = React.Children.map(this.props.children, function(child, idx){
render() {
var children = React.Children.map(this.props.children, (child, idx) => {
return React.cloneElement(child, {
activate: that.activate.bind(that, idx),
isActive: that.state.sections[idx].isActive,
activate: this.activate.bind(this, idx),
isActive: this.state.sections[idx].isActive,
});
});
return (
@ -49,21 +44,19 @@ var Accordion = React.createClass({
{children}
</div>
);
},
});
}
}
var AccordionSection = React.createClass({
displayName: 'AccordionSection',
class AccordionSection extends Component {
propTypes: {
static propTypes = {
activate: React.PropTypes.func.isRequired,
children: React.PropTypes.node.isRequired,
isActive: React.PropTypes.bool,
},
render: function() {
};
render() {
var classes = cx(
'ac-section', {'active': this.props.isActive});
@ -73,29 +66,28 @@ var AccordionSection = React.createClass({
{this.props.children}
</section>
);
},
});
}
}
var AccordionContent = React.createClass({
class AccordionContent extends Component {
displayName: 'AccordionContent',
propTypes: {
static propTypes = {
children: React.PropTypes.node.isRequired,
},
};
render: function() {
render() {
return (
<div className="ac-content">
{this.props.children}
</div>
);
},
});
}
}
module.exports = {
Accordion: Accordion,
AccordionContent: AccordionContent,
AccordionSection: AccordionSection,
export default {
Accordion,
AccordionContent,
AccordionSection,
};

Просмотреть файл

@ -1,40 +1,39 @@
'use strict';
import $ from 'jquery';
import React, { Component, PropTypes } from 'react';
var $ = require('jquery');
var React = require('react');
import CardList from 'components/card-list';
import SubmitButton from 'components/submit-button';
var CardList = require('components/card-list');
var SubmitButton = require('components/submit-button');
import { gettext } from 'utils';
import * as purchaseActions from 'actions/purchase';
var gettext = require('utils').gettext;
var purchaseActions = require('actions/purchase');
var reduxConfig = require('redux-config');
import reduxConfig from 'redux-config';
module.exports = React.createClass({
displayName: 'CardChoice',
export default class CardChoice extends Component {
propTypes: {
cards: React.PropTypes.arrayOf(
React.PropTypes.shape({
id: React.PropTypes.number,
resource_uri: React.PropTypes.string,
truncated_id: React.PropTypes.string,
type_name: React.PropTypes.string,
static propTypes = {
cards: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number,
resource_uri: PropTypes.string,
truncated_id: PropTypes.string,
type_name: PropTypes.string,
}
)).isRequired,
productId: React.PropTypes.string.isRequired,
},
productId: PropTypes.string.isRequired,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
isSubmitting: false,
card: (this.props.cards.length === 1 ?
this.props.cards[0].resource_uri : null),
};
},
}
handleSubmit: function(e) {
handleSubmit = (e) => {
e.preventDefault();
this.setState({isSubmitting: true});
@ -51,20 +50,20 @@ module.exports = React.createClass({
}).done(function() {
console.log('Successfully subscribed with existing card');
reduxConfig.default.dispatch(
reduxConfig.dispatch(
purchaseActions.complete()
);
}).fail(function() {
// TODO: handler errors.
});
},
}
handleCardChange: function(e) {
handleCardChange = (e) => {
this.setState({card: e.target.value});
},
}
render: function() {
render() {
var cardData = this.props.cards;
for (var i = 0; i < cardData.length; i += 1) {
var card = cardData[i];
@ -75,12 +74,14 @@ module.exports = React.createClass({
return (
<form id="card-listing" onSubmit={this.handleSubmit}>
<CardList cards={cardData} onCardChange={this.handleCardChange} />
<CardList
cards={cardData}
onCardChange={this.handleCardChange} />
<SubmitButton isDisabled={!formIsValid}
showSpinner={this.state.isSubmitting}
text={gettext('Subscribe')}
/>
</form>
);
},
});
}
}

Просмотреть файл

@ -1,20 +1,17 @@
'use strict';
import $ from 'jquery';
import CardValidator from 'card-validator';
import React, { Component, PropTypes } from 'react';
import braintree from 'braintree-web';
var $ = require('jquery');
var CardValidator = require('card-validator');
var React = require('react');
var braintree = require('braintree-web');
import { gettext } from 'utils';
var utils = require('utils');
var gettext = utils.gettext;
var CardInput = require('components/card-input');
var SubmitButton = require('components/submit-button');
var purchaseActions = require('actions/purchase');
var reduxConfig = require('redux-config');
import CardInput from 'components/card-input';
import SubmitButton from 'components/submit-button';
import * as purchaseActions from 'actions/purchase';
import reduxConfig from 'redux-config';
var defaultFieldAttrs = {
const defaultFieldAttrs = {
'autoComplete': 'off',
// inputMode is not yet supported in React.
// See https://github.com/facebook/react/pull/3798
@ -22,32 +19,22 @@ var defaultFieldAttrs = {
'type': 'tel',
};
module.exports = React.createClass({
displayName: 'CardForm',
propTypes: {
card: React.PropTypes.object,
cvv: React.PropTypes.object,
'data-token': React.PropTypes.string.isRequired,
expiration: React.PropTypes.object,
id: React.PropTypes.string.isRequired,
productId: React.PropTypes.string.isRequired,
const errorKeyToFieldMap = {
'__all__': {
field: 'card',
error: 'declined',
},
getInitialState: function() {
return {
isSubmitting: false,
cardType: null,
errors: {},
card: '',
expiration: '',
cvv: '',
};
'fraud': {
field: 'card',
error: 'declined',
},
'cvv': {
field: 'cvv',
error: 'invalid',
},
};
fieldProps: {
const fieldProps = {
card: {
'attrs': defaultFieldAttrs,
'classNames': ['card'],
@ -82,18 +69,42 @@ module.exports = React.createClass({
'id': 'cvv',
'validator': CardValidator.cvv,
},
},
};
handleChange: function(e) {
export default class CardForm extends Component {
static propTypes = {
card: PropTypes.object,
cvv: PropTypes.object,
'data-token': PropTypes.string.isRequired,
expiration: PropTypes.object,
id: PropTypes.string.isRequired,
productId: PropTypes.string.isRequired,
};
constructor(props) {
super(props);
this.state = {
isSubmitting: false,
cardType: null,
errors: {},
card: '',
expiration: '',
cvv: '',
};
}
handleChange = (e) => {
var fieldId = e.target.id;
var val = e.target.value;
var fieldProps = this.fieldProps[fieldId];
var valData = fieldProps.validator(this.stripPlaceholder(val));
fieldProps.hasVal = val.length > 0 || false;
fieldProps.isValid = valData.isValid === true;
fieldProps.showError = !valData.isValid && !valData.isPotentiallyValid;
fieldProps.errorMessage = fieldProps.errors.invalid;
var fieldData = fieldProps[fieldId];
var valData = fieldData.validator(this.stripPlaceholder(val));
fieldData.hasVal = val.length > 0 || false;
fieldData.isValid = valData.isValid === true;
fieldData.showError = !valData.isValid && !valData.isPotentiallyValid;
fieldData.errorMessage = fieldData.errors.invalid;
var newState = {
[e.target.id]: e.target.value,
@ -106,9 +117,9 @@ module.exports = React.createClass({
}
this.setState(newState);
},
}
handleSubmit: function(e) {
handleSubmit = (e) => {
e.preventDefault();
this.setState({isSubmitting: true});
var that = this;
@ -136,7 +147,7 @@ module.exports = React.createClass({
}).done(function() {
console.log('Successfully subscribed + completed payment');
reduxConfig.default.dispatch(
reduxConfig.dispatch(
purchaseActions.complete()
);
@ -145,25 +156,9 @@ module.exports = React.createClass({
});
}
});
},
}
errorKeyToFieldMap: {
'__all__': {
field: 'card',
error: 'declined',
},
'fraud': {
field: 'card',
error: 'declined',
},
'cvv': {
field: 'cvv',
error: 'invalid',
},
},
processApiErrors: function(errors) {
var that = this;
processApiErrors(errors) {
if (errors.error_response && errors.error_response.braintree) {
var apiErrors = errors.error_response.braintree;
// Iterate over the error object and create a new data
@ -171,10 +166,10 @@ module.exports = React.createClass({
// a list of generic errors.
Object.keys(apiErrors).forEach(function(key) {
console.log('API ErrorMessage: ' + JSON.stringify(apiErrors[key]));
var errorData = that.errorKeyToFieldMap[key] || {};
var errorData = errorKeyToFieldMap[key] || {};
var field = errorData.field;
if (field) {
var fieldData = that.fieldProps[field];
var fieldData = fieldProps[field];
fieldData.isValid = false;
fieldData.showError = true;
fieldData.errorMessage = fieldData.errors[errorData.error];
@ -184,33 +179,31 @@ module.exports = React.createClass({
this.setState({
isSubmitting: false,
});
},
}
stripPlaceholder: function(val) {
stripPlaceholder(val) {
return val ? val.replace(/_/g, '') : '';
},
render: function() {
}
render() {
var formIsValid = true;
var that = this;
// Update form validity based on fieldProps.
Object.keys(this.fieldProps).forEach(function(field) {
if (!that.fieldProps[field].isValid) {
Object.keys(fieldProps).forEach(function(field) {
if (!fieldProps[field].isValid) {
formIsValid = false;
}
});
return (
<form {...this.props} onSubmit={this.handleSubmit}>
<CardInput {...this.fieldProps.card}
<CardInput {...fieldProps.card}
cardType={this.state.cardType}
onChangeHandler={this.handleChange} />
<CardInput {...this.fieldProps.expiration}
<CardInput {...fieldProps.expiration}
cardType={this.state.cardType}
onChangeHandler={this.handleChange} />
<CardInput {...this.fieldProps.cvv}
<CardInput {...fieldProps.cvv}
cardType={this.state.cardType}
onChangeHandler={this.handleChange} />
<SubmitButton isDisabled={!formIsValid}
@ -218,5 +211,5 @@ module.exports = React.createClass({
text={gettext('Subscribe')} />
</form>
);
},
});
}
}

Просмотреть файл

@ -1,14 +1,19 @@
'use strict';
import cx from 'classnames';
import React, { Component, PropTypes } from 'react';
var cx = require('classnames');
var React = require('react');
// Just a convenience mapping for cards from card-validator
// to shorted classes used in CSS.
const cardTypeMap = {
'american-express': 'amex',
'diners-club': 'diners',
'master-card': 'mastercard',
};
export default class CardIcon extends Component {
module.exports = React.createClass({
displayName: 'CardIcon',
propTypes: {
cardType: React.PropTypes.oneOf([
static propTypes = {
cardType: PropTypes.oneOf([
'amex',
'american-express',
'diners-club',
'discover',
@ -18,23 +23,16 @@ module.exports = React.createClass({
'master-card',
'visa',
]),
},
};
// Just a convenience mapping for cards from card-validator
// to shorted classes used in CSS.
cardTypeMap: {
'american-express': 'amex',
'diners-club': 'diners',
'master-card': 'mastercard',
},
render: function() {
render() {
// This is only displayed if a cardType is passed-in.
var cardType = this.props.cardType;
var cardClassName = cx([
'card-icon',
'cctype-' + (this.cardTypeMap[cardType] || cardType),
'cctype-' + (cardTypeMap[cardType] || cardType),
]);
return cardType ? <span className={cardClassName} /> : null;
},
});
}
}

Просмотреть файл

@ -1,37 +1,13 @@
'use strict';
import classNames from 'classnames';
import React, { Component, PropTypes } from 'react';
var classNames = require('classnames');
var React = require('react');
import CardIcon from 'components/card-icon';
import InputError from 'components/input-error';
import MaskedInput from 'react-maskedinput';
var CardIcon = require('components/card-icon');
var InputError = require('components/input-error');
var MaskedInput = require('react-maskedinput');
import { defaults, gettext } from 'utils';
var utils = require('utils');
var gettext = utils.gettext;
module.exports = React.createClass({
displayName: 'CardInput',
propTypes: {
attrs: React.PropTypes.object,
cardType: React.PropTypes.string,
classNames: React.PropTypes.array,
errorMessage: React.PropTypes.string,
errorModifier: React.PropTypes.string,
hasVal: React.PropTypes.bool,
id: React.PropTypes.string.isRequired,
isValid: React.PropTypes.bool,
label: React.PropTypes.string,
onChangeHandler: React.PropTypes.func.isRequired,
pattern: React.PropTypes.string,
placeholder: React.PropTypes.string,
showError: React.PropTypes.bool,
},
cardPatterns: {
const cardPatterns = {
'default': {
card: {
pattern: '1111 1111 1111 1111',
@ -59,21 +35,40 @@ module.exports = React.createClass({
placeholder: gettext('CVV'),
},
},
},
};
updatePattern: function(fieldId, cardType) {
export default class CardInput extends Component {
static propTypes = {
attrs: PropTypes.object,
cardType: PropTypes.string,
classNames: PropTypes.array,
errorMessage: PropTypes.string,
errorModifier: PropTypes.string,
hasVal: PropTypes.bool,
id: PropTypes.string.isRequired,
isValid: PropTypes.bool,
label: PropTypes.string,
onChangeHandler: PropTypes.func.isRequired,
pattern: PropTypes.string,
placeholder: PropTypes.string,
showError: PropTypes.bool,
};
updatePattern(fieldId, cardType) {
// Update the pattern for card + cvv field if card was detected.
if (cardType && this.cardPatterns[cardType]) {
return utils.defaults(
this.cardPatterns[cardType][fieldId] || {},
this.cardPatterns.default[fieldId]
if (cardType && cardPatterns[cardType]) {
return defaults(
cardPatterns[cardType][fieldId] || {},
cardPatterns.default[fieldId]
);
} else {
return this.cardPatterns.default[fieldId] || {};
return cardPatterns.default[fieldId] || {};
}
}
},
render: function() {
render() {
var labelClassNames = this.props.classNames || [];
// Use a copy of the list to avoid appending ad infinitum.
@ -95,8 +90,6 @@ module.exports = React.createClass({
var showCardIcon = this.props.id === 'card';
return (
<label className={labelClass} htmlFor={this.props.id} >
<span className="vh">{label}</span>
@ -114,5 +107,5 @@ module.exports = React.createClass({
/>
</label>
);
},
});
}
}

Просмотреть файл

@ -1,23 +1,19 @@
'use strict';
var React = require('react');
var CardIcon = require('components/card-icon');
import React, { Component, PropTypes } from 'react';
import CardIcon from 'components/card-icon';
module.exports = React.createClass({
export default class CardItem extends Component {
displayName: 'CardItem',
static propTypes = {
checked: PropTypes.bool.isRequired,
id: PropTypes.number.isRequired,
onChangeHandler: PropTypes.func.isRequired,
resource_uri: PropTypes.string.isRequired,
truncated_id: PropTypes.string.isRequired,
type_name: PropTypes.string.isRequired,
};
propTypes: {
checked: React.PropTypes.bool.isRequired,
id: React.PropTypes.number.isRequired,
onChangeHandler: React.PropTypes.func.isRequired,
resource_uri: React.PropTypes.string.isRequired,
truncated_id: React.PropTypes.string.isRequired,
type_name: React.PropTypes.string.isRequired,
},
render: function() {
render() {
var cardType = this.props.type_name.toLowerCase();
var cardText = '●●●● ●●●● ●●●● ' + this.props.truncated_id;
var inputId = 'card-' + this.props.id;
@ -33,6 +29,5 @@ module.exports = React.createClass({
className="text">{cardText}</label>
</li>
);
},
});
}
}

Просмотреть файл

@ -1,38 +1,34 @@
'use strict';
import React, { Component, PropTypes } from 'react';
import CardItem from 'components/card-item';
var React = require('react');
var CardItem = require('components/card-item');
module.exports = React.createClass({
displayName: 'CardList',
export default class CardList extends Component {
propTypes: {
cards: React.PropTypes.arrayOf(
React.PropTypes.shape({
id: React.PropTypes.number,
resource_uri: React.PropTypes.string,
truncated_id: React.PropTypes.string,
type_name: React.PropTypes.string,
static propTypes = {
cards: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number,
resource_uri: PropTypes.string,
truncated_id: PropTypes.string,
type_name: PropTypes.string,
}
)).isRequired,
onCardChange: React.PropTypes.func.isRequired,
},
onCardChange: PropTypes.func.isRequired,
}
render: function() {
render() {
var cardData = this.props.cards;
var cardList = [];
for (var i = 0; i < cardData.length; i += 1) {
var { checked, ...card } = cardData[i];
cardList.push(<CardItem {...card} key={'ci-' + i}
onChangeHandler={this.props.onCardChange}
checked={checked} />);
}
return (
<ul className="card-listing">
{cardList}
</ul>
);
},
});
}
}

Просмотреть файл

@ -1,20 +1,14 @@
'use strict';
var React = require('react');
var utils = require('utils');
var gettext = utils.gettext;
import React, { Component, PropTypes } from 'react';
import { gettext } from 'utils';
module.exports = React.createClass({
displayName: 'Error',
export default class ErrorMessage extends Component {
propTypes: {
error: React.PropTypes.object.isRequired,
},
static propTypes = {
error: PropTypes.object.isRequired,
};
render: function() {
render() {
console.log('rendering app error:', this.props.error);
return (
<div className="app-error">
@ -23,6 +17,5 @@ module.exports = React.createClass({
</p>
</div>
);
},
});
}
}

Просмотреть файл

@ -1,10 +1,9 @@
'use strict';
var cx = require('classnames');
var React = require('react');
import React from 'react';
import cx from 'classnames';
module.exports = React.createClass({
export default React.createClass({
displayName: 'InputError',
propTypes: {

Просмотреть файл

@ -1,21 +1,17 @@
'use strict';
var React = require('react');
var gettext = require('utils').gettext;
var cx = require('classnames');
import React, { Component, PropTypes } from 'react';
import { gettext } from 'utils';
import cx from 'classnames';
module.exports = React.createClass({
export default class Modal extends Component {
displayName: 'Modal',
static propTypes = {
children: PropTypes.object.isRequired,
handleClose: PropTypes.func.isRequired,
title: PropTypes.string,
};
propTypes: {
children: React.PropTypes.object.isRequired,
handleClose: React.PropTypes.func.isRequired,
title: React.PropTypes.string,
},
onClose: function(e) {
onClose = (e) => {
var targetClassName = e.target.getAttribute('class') || '';
var classes = targetClassName.split(' ');
// Only deal with closing the window if the event
@ -27,9 +23,9 @@ module.exports = React.createClass({
e.stopPropagation();
this.props.handleClose();
}
},
}
render: function() {
render() {
var classes = cx(['modal', {'active': true}]);
return (
@ -47,5 +43,5 @@ module.exports = React.createClass({
</div>
</div>
);
},
});
}
}

Просмотреть файл

@ -1,18 +1,14 @@
'use strict';
import React, { Component, PropTypes } from 'react';
import products from 'products';
import { gettext } from 'utils';
var React = require('react');
var products = require('products');
var gettext = require('utils').gettext;
export default class ProductDetail extends Component {
module.exports = React.createClass({
static propTypes = {
productId: PropTypes.string.isRequired,
};
displayName: 'ProductDetail',
propTypes: {
productId: React.PropTypes.string.isRequired,
},
render: function() {
render() {
var productId = this.props.productId;
var productData = products[productId];
@ -29,5 +25,5 @@ module.exports = React.createClass({
<div>{gettext('per month')}</div>
</div>
);
},
});
}
}

Просмотреть файл

@ -1,28 +1,23 @@
'use strict';
import React, { Component, PropTypes } from 'react';
import { gettext } from 'utils';
var React = require('react');
var gettext = require('utils').gettext;
export default class Spinner extends Component {
module.exports = React.createClass({
displayName: 'Spinner',
static propTypes = {
text: PropTypes.string.isRequired,
}
propTypes: {
text: React.PropTypes.string.isRequired,
},
getDefaultProps: function() {
return {
static defaultProps = {
text: gettext('Loading'),
};
},
}
render: function() {
render() {
return (
<div className="spinner-cont">
<div className="spinner" />
<span className="text">{this.props.text}</span>
</div>
);
},
});
}
}

Просмотреть файл

@ -1,20 +1,16 @@
'use strict';
var cx = require('classnames');
var React = require('react');
import React, { Component, PropTypes } from 'react';
import cx from 'classnames';
module.exports = React.createClass({
displayName: 'SubmitButton',
export default class SubmitButton extends Component {
propTypes: {
isDisabled: React.PropTypes.bool,
showSpinner: React.PropTypes.bool,
text: React.PropTypes.string.isRequired,
},
render: function() {
static propTypes = {
isDisabled: PropTypes.bool,
showSpinner: PropTypes.bool,
text: PropTypes.string.isRequired,
};
render() {
var { isDisabled, text, showSpinner, ...buttonAttrs } = this.props;
var buttonClassNames = cx({
@ -33,5 +29,5 @@ module.exports = React.createClass({
disabled={isDisabled}
type="submit">{text}</button>
);
},
});
}
}

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
export const APP_ERROR = 'APP_ERROR';
export const USER_SIGNED_IN = 'USER_SIGNED_IN';
export const COMPLETE_PURCHASE = 'COMPLETE_PURCHASE';

Просмотреть файл

@ -1,6 +1,4 @@
'use strict';
module.exports = {
export default {
'mozilla-concrete-brick': require('json!mozilla-concrete-brick'),
'mozilla-concrete-mortar': require('json!mozilla-concrete-mortar'),
};

Просмотреть файл

@ -1,12 +1,9 @@
'use strict';
var redux = require('redux');
var dataStore = require('stores/index');
console.log(dataStore);
import { createRedux as createRedux_ } from 'redux';
import * as stores from 'stores';
function createRedux() {
return redux.createRedux(dataStore);
return createRedux_(stores);
}
exports.create = createRedux;
exports.default = createRedux();
export default createRedux();
export { createRedux as create };

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
module.exports = {
'supportedLanguages': [
'ca',

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
import assign from 'object.assign';
assign.shim();

Просмотреть файл

@ -1,6 +1,4 @@
'use strict';
var actionTypes = require('constants/action-types');
import * as actionTypes from 'constants/action-types';
export default function app(state, action) {
if (action.type === actionTypes.APP_ERROR) {

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
export { default as app } from './app';
export { default as management } from './management';
export { default as purchase } from './purchase';

Просмотреть файл

@ -1,6 +1,5 @@
'use strict';
import * as actionTypes from 'constants/action-types';
var actionTypes = require('constants/action-types');
export default function management(state, action) {
console.log('management store: got action', action);

Просмотреть файл

@ -1,6 +1,5 @@
'use strict';
import * as actionTypes from 'constants/action-types';
var actionTypes = require('constants/action-types');
export default function purchase(state, action) {

Просмотреть файл

@ -1,6 +1,4 @@
'use strict';
var actionTypes = require('constants/action-types');
import * as actionTypes from 'constants/action-types';
export default function user(state, action) {
if (action.type === actionTypes.USER_SIGNED_IN) {

Просмотреть файл

@ -1,11 +1,9 @@
'use strict';
/* global ga */
var settings = require('settings');
import settings from 'settings';
class Tracking {
export class Tracking {
constructor(opts) {
opts = opts || {};
@ -86,7 +84,7 @@ class Tracking {
}
}
module.exports = new Tracking({
export default new Tracking({
enabled: settings.tracking.enabled,
trackingId: settings.tracking.id,
});

Просмотреть файл

@ -1,5 +1,3 @@
'use strict';
/**
* Populates an object with defaults if the key is not yet defined.
* Similar to _.defaults except this takes only a single defaults object.
@ -8,30 +6,30 @@
* @returns {object}
*/
exports.defaults = (object, defaults) => {
export function defaults(object, defaults_) {
object = object || {};
defaults = defaults || {};
Object.keys(defaults).forEach(function(key) {
defaults_ = defaults_ || {};
Object.keys(defaults_).forEach(function(key) {
if (typeof object[key] === 'undefined') {
object[key] = defaults[key];
object[key] = defaults_[key];
}
});
return object;
};
}
exports.getMountNode = (node) => {
export function getMountNode(node){
return node || document.getElementById('view');
};
}
exports.gettext = (string) => {
export function gettext(string){
// Initial no-op gettext stand-in.
return string;
};
}
exports.parseQuery = (url) => {
export function parseQuery(url){
//
// Given a complete URL, parse the query string and return an
// object of parameters->values. This doesn't bother with repeated
@ -51,4 +49,4 @@ exports.parseQuery = (url) => {
}
return data;
};
}

Просмотреть файл

@ -1,33 +1,32 @@
'use strict';
import $ from 'jquery';
import React, { Component, PropTypes } from 'react';
var $ = require('jquery');
var React = require('react');
import CardForm from 'components/card-form';
import ProductDetail from 'components/product-detail';
import Spinner from 'components/spinner';
var CardForm = require('components/card-form');
var ProductDetail = require('components/product-detail');
var Spinner = require('components/spinner');
var tracking = require('tracking');
import { default as tracking } from 'tracking';
module.exports = React.createClass({
export default class CardDetailsView extends Component {
displayName: 'CardDetailsView',
static propTypes = {
productId: PropTypes.string.isRequired,
};
propTypes: {
productId: React.PropTypes.string.isRequired,
},
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
braintree_token: false,
};
},
}
componentDidMount: function() {
componentDidMount() {
console.log('Requesting braintree token');
// TODO: move this to a purchase action.
this.mounted = true;
tracking.setPage('/card-details');
$.ajax({
@ -35,16 +34,20 @@ module.exports = React.createClass({
url: '/api/braintree/token/generate/',
context: this,
}).then(function(data) {
if (this.isMounted()) {
if (this.mounted) {
this.setState({'braintree_token': data.token}); // eslint-disable-line
}
}).fail(function() {
// TODO: some error state.
console.log('failed to get braintree token');
});
},
}
render: function() {
componentWillUnmount() {
this.mounted = false;
}
render() {
if (this.state.braintree_token) {
return (
@ -61,5 +64,5 @@ module.exports = React.createClass({
} else {
return <Spinner />;
}
},
});
}
}

Просмотреть файл

@ -1,29 +1,25 @@
'use strict';
import React, { Component, PropTypes } from 'react';
var React = require('react');
import CardChoice from 'components/card-choice';
import ProductDetail from 'components/product-detail';
var CardChoice = require('components/card-choice');
var ProductDetail = require('components/product-detail');
var gettext = require('utils').gettext;
var tracking = require('tracking');
import { gettext } from 'utils';
import tracking from 'tracking';
module.exports = React.createClass({
export default class CardListing extends Component {
displayName: 'CardListing',
static propTypes = {
payWithNewCard: PropTypes.func.isRequired,
paymentMethods: PropTypes.array.isRequired,
productId: PropTypes.string.isRequired,
};
propTypes: {
payWithNewCard: React.PropTypes.func.isRequired,
paymentMethods: React.PropTypes.array.isRequired,
productId: React.PropTypes.string.isRequired,
},
componentDidMount: function() {
componentDidMount() {
tracking.setPage('/card-listing');
},
}
render: function() {
render() {
return (
<div className="card-listing">
<ProductDetail productId={this.props.productId} />
@ -37,5 +33,5 @@ module.exports = React.createClass({
</a>
</div>
);
},
});
}
}

Просмотреть файл

@ -1,15 +1,13 @@
'use strict';
import React from 'react';
var React = require('react');
import ProductDetail from 'components/product-detail';
import SubmitButton from 'components/submit-button';
var ProductDetail = require('components/product-detail');
var SubmitButton = require('components/submit-button');
var gettext = require('utils').gettext;
var tracking = require('tracking');
import { gettext } from 'utils';
import tracking from 'tracking';
module.exports = React.createClass({
export default React.createClass({
displayName: 'CompleteView',

Просмотреть файл

@ -1,29 +1,25 @@
'use strict';
import React, { Component, PropTypes } from 'react';
var React = require('react');
import Spinner from 'components/spinner';
var Spinner = require('components/spinner');
var gettext = require('utils').gettext;
var tracking = require('tracking');
import { gettext } from 'utils';
import tracking from 'tracking';
module.exports = React.createClass({
export default class Login extends Component {
displayName: 'Login',
static propTypes = {
accessToken: PropTypes.string.isRequired,
signIn: PropTypes.func.isRequired,
};
propTypes: {
accessToken: React.PropTypes.string.isRequired,
signIn: React.PropTypes.func.isRequired,
},
componentDidMount: function() {
componentDidMount() {
tracking.setPage('/login');
this.props.signIn(this.props.accessToken);
},
}
render: function() {
render() {
return <Spinner text={gettext('Signing in')}/>;
},
}
});
}

Просмотреть файл

@ -1,23 +1,19 @@
'use strict';
import React, { Component, PropTypes } from 'react';
var React = require('react');
import Modal from 'components/modal';
import CardList from 'components/card-list';
var Modal = require('components/modal');
var CardList = require('components/card-list');
var gettext = require('utils').gettext;
import { gettext } from 'utils';
module.exports = React.createClass({
export default class ManageCards extends Component {
displayName: 'ManageCards',
static propTypes = {
closeModal: PropTypes.func.isRequired,
paymentMethods: PropTypes.array.isRequired,
};
propTypes: {
closeModal: React.PropTypes.func.isRequired,
paymentMethods: React.PropTypes.array.isRequired,
},
render: function() {
render() {
return (
<Modal
handleClose={this.props.closeModal}
@ -25,7 +21,6 @@ module.exports = React.createClass({
<CardList cards={this.props.paymentMethods} />
</Modal>
);
},
});
}
}

Просмотреть файл

@ -1,24 +1,20 @@
'use strict';
import React, { Component, PropTypes } from 'react';
var React = require('react');
var {
import {
Accordion,
AccordionContent,
AccordionSection,
} = require('components/accordion');
AccordionSection } from 'components/accordion';
var gettext = require('utils').gettext;
import { gettext } from 'utils';
module.exports = React.createClass({
displayName: 'ManagementApp',
export default class Management extends Component {
propTypes: {
getPayMethods: React.PropTypes.func.isRequired,
},
static propTypes = {
getPayMethods: PropTypes.func.isRequired,
};
render: function() {
render() {
return (
<div>
@ -75,5 +71,5 @@ module.exports = React.createClass({
</div>
</div>
);
},
});
}
}

Просмотреть файл

@ -1,26 +1,23 @@
'use strict';
import React, { Component, PropTypes } from 'react';
var React = require('react');
var Modal = require('components/modal');
import Modal from 'components/modal';
import ErrorMessage from 'components/error';
var ErrorMessage = require('components/error');
module.exports = React.createClass({
export default class ModalError extends Component {
displayName: 'ModalError',
static propTypes = {
closeModal: PropTypes.func.isRequired,
error: PropTypes.object.isRequired,
};
propTypes: {
closeModal: React.PropTypes.func.isRequired,
error: React.PropTypes.object.isRequired,
},
render: function() {
render() {
return (
<Modal handleClose={this.props.closeModal}>
<ErrorMessage error={this.props.error} />
</Modal>
);
},
}
});
}

Просмотреть файл

@ -1,44 +1,55 @@
'use strict';
import React, { Component, PropTypes } from 'react';
var React = require('react');
var bindActionCreators = require('redux').bindActionCreators;
var Connector = require('redux/react').Connector;
import { bindActionCreators } from 'redux';
import { Connector } from 'redux/react';
var CardDetails = require('views/card-details');
var CardListing = require('views/card-listing');
var CompletePayment = require('views/complete-payment');
var purchaseActions = require('actions/purchase');
import * as purchaseActions from 'actions/purchase';
import CardDetails from 'views/card-details';
import CardListing from 'views/card-listing';
import CompletePayment from 'views/complete-payment';
module.exports = React.createClass({
export default class Purchase extends Component {
displayName: 'Purchase',
static propTypes = {
CardDetails: PropTypes.object,
CardListing: PropTypes.object,
CompletePayment: PropTypes.object,
productId: PropTypes.string.isRequired,
user: PropTypes.object.isRequired,
}
propTypes: {
productId: React.PropTypes.string.isRequired,
user: React.PropTypes.object.isRequired,
},
static defaultProps = {
CardDetails: CardDetails,
CardListing: CardListing,
CompletePayment: CompletePayment,
}
selectData: function(state) {
selectData(state) {
return {
purchase: state.purchase,
};
},
}
render () {
var props = this.props;
var CompletePayment_ = this.props.CompletePayment;
var CardListing_ = this.props.CardListing;
var CardDetails_ = this.props.CardDetails;
return (
<Connector select={this.selectData}>
{function(result) {
if (result.purchase.completed) {
return (
<CompletePayment productId={props.productId}
<CompletePayment_
productId={props.productId}
userEmail={props.user.email} />
);
} else if (result.purchase.payment_methods.length > 0) {
console.log('rendering card listing');
return (
<CardListing
<CardListing_
productId={props.productId}
paymentMethods={result.purchase.payment_methods}
{...bindActionCreators(purchaseActions, result.dispatch)}
@ -46,10 +57,10 @@ module.exports = React.createClass({
);
} else {
console.log('rendering card entry');
return <CardDetails productId={props.productId} />;
return <CardDetails_ productId={props.productId} />;
}
}}
</Connector>
);
},
});
}
}