Adding 3rd Party Account - revised

This commit is contained in:
Andrew Revinsky (DART)
2016-03-23 00:22:52 +03:00
parent faa4027305
commit 888544b700
13 changed files with 265 additions and 265 deletions

View File

@@ -13,17 +13,15 @@ export const accountsListRequested = makeActionCreator(T.ACCOUNTS.LIST_START);
export const accountsListReceived = makeActionCreator(T.ACCOUNTS.LIST_COMPLETE, 'payload');
export const accountsListError = makeActionCreator(T.ACCOUNTS.LIST_ERROR, 'error');
//export const accountsRefListRequested = makeActionCreator(T.ACCOUNTS.LIST_REF_START, 'id');
export const accountsRefListReceived = makeActionCreator(T.ACCOUNTS.LIST_REF_COMPLETE, 'payload');
//export const accountsRefListError = makeActionCreator(T.ACCOUNTS.LIST_REF_ERROR, 'id');
export const accountCreateStart = makeActionCreator(T.ACCOUNTS.CREATE_START);
export const accountCreateComplete = makeActionCreator(T.ACCOUNTS.CREATE_COMPLETE, 'accountId');
export const accountCreateComplete = makeActionCreator(T.ACCOUNTS.CREATE_COMPLETE, 'payload');
export const accountCreateError = makeActionCreator(T.ACCOUNTS.CREATE_ERROR, 'error');
export const accountCreateFormUpdate = makeActionCreator(T.ACCOUNTS.CREATE_FORM_UPDATE, 'key', 'value');
export const accountRefCreateStart = makeActionCreator(T.ACCOUNTS.CREATE_REF_START, 'data');
export const accountRefCreateComplete = makeActionCreator(T.ACCOUNTS.CREATE_REF_COMPLETE, 'data');
export const accountRefCreateStart = makeActionCreator(T.ACCOUNTS.CREATE_REF_START);
export const accountRefCreateComplete = makeActionCreator(T.ACCOUNTS.CREATE_REF_COMPLETE, 'payload');
export const accountRefCreateError = makeActionCreator(T.ACCOUNTS.CREATE_REF_ERROR, 'error');
export const accountRefCreateFormUpdate = makeActionCreator(T.ACCOUNTS.CREATE_REF_FORM_UPDATE, 'key', 'value');
@@ -66,6 +64,26 @@ export function accountCreate(customerId, payload) {
};
}
export function accountRefCreate(customerId, payload) {
return dispatch => {
dispatch(accountRefCreateStart());
return api.apiCreateRefAccount(customerId, payload)
.then(({ id }) => {
dispatch(accountRefCreateComplete({
...payload,
id
}));
dispatch(entityReceived(id, payload));
return dispatch(authenticate(true));
})
.catch(err => {
debugger;
dispatch(accountRefCreateError(err));
return Promise.resolve({ error: err });
})
};
}
@@ -151,7 +169,7 @@ export const createRefOwnerLookup = lookup => {
return { options: arr };
})
.catch(err => {
dispatch(createRefOwnerLookupComplete(null));
dispatch(createRefOwnerLookupComplete([]));
return { options: [] };
});
};
@@ -160,10 +178,18 @@ export const createRefOwnerLookup = lookup => {
export const createRefAccountLookup = customerId => {
return dispatch => {
dispatch(createRefAccountLookupStart());
return api.apiRetrieveUser(customerId)
return api.apiRetrieveAccounts(customerId)
.then(data => {
debugger;
const arr = data.map(({ accountId, title }) => ({
value: accountId,
label: title
}));
dispatch(createRefAccountLookupComplete(arr));
return { options: arr };
})
.catch(err => {
dispatch(createRefAccountLookupComplete([]));
return { options: [] };
});
};
};

View File

@@ -38,9 +38,7 @@ export function emailSignIn(body) {
return dispatch => {
// save previous endpoint key in case of failure
//var prevEndpointKey = getCurrentEndpointKey();
const endpointKey = 'default';
// necessary for fetch to recognize the response as an api request
//setCurrentEndpointKey(endpointKey);
//var currentEndpointKey = getCurrentEndpointKey();

View File

@@ -38,7 +38,9 @@ export function emailSignUp(body) {
.then(({data}) => {
dispatch(emailSignUpComplete(data));
const { email } = body;
return dispatch(emailSignIn({ email }));
return new Promise((rs, rj) => {
dispatch(emailSignIn({ email })).then(rs).catch(rj);
});
})
.catch(({errors}) => dispatch(emailSignUpError(errors)));

View File

@@ -1,75 +0,0 @@
import './DocumentForm.less'
import React, {PropTypes} from 'react'
import * as actions from '../actions/documentView'
import { pacomoTransformer } from '../utils/pacomo'
function updater(original, prop, fn) {
return e => fn(Object.assign({}, original, {[prop]: e.target.value}))
}
function preventDefault(fn) {
return e => {
e.preventDefault()
fn && fn(e)
}
}
const errorMap = (error, i) => <li className='error' key={i}>{error}</li>
const DocumentForm = ({
data,
errors,
onUpdate,
onSubmit,
onCancel,
}) =>
<form
onSubmit={preventDefault(onSubmit)}
noValidate={true}
>
<ul className='errors'>
{errors && Object.values(errors).map(errorMap)}
</ul>
<input
type='text'
className='title'
placeholder='Title'
onChange={updater(data, 'title', onUpdate)}
value={data.title || ''}
autoFocus
/>
<textarea
type='text'
className='content'
onChange={updater(data, 'content', onUpdate)}
value={data.content || ''}
/>
<footer className='buttons'>
<button
type='button'
className='cancel'
onClick={onCancel}
>
Cancel
</button>
<button
type='submit'
className='submit'
disabled={!onSubmit}
>
Save
</button>
</footer>
</form>
DocumentForm.propTypes = {
data: PropTypes.object.isRequired,
errors: PropTypes.object,
onUpdate: PropTypes.func.isRequired,
onSubmit: PropTypes.func,
onCancel: PropTypes.func.isRequired,
}
export default pacomoTransformer(DocumentForm)

View File

@@ -1,25 +0,0 @@
.app-DocumentForm {
width: 100%;
padding: 5px;
&-errors {
list-style: none;
padding: 0;
margin: 0;
}
&-error {
color: red;
margin-bottom: 5px;
}
&-title,
&-content {
display: block;
width: 100%;
margin-bottom: 5px;
}
&-cancel {
margin-right: 5px;
}
}

View File

@@ -1,71 +0,0 @@
import './DocumentList.less'
import React, {PropTypes} from 'react'
import * as actions from '../actions/documentListView'
import { pacomoTransformer } from '../utils/pacomo'
import Link from './Link'
function mapValue(fn) {
return e => fn(e.target.value)
}
const DocumentList = ({
id: activeId,
query,
documents,
onChangeQuery,
}) =>
<div>
<header className='header'>
<input
className='query'
type="text"
placeholder="Search..."
value={query}
onChange={mapValue(onChangeQuery)}
/>
</header>
<ul className='list'>
{documents.map(([id, data]) =>
<li
key={id}
className={{
'document-item': true,
'document-item-active': activeId == id,
}}
>
<Link
className='document-link'
name='documentEdit'
options={{id}}
>
{data.title}
</Link>
</li>
)}
<li
className={{
'add-item': true,
'add-item-active': activeId == 'new',
}}
>
<Link
className='add-link'
name='documentEdit'
options={{id: 'new'}}
>
Add Document
</Link>
</li>
</ul>
</div>
DocumentList.propTypes = {
id: PropTypes.string,
query: PropTypes.string,
documents: PropTypes.array.isRequired,
onChangeQuery: PropTypes.func.isRequired,
}
export default pacomoTransformer(DocumentList)

View File

@@ -1,33 +0,0 @@
.app-DocumentList {
width: 100%;
&-header {
width: 100%;
padding: 5px;
}
&-query {
width: 100%;
}
&-list {
list-style: none;
margin: 0;
padding: 0;
}
&-document-item-active,
&-add-item-active {
background-color: #f0f0f0;
}
&-document-link,
&-add-link {
display: block;
padding: 5px;
&:hover {
background-color: #f8f8f8;
}
}
}

View File

@@ -0,0 +1,53 @@
/**
* Created by andrew on 15/02/16.
*/
import React, { PropTypes } from "react";
import { Glyphicon } from "react-bootstrap";
class AuxErrorLabel extends React.Component {
static propTypes = {
label: PropTypes.string,
errors: PropTypes.array
};
static defaultProps = {
label: '',
errors: []
};
// <Input {...this.props}
// bsStyle={(this.props.errors.length) ? "error" : null}
// onChange={this.handleInput.bind(this)} />
render () {
const { errors } = this.props;
if (errors.length) {
return (
<div className='has-error'>
{ errors.map((err, i) => {
return (
<p className="control-label inline-error-item"
style={{paddingLeft: "20px", position: "relative", marginBottom: "28px"}}
key={i}>
<Glyphicon glyph="exclamation-sign"
style={{
position: "absolute",
left: 0,
top: 2
}}
/> {this.props.label} {err}
</p>
);
})}
</div>
);
} else {
return <span />;
}
}
}
export default AuxErrorLabel;

View File

@@ -85,10 +85,16 @@ export const bookmarkAccount = (state = {...initialState}, action) => {
...state.form,
[key]: value
};
const nextErrors = {
...state.errors,
[key]: null
};
return {
...state,
accountsDisabled: nextAccountsDisabled,
form: nextForm
form: nextForm,
errors: nextErrors
};
}
case T.ACCOUNTS.CREATE_REF_OWNER_LOOKUP_START:

View File

@@ -71,6 +71,23 @@ export function apiCreateAccount(customerId, {
}).then(parseResponse);
}
export function apiCreateRefAccount(customerId, {
owner, account: accountId, title, description }) {
return fetch(`${getCustomersUrl()}/${customerId}/toaccounts`, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "post",
body: root.JSON.stringify({
owner,
id: accountId,
title,
description })
}).then(parseResponse);
}
export function apiMakeTransfer(fromAccountId, {
account, amount, description }) {
@@ -146,7 +163,7 @@ export function apiRetrieveUsers(email) {
}
export function apiRetrieveUser(customerId) {
return fetch(`${getCustomersUrl()}?${makeQuery({ customerId })}`, {
return fetch(`${getCustomersUrl()}/${ customerId }`, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"

View File

@@ -67,8 +67,20 @@ class MyAccounts extends React.Component {
});
}
create3rdPartyAccountModalConfirmed() {
create3rdPartyAccountModalConfirmed(payload) {
const {
id: customerId
} = this.props.auth.user.attributes;
this.props.dispatch(A.accountRefCreate(customerId, payload))
.then(() => {
this.close.bind(this);
return this.props.dispatch(A.fetchOwnAccounts(customerId));
})
.catch(err => {
debugger;
this.props.dispatch(A.accountRefCreateError(err));
});
}
remove3rdPartyAccountModal(accountId, evt) {

View File

@@ -4,6 +4,9 @@
import React from "react";
//import { PageHeader } from "react-bootstrap";
import { connect } from "react-redux";
import { pushState } from 'redux-router';
import read from '../utils/readProp';
import { PageHeader, OverlayTrigger, Tooltip, Row, ButtonGroup, Table } from "react-bootstrap";
import * as BS from "react-bootstrap";
import { Link, IndexLink} from "react-router";
@@ -13,6 +16,22 @@ import EmailSignUpForm from "../controls/bootstrap/EmailSignUpForm";
export class SignUp extends React.Component {
checkRedirect(props) {
if (props.isAuthenticated) {
// redirect to login and add next param so we can redirect again after login
// const redirectAfterLogin = props.location.pathname;
props.dispatch(pushState(null, `/`));
}
}
componentWillMount() {
this.checkRedirect(this.props);
}
componentWillReceiveProps(nextProps) {
this.checkRedirect(nextProps);
}
render () {
return (
<div>
@@ -27,4 +46,9 @@ export class SignUp extends React.Component {
}
}
export default connect(({routes}) => ({routes}))(SignUp);
export default connect(({
routes,
app
}) => ({routes,
isAuthenticated: read(app, 'auth.user.isSignedIn', false)
}))(SignUp);

View File

@@ -2,9 +2,11 @@
* Created by andrew on 20/02/16.
*/
import React from "react";
import { PageHeader, OverlayTrigger, Modal, Tooltip, Grid, Col, Row, Nav, NavItem, ButtonGroup, Button, Table } from "react-bootstrap";
// import { PageHeader, OverlayTrigger, Modal, Tooltip, Grid, Col, Row, Nav, NavItem, ButtonGroup, Button, Table } from "react-bootstrap";
import * as BS from "react-bootstrap";
import ButtonLoader from "../../controls/bootstrap/ButtonLoader";
import Input from "../../controls/bootstrap/Input";
import AuxErrorLabel from "../../controls/bootstrap/AuxErrorLabel";
import read from '../../utils/readProp';
import { Link, IndexLink} from "react-router";
@@ -13,21 +15,66 @@ import Select from "react-select";
import * as A from '../../actions/entities';
const formValidation = (payload) => ['owner', 'account', 'title', 'description'].reduce((memo, prop) => {
let result = [];
const value = (payload[prop] || '').replace(/(^\s+)|(\s+$)/g, '');
switch (prop) {
case 'owner':
case 'account':
case 'title':
if (/^$/.test(value)) {
result.push('required');
}
}
switch (prop) {
case 'description':
if (value.length > 400) {
result.push('need to less than 400 symbols long');
}
}
if (result.length) {
memo[prop] = result;
memo.hasErrors = true;
}
return memo;
}, {});
export class Add3rdPartyAccountModal extends React.Component {
handleInput(key, value) {
this.props.dispatch(A.accountRefCreateFormUpdate(key, value));
switch(key) {
case 'owner':
debugger;
if (value) {
this.props.dispatch(A.createRefAccountLookup(value));
} else {
this.props.dispatch(A.createRefAccountLookupComplete({}));
this.props.dispatch(A.createRefAccountLookupComplete([]));
}
}
}
handleSubmit(evt) {
evt.preventDefault();
const payload = { ...this.props.data.form };
const validationErrors = formValidation(payload);
if (validationErrors.hasErrors) {
this.props.dispatch(A.accountRefCreateError(validationErrors));
return;
}
const { action } = this.props;
if (action) {
action(payload);
}
}
getOwnersOptions(input) {
if (!input) {
return Promise.resolve({ options: [] });
@@ -36,47 +83,59 @@ export class Add3rdPartyAccountModal extends React.Component {
}
render() {
const disabled = read(this.props.data, 'loading', false);
const disabled = false;
const ownersLookup = this.props.data.ownersLookup;
const ownersOptions = ownersLookup.options;
const ownersValue = ownersLookup.value;
const ownersLoading = ownersLookup.loading;
// onInputChange={this.ownerTypeIn.bind(this)}
// value={ownersValue}
const ownersLoading = read(this.props.data, 'ownersLookup.loading', false);
return (
<Modal show={this.props.show} onHide={this.props.onHide} key={1}>
<Modal.Header closeButton>
<Modal.Title>Add 3rd Party Account</Modal.Title>
</Modal.Header>
<Modal.Body>
<BS.Modal show={this.props.show} onHide={this.props.onHide} key={1}>
<BS.Modal.Header closeButton>
<BS.Modal.Title>Add 3rd Party Account</BS.Modal.Title>
</BS.Modal.Header>
<BS.Modal.Body>
<form>
<label>Owner:</label>
<Select
name="owner"
clearable={true}
isLoading={ownersLoading}
onBlurResetsInput={false}
asyncOptions={this.getOwnersOptions.bind(this)}
matchProp="label"
onChange={this.handleInput.bind(this, 'owner')} />
<div className="form-group">
<Select
name="owner"
onBlurResetsInput={false}
asyncOptions={this.getOwnersOptions.bind(this)}
matchProp="label"
onChange={this.handleInput.bind(this, 'owner')}
value={read(this.props.data, 'form.owner', '')}
disabled={disabled}
/>
<AuxErrorLabel
label="Owner:"
errors={read(this.props.data, 'errors.owner', [])}
/>
</div>
<label>Account:</label>
<Select
name="account"
value={this.props.theme}
clearable={false}
options={[
{value: "default", label: "Default"},
{value: "bootstrap", label: "Bootstrap"},
{value: "materialUi", label: "Material UI"}
]}
onChange={this.handleInput.bind(this, 'account')} />
<div className="form-group">
<Select
name="account"
value={read(this.props.data, 'form.account', '')}
disabled={ownersLoading || disabled}
clearable={false}
searchable={false}
options={read(this.props.data, 'accountsLookup.options', [])}
onChange={this.handleInput.bind(this, 'account')} />
<AuxErrorLabel
label="Owner:"
errors={read(this.props.data, 'errors.account', [])}
/>
</div>
<Input type="text"
className="account-create-description"
label="Title:"
placeholder="Title"
name="title"
disabled={disabled}
value={read(this.props.data, 'form.title', '')}
errors={read(this.props.data, 'errors.title', [])}
onChange={this.handleInput.bind(this, 'title')} />
<Input type="textarea"
className="account-create-description"
@@ -84,16 +143,23 @@ export class Add3rdPartyAccountModal extends React.Component {
placeholder="Description"
name="description"
disabled={disabled}
value={read(this.props, 'account.form.description', '')}
errors={read(this.props, 'account.errors.description', [])}
value={read(this.props.data, 'form.description', '')}
errors={read(this.props.data, 'errors.description', [])}
onChange={this.handleInput.bind(this, 'description')} />
</form>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.props.onHide}>Cancel</Button>
<Button bsStyle="primary" onClick={this.props.action}>Add</Button>
</Modal.Footer>
</Modal>
</BS.Modal.Body>
<BS.Modal.Footer>
<BS.Button onClick={this.props.onHide}>Cancel</BS.Button>
<ButtonLoader loading={read(this.props.data, 'loading', false)}
type="submit"
bsStyle="primary"
icon={<BS.Glyphicon glyph="plus" />}
disabled={disabled}
onClick={this.handleSubmit.bind(this)}>
Add
</ButtonLoader>
</BS.Modal.Footer>
</BS.Modal>
);
}
}