mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-27 19:42:11 +10:00
Merge pull request #101 from sharetribe/new-listing-connect-to-stripe
Connect provider bank account in new listing form
This commit is contained in:
commit
e6664f8d45
10 changed files with 191 additions and 32 deletions
|
|
@ -9,7 +9,7 @@ import css from './StripeBankAccountToken.css';
|
|||
|
||||
const DEBOUNCE_WAIT_TIME = 200;
|
||||
|
||||
const ErrorMessage = props => <p style={{ color: 'red' }}>{props.children}</p>;
|
||||
const ErrorMessage = props => <span style={{ color: 'red' }}>{props.children}</span>;
|
||||
ErrorMessage.propTypes = { children: PropTypes.any.isRequired };
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,3 +40,7 @@
|
|||
composes: error;
|
||||
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Field, reduxForm, formValueSelector, propTypes as formPropTypes } from 'redux-form';
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { isEqual } from 'lodash';
|
||||
import { arrayMove } from 'react-sortable-hoc';
|
||||
import config from '../../config';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import {
|
||||
noEmptyArray,
|
||||
maxLength,
|
||||
|
|
@ -17,6 +20,7 @@ import {
|
|||
LocationAutocompleteInput,
|
||||
Button,
|
||||
Input,
|
||||
StripeBankAccountToken,
|
||||
} from '../../components';
|
||||
import css from './EditListingForm.css';
|
||||
|
||||
|
|
@ -111,7 +115,7 @@ RenderAddImage.propTypes = {
|
|||
type: string.isRequired,
|
||||
};
|
||||
|
||||
class EditListingForm extends Component {
|
||||
export class EditListingFormComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleInitialize = this.handleInitialize.bind(this);
|
||||
|
|
@ -155,6 +159,7 @@ class EditListingForm extends Component {
|
|||
pristine,
|
||||
saveActionMsg = 'Save listing',
|
||||
submitting,
|
||||
selectedPlace,
|
||||
} = this.props;
|
||||
const titleRequiredMessage = intl.formatMessage({ id: 'EditListingForm.titleRequired' });
|
||||
const maxLengthMessage = intl.formatMessage(
|
||||
|
|
@ -172,11 +177,17 @@ class EditListingForm extends Component {
|
|||
const locationNotRecognizedMessage = intl.formatMessage({
|
||||
id: 'EditListingForm.locationNotRecognized',
|
||||
});
|
||||
const bankAccountNumberRequiredMessage = intl.formatMessage({
|
||||
id: 'EditListingForm.bankAccountNumberRequired',
|
||||
});
|
||||
const descriptionRequiredMessage = intl.formatMessage({
|
||||
id: 'EditListingForm.descriptionRequired',
|
||||
});
|
||||
const pricePlaceholderMessage = intl.formatMessage({ id: 'EditListingForm.pricePlaceholder' });
|
||||
|
||||
const country = selectedPlace ? selectedPlace.country : null;
|
||||
const currency = config.currencyConfig.currency;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Field
|
||||
|
|
@ -241,21 +252,55 @@ class EditListingForm extends Component {
|
|||
component={EnhancedTextArea}
|
||||
validate={[required(descriptionRequiredMessage)]}
|
||||
/>
|
||||
<Button type="submit" disabled={pristine || submitting || disabled}>{saveActionMsg}</Button>
|
||||
|
||||
{country
|
||||
? <Field
|
||||
name="bankAccountToken"
|
||||
component={StripeBankAccountToken}
|
||||
props={{ country, currency }}
|
||||
validate={required(bankAccountNumberRequiredMessage)}
|
||||
/>
|
||||
: null}
|
||||
|
||||
<Button
|
||||
className={css.submitButton}
|
||||
type="submit"
|
||||
disabled={pristine || submitting || disabled}
|
||||
>
|
||||
{saveActionMsg}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditListingForm.defaultProps = { initData: {} };
|
||||
EditListingFormComponent.defaultProps = { initData: {}, selectedPlace: null };
|
||||
|
||||
EditListingForm.propTypes = {
|
||||
EditListingFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
initData: shape({ title: string, description: string }),
|
||||
selectedPlace: propTypes.place,
|
||||
intl: intlShape.isRequired,
|
||||
onImageUpload: func.isRequired,
|
||||
onUpdateImageOrder: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
};
|
||||
|
||||
export default reduxForm({ form: 'EditListingForm' })(injectIntl(EditListingForm));
|
||||
const formName = 'EditListingForm';
|
||||
|
||||
// When a field depends on the value of another field, we must connect
|
||||
// to the store and select the required values to inject to the
|
||||
// component.
|
||||
//
|
||||
// See: http://redux-form.com/6.6.1/examples/selectingFormValues/
|
||||
const selector = formValueSelector(formName);
|
||||
const mapStateToProps = state => {
|
||||
const location = selector(state, 'location');
|
||||
return {
|
||||
selectedPlace: location && location.selectedPlace ? location.selectedPlace : null,
|
||||
};
|
||||
};
|
||||
|
||||
export default compose(connect(mapStateToProps), reduxForm({ form: formName }), injectIntl)(
|
||||
EditListingFormComponent
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@
|
|||
// (react-sortable-hoc uses them)
|
||||
import React from 'react';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import EditListingForm from './EditListingForm';
|
||||
import { fakeIntl, fakeFormProps } from '../../util/test-data';
|
||||
import { EditListingFormComponent } from './EditListingForm';
|
||||
|
||||
const noop = () => null;
|
||||
|
||||
describe('EditListingForm', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderShallow(
|
||||
<EditListingForm
|
||||
images={[]}
|
||||
<EditListingFormComponent
|
||||
{...fakeFormProps}
|
||||
intl={fakeIntl}
|
||||
dispatch={noop}
|
||||
onImageUpload={v => v}
|
||||
onSubmit={v => v}
|
||||
onUpdateImageOrder={v => v}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,86 @@
|
|||
exports[`EditListingForm matches snapshot 1`] = `
|
||||
<Connect(Form(InjectIntl(EditListingForm)))
|
||||
destroyOnUnmount={true}
|
||||
enableReinitialize={false}
|
||||
forceUnregisterOnUnmount={false}
|
||||
form="EditListingForm"
|
||||
getFormState={[Function]}
|
||||
images={Array []}
|
||||
keepDirtyOnReinitialize={false}
|
||||
onImageUpload={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateImageOrder={[Function]}
|
||||
persistentSubmitErrors={false}
|
||||
pure={true}
|
||||
shouldAsyncValidate={[Function]}
|
||||
shouldValidate={[Function]}
|
||||
touchOnBlur={true}
|
||||
touchOnChange={false} />
|
||||
<form
|
||||
onSubmit={[Function]}>
|
||||
<Field
|
||||
component={[Function]}
|
||||
label="Title"
|
||||
name="title"
|
||||
type="text"
|
||||
validate={
|
||||
Array [
|
||||
[Function],
|
||||
[Function],
|
||||
]
|
||||
} />
|
||||
<Field
|
||||
component={[Function]}
|
||||
currencyConfig={
|
||||
Object {
|
||||
"currency": "USD",
|
||||
"currencyDisplay": "symbol",
|
||||
"maximumFractionDigits": 2,
|
||||
"minimumFractionDigits": 2,
|
||||
"style": "currency",
|
||||
"subUnitDivisor": 100,
|
||||
"useGrouping": true,
|
||||
}
|
||||
}
|
||||
label="Price"
|
||||
name="price"
|
||||
placeholder="EditListingForm.pricePlaceholder"
|
||||
validate={
|
||||
Array [
|
||||
[Function],
|
||||
]
|
||||
} />
|
||||
<h3>
|
||||
Images
|
||||
</h3>
|
||||
<AddImages
|
||||
images={Array []}
|
||||
onSortEnd={[Function]}>
|
||||
<Field
|
||||
accept="image/*"
|
||||
component={[Function]}
|
||||
label="+ Add image"
|
||||
name="addImage"
|
||||
onChange={[Function]}
|
||||
type="file" />
|
||||
<Field
|
||||
component={[Function]}
|
||||
name="images"
|
||||
type="hidden"
|
||||
validate={
|
||||
Array [
|
||||
[Function],
|
||||
]
|
||||
} />
|
||||
</AddImages>
|
||||
<Field
|
||||
component={[Function]}
|
||||
format={null}
|
||||
label="Location"
|
||||
name="location"
|
||||
validate={
|
||||
Array [
|
||||
[Function],
|
||||
[Function],
|
||||
]
|
||||
} />
|
||||
<Field
|
||||
component={[Function]}
|
||||
label="Description"
|
||||
name="description"
|
||||
validate={
|
||||
Array [
|
||||
[Function],
|
||||
]
|
||||
} />
|
||||
<Button
|
||||
className={null}
|
||||
disabled={true}
|
||||
type="submit">
|
||||
Save listing
|
||||
</Button>
|
||||
</form>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-console */
|
||||
import { showListingsSuccess as globalShowListingsSuccess } from '../../ducks/sdk.duck';
|
||||
|
||||
const requestAction = actionType => params => ({ type: actionType, payload: { params } });
|
||||
|
|
@ -48,6 +47,7 @@ export default function reducer(state = initialState, action = {}) {
|
|||
case CREATE_LISTING_SUCCESS:
|
||||
return { ...state, submittedListingId: payload.data.id, redirectToListing: true };
|
||||
case CREATE_LISTING_ERROR:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(payload);
|
||||
return { ...state, createListingsError: payload, redirectToListing: false };
|
||||
|
||||
|
|
@ -56,6 +56,7 @@ export default function reducer(state = initialState, action = {}) {
|
|||
case SHOW_LISTINGS_SUCCESS:
|
||||
return initialState;
|
||||
case SHOW_LISTINGS_ERROR:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(payload);
|
||||
return { ...state, showListingsError: payload, redirectToListing: false };
|
||||
|
||||
|
|
@ -75,6 +76,7 @@ export default function reducer(state = initialState, action = {}) {
|
|||
return { ...state, images };
|
||||
}
|
||||
case UPLOAD_IMAGE_ERROR: {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(payload);
|
||||
const { id, error } = payload;
|
||||
const { file } = state.images[id];
|
||||
|
|
@ -138,8 +140,11 @@ export function requestShowListing(actionPayload) {
|
|||
};
|
||||
}
|
||||
|
||||
export function requestCreateListing(actionPayload) {
|
||||
export function requestCreateListing(data) {
|
||||
return (dispatch, getState, sdk) => {
|
||||
const { bankAccountToken, country, ...actionPayload } = data;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('TODO: call API with Stripe token:', bankAccountToken, 'and country:', country);
|
||||
dispatch(createListing(actionPayload));
|
||||
return sdk.listings
|
||||
.create(actionPayload)
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import {
|
|||
} from './EditListingPage.duck';
|
||||
|
||||
const formatRequestData = values => {
|
||||
const { description, images, location, price, title } = values;
|
||||
const { selectedPlace: { address, origin } } = location;
|
||||
const { description, images, location, price, title, bankAccountToken } = values;
|
||||
const { selectedPlace: { address, origin, country } } = location;
|
||||
|
||||
return {
|
||||
address,
|
||||
|
|
@ -24,6 +24,8 @@ const formatRequestData = values => {
|
|||
images: images.map(i => i.imageId),
|
||||
price,
|
||||
title,
|
||||
country,
|
||||
bankAccountToken,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
exports[`EditListingPageComponent matches snapshot 1`] = `
|
||||
<Connect(withRouter(PageLayout))
|
||||
title="EditListingPage.titleCreateListing">
|
||||
<ReduxForm
|
||||
<Connect(ReduxForm)
|
||||
images={Array []}
|
||||
initData={Object {}}
|
||||
onImageUpload={[Function]}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"AuthenticationPage.loginFailed": "The email and password you entered did not match our records. Please double-check and try again.",
|
||||
"AuthenticationPage.loginRequiredFor": "You must log in to view the page at",
|
||||
"EditListingForm.bankAccountNumberRequired": "You need to add a bank account number.",
|
||||
"EditListingForm.descriptionRequired": "You need to add a description.",
|
||||
"EditListingForm.imageRequired": "You need to add at least one image.",
|
||||
"EditListingForm.locationNotRecognized": "We didn't recognize this location. Please try another location.",
|
||||
|
|
|
|||
|
|
@ -47,3 +47,33 @@ export const fakeIntl = {
|
|||
formatTime: d => d,
|
||||
now: d => d,
|
||||
};
|
||||
|
||||
const noop = () => null;
|
||||
|
||||
export const fakeFormProps = {
|
||||
anyTouched: false,
|
||||
asyncValidating: false,
|
||||
dirty: false,
|
||||
form: 'fakeTestForm',
|
||||
invalid: false,
|
||||
pristine: true,
|
||||
clearSubmit: noop,
|
||||
touch: noop,
|
||||
untouch: noop,
|
||||
submit: noop,
|
||||
reset: noop,
|
||||
initialize: noop,
|
||||
handleSubmit: noop,
|
||||
destroy: noop,
|
||||
clearAsyncError: noop,
|
||||
change: noop,
|
||||
blur: noop,
|
||||
autofill: noop,
|
||||
asyncValidate: noop,
|
||||
valid: true,
|
||||
submitSucceeded: false,
|
||||
submitFailed: false,
|
||||
submitting: false,
|
||||
pure: true,
|
||||
initialized: true,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue