Use redux with management interface (fixes #217)
This commit is contained in:
Родитель
a3f42c000f
Коммит
790b185253
|
@ -21,7 +21,7 @@
|
|||
"require": false,
|
||||
},
|
||||
"rules": {
|
||||
"block-scoped-var": 2,
|
||||
"block-scoped-var": 0,
|
||||
"camelcase": 0,
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
"comma-style": [2, "last"],
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
module.exports = {
|
||||
error: function(debugMessage) {
|
||||
return {
|
||||
type: actionTypes.APP_ERROR,
|
||||
error: {debugMessage: debugMessage},
|
||||
};
|
||||
},
|
||||
};
|
||||
export function error(debugMessage) {
|
||||
return {
|
||||
type: actionTypes.APP_ERROR,
|
||||
error: {debugMessage: debugMessage},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
import $ from 'jquery';
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
|
||||
export function error(debugMessage) {
|
||||
return {
|
||||
type: actionTypes.APP_ERROR,
|
||||
error: {debugMessage: debugMessage},
|
||||
};
|
||||
}
|
||||
|
||||
export function manageCards() {
|
||||
return function(dispatch) {
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: '/api/braintree/mozilla/paymethod/',
|
||||
context: this,
|
||||
}).then(function(data) {
|
||||
dispatch({
|
||||
type: actionTypes.MANAGE_CARD_LIST,
|
||||
management: {
|
||||
paymentMethods: data,
|
||||
},
|
||||
});
|
||||
}).fail(function() {
|
||||
console.log('Retrieving cards failed');
|
||||
dispatch(error('Retrieving cards failed'));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function closeModal() {
|
||||
console.log('closeModal');
|
||||
return {
|
||||
type: actionTypes.MANAGE_CLOSE_MODAL,
|
||||
};
|
||||
}
|
|
@ -1,22 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
var actionTypes = require('constants/action-types');
|
||||
|
||||
import * as actionTypes from 'constants/action-types';
|
||||
|
||||
// TODO: expand these actions to encapsulate the Ajax
|
||||
// logic more directly. This will allow the Ajax requests to
|
||||
// be tested more easily. CardForm and CardChoice will need
|
||||
// to be refactored.
|
||||
|
||||
exports.complete = function() {
|
||||
export function complete() {
|
||||
return {
|
||||
type: actionTypes.COMPLETE_PURCHASE,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
exports.payWithNewCard = function() {
|
||||
export function payWithNewCard() {
|
||||
return {
|
||||
type: actionTypes.PAY_WITH_NEW_CARD,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,42 +5,40 @@ var $ = require('jquery');
|
|||
var appActions = require('./app');
|
||||
var actionTypes = require('constants/action-types');
|
||||
|
||||
module.exports = {
|
||||
|
||||
signIn: function(accessToken) {
|
||||
return function(dispatch) {
|
||||
$.ajax({
|
||||
data: {
|
||||
access_token: accessToken,
|
||||
export function signIn(accessToken) {
|
||||
return function(dispatch) {
|
||||
$.ajax({
|
||||
data: {
|
||||
access_token: accessToken,
|
||||
},
|
||||
method: 'post',
|
||||
url: '/api/auth/sign-in/',
|
||||
context: this,
|
||||
}).then(function(data) {
|
||||
|
||||
console.log('setting CSRF token for subsequent requests:',
|
||||
data.csrf_token);
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRFToken': data.csrf_token,
|
||||
},
|
||||
method: 'post',
|
||||
url: '/api/auth/sign-in/',
|
||||
context: this,
|
||||
}).then(function(data) {
|
||||
|
||||
console.log('setting CSRF token for subsequent requests:',
|
||||
data.csrf_token);
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'X-CSRFToken': data.csrf_token,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('login succeeded, setting user');
|
||||
dispatch({
|
||||
type: actionTypes.USER_SIGNED_IN,
|
||||
user: {
|
||||
email: data.buyer_email,
|
||||
payment_methods: data.payment_methods,
|
||||
},
|
||||
});
|
||||
|
||||
}).fail(function() {
|
||||
|
||||
console.log('login failed');
|
||||
dispatch(appActions.error('user login failed'));
|
||||
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
console.log('login succeeded, setting user');
|
||||
dispatch({
|
||||
type: actionTypes.USER_SIGNED_IN,
|
||||
user: {
|
||||
email: data.buyer_email,
|
||||
payment_methods: data.payment_methods,
|
||||
},
|
||||
});
|
||||
|
||||
}).fail(function() {
|
||||
|
||||
console.log('login failed');
|
||||
dispatch(appActions.error('user login failed'));
|
||||
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,89 +1,67 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionSection} = require('components/accordion');
|
||||
var Provider = require('redux/react').Provider;
|
||||
var Connector = require('redux/react').Connector;
|
||||
var bindActionCreators = require('redux').bindActionCreators;
|
||||
|
||||
var Modal = require('components/modal');
|
||||
var reduxConfig = require('redux-config');
|
||||
var managementActions = require('actions/management');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
var ModalError = require('views/modal-error');
|
||||
var Management = require('views/management');
|
||||
var ManageCards = require('views/manage-cards');
|
||||
|
||||
|
||||
var Management = React.createClass({
|
||||
var App = React.createClass({
|
||||
|
||||
displayName: 'ManagementApp',
|
||||
|
||||
render: function() {
|
||||
selectData: function(state) {
|
||||
return {
|
||||
management: state.management,
|
||||
};
|
||||
},
|
||||
|
||||
renderChild(result) {
|
||||
var actions = bindActionCreators(managementActions, result.dispatch);
|
||||
var children = [];
|
||||
|
||||
if (result.management.error) {
|
||||
children.push(
|
||||
<ModalError {...actions} error={result.management.error} />
|
||||
);
|
||||
} else if (result.management.paymentMethods) {
|
||||
children.push((
|
||||
<ManageCards {...actions}
|
||||
paymentMethods={result.management.paymentMethods} />
|
||||
));
|
||||
}
|
||||
children.push(<Management {...actions} />);
|
||||
return <div>{children}</div>;
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
|
||||
<div>
|
||||
<header className="top-nav">
|
||||
<h1 className="logo">Firefox Payments</h1>
|
||||
<button>{gettext('Sign Out')}</button>
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
<div className="user">
|
||||
<p>Hello, placeholder@placeholder.com</p>
|
||||
</div>
|
||||
|
||||
<Accordion>
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Payment Accounts')}</h2>
|
||||
<button data-activate>{gettext('Change')}</button>
|
||||
</header>
|
||||
<AccordionContent>
|
||||
<p>Payment list will go here</p>
|
||||
<ul>
|
||||
<li>4111 1111 1111 1111</li>
|
||||
<li>4222 2222 2222 2222</li>
|
||||
</ul>
|
||||
</AccordionContent>
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Receipts and Subscriptions')}</h2>
|
||||
<button data-activate>{gettext('View/Change')}</button>
|
||||
</header>
|
||||
<AccordionContent>
|
||||
<p>Placeholder</p>
|
||||
</AccordionContent>
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Email Address and Password')}</h2>
|
||||
<a className="button"
|
||||
href="https://mozilla.org/"
|
||||
target="_blank">{gettext('Change')}</a>
|
||||
<p>placeholder@placeholder.com</p>
|
||||
</header>
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Delete Account')}</h2>
|
||||
<button data-activate>{gettext('Delete')}</button>
|
||||
</header>
|
||||
<AccordionContent>
|
||||
<p>Placeholder content</p>
|
||||
</AccordionContent>
|
||||
</AccordionSection>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
<Connector select={this.selectData}>
|
||||
{this.renderChild}
|
||||
</Connector>
|
||||
</main>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
component: Management,
|
||||
component: App,
|
||||
init: function() {
|
||||
React.render(<Modal />, document.getElementById('modal'));
|
||||
React.render(<Management />, document.getElementById('view'));
|
||||
React.render((
|
||||
<Provider redux={reduxConfig.default}>
|
||||
{function() {
|
||||
return <App/>;
|
||||
}}
|
||||
</Provider>
|
||||
), document.body);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ var Accordion = React.createClass({
|
|||
displayName: 'Accordion',
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.array,
|
||||
children: React.PropTypes.node.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
@ -57,8 +57,8 @@ var AccordionSection = React.createClass({
|
|||
displayName: 'AccordionSection',
|
||||
|
||||
propTypes: {
|
||||
activate: React.PropTypes.function,
|
||||
children: React.PropTypes.array,
|
||||
activate: React.PropTypes.func,
|
||||
children: React.PropTypes.node.isRequired,
|
||||
isActive: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
|
@ -82,7 +82,7 @@ var AccordionContent = React.createClass({
|
|||
displayName: 'AccordionContent',
|
||||
|
||||
propTypes: {
|
||||
children: React.PropTypes.array,
|
||||
children: React.PropTypes.node.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -18,13 +18,6 @@ module.exports = React.createClass({
|
|||
onCardChange: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
card: (this.props.cards.length === 1 ?
|
||||
this.props.cards[0].resource_uri : null),
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cardData = this.props.cards;
|
||||
var cardList = [];
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
APP_ERROR: 'APP_ERROR',
|
||||
USER_SIGNED_IN: 'USER_SIGNED_IN',
|
||||
COMPLETE_PURCHASE: 'COMPLETE_PURCHASE',
|
||||
PAY_WITH_NEW_CARD: 'PAY_WITH_NEW_CARD',
|
||||
};
|
||||
export const APP_ERROR = 'APP_ERROR';
|
||||
export const APP_CLEAR_ERROR = 'APP_CLEAR_ERROR';
|
||||
export const USER_SIGNED_IN = 'USER_SIGNED_IN';
|
||||
export const COMPLETE_PURCHASE = 'COMPLETE_PURCHASE';
|
||||
export const PAY_WITH_NEW_CARD = 'PAY_WITH_NEW_CARD';
|
||||
export const MANAGE_CARD_LIST = 'MANAGE_CARD_LIST';
|
||||
export const MANAGE_CLOSE_MODAL = 'MANAGE_CLOSE_MODAL';
|
||||
|
|
|
@ -11,11 +11,5 @@ export default function app(state, action) {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === actionTypes.CLEAR_ERROR) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return state || {};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,21 +5,42 @@ var actionTypes = require('constants/action-types');
|
|||
export default function management(state, action) {
|
||||
console.log('management store: got action', action);
|
||||
|
||||
if (action.type === actionTypes.APP_ERROR) {
|
||||
return {
|
||||
loading: false,
|
||||
error: {
|
||||
debugMessage: action.error.debugMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === actionTypes.MANAGE_MODAL_LOADING) {
|
||||
return {
|
||||
loading: true,
|
||||
error: null,
|
||||
paymentMethods: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === actionTypes.MANAGE_CARD_LIST) {
|
||||
return {
|
||||
showModal: true,
|
||||
payment_methods: action.user.payment_methods,
|
||||
loading: false,
|
||||
paymentMethods: action.management.paymentMethods,
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === actionTypes.MANAGE_CLOSE_MODAL) {
|
||||
return {
|
||||
showModal: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
paymentMethods: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return state || {
|
||||
showModal: false,
|
||||
payment_methods: [],
|
||||
error: null,
|
||||
loading: false,
|
||||
paymentMethods: null,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var Modal = require('components/modal');
|
||||
var CardList = require('components/card-list');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
displayName: 'ManageCards',
|
||||
|
||||
propTypes: {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
paymentMethods: React.PropTypes.array.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<Modal
|
||||
handleClose={this.props.closeModal}
|
||||
title={gettext('Payment Methods')}>
|
||||
<CardList cards={this.props.paymentMethods} />
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
|
||||
var {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionSection,
|
||||
} = require('components/accordion');
|
||||
|
||||
var gettext = require('utils').gettext;
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
displayName: 'ManagementApp',
|
||||
|
||||
propTypes: {
|
||||
manageCards: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
|
||||
<div>
|
||||
<header className="top-nav">
|
||||
<h1 className="logo">Firefox Payments</h1>
|
||||
<button>{gettext('Sign Out')}</button>
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
<div className="user">
|
||||
<p>Hello, placeholder@placeholder.com</p>
|
||||
</div>
|
||||
|
||||
<Accordion>
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Payment Accounts')}</h2>
|
||||
<button
|
||||
onClick={this.props.manageCards}>{gettext('Change')}
|
||||
</button>
|
||||
</header>
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Receipts and Subscriptions')}</h2>
|
||||
<button data-activate>{gettext('View/Change')}</button>
|
||||
</header>
|
||||
<AccordionContent>
|
||||
<p>Placeholder</p>
|
||||
</AccordionContent>
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Email Address and Password')}</h2>
|
||||
<a className="button"
|
||||
href="https://mozilla.org/"
|
||||
target="_blank">{gettext('Change')}</a>
|
||||
<p>placeholder@placeholder.com</p>
|
||||
</header>
|
||||
</AccordionSection>
|
||||
|
||||
<AccordionSection>
|
||||
<header>
|
||||
<h2>{gettext('Delete Account')}</h2>
|
||||
<button data-activate>{gettext('Delete')}</button>
|
||||
</header>
|
||||
<AccordionContent>
|
||||
<p>Placeholder content</p>
|
||||
</AccordionContent>
|
||||
</AccordionSection>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var Modal = require('components/modal');
|
||||
|
||||
var ErrorMessage = require('components/error');
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
displayName: 'ModalError',
|
||||
|
||||
propTypes: {
|
||||
closeModal: React.PropTypes.func.isRequired,
|
||||
error: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<Modal handleClose={this.props.closeModal}>
|
||||
<ErrorMessage error={this.props.error} />
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
.content {
|
||||
margin: 0 auto;
|
||||
max-width: 700px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
|
|
|
@ -14,11 +14,11 @@ newWebpackConfig.module.loaders[0].loaders.unshift('react-hot');
|
|||
module.exports = {
|
||||
options: {
|
||||
host: 'localhost',
|
||||
inline: true,
|
||||
hot: true,
|
||||
publicPath: '/dist/',
|
||||
contentBase: 'public/',
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
inline: true,
|
||||
publicPath: '/dist/',
|
||||
},
|
||||
start: {
|
||||
webpack: newWebpackConfig,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
var actionTypes = require('constants/action-types');
|
||||
var appActions = require('actions/app');
|
||||
var purchaseActions = require('actions/purchase');
|
||||
var dataStore = require('data-store');
|
||||
var dataStore = require('stores');
|
||||
|
||||
|
||||
describe('app', function() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче