mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Refactor EditListingPhotosForm: use Form connector from Final Form
This commit is contained in:
parent
c38a2bc19b
commit
9c1130b519
2 changed files with 233 additions and 247 deletions
|
|
@ -1,51 +1,19 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bool, func, object, shape, string } from 'prop-types';
|
||||
import { compose } from 'redux';
|
||||
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { Form as FinalForm, Field } from 'react-final-form';
|
||||
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
|
||||
import { isEqual } from 'lodash';
|
||||
import { arrayMove } from 'react-sortable-hoc';
|
||||
import classNames from 'classnames';
|
||||
import { propTypes } from '../../util/types';
|
||||
import { nonEmptyArray } from '../../util/validators';
|
||||
import { nonEmptyArray, composeValidators } from '../../util/validators';
|
||||
import { isUploadListingImageOverLimitError } from '../../util/errors';
|
||||
import { Form, AddImages, Button, ValidationError } from '../../components';
|
||||
import { AddImages, Button, Form, ValidationError } from '../../components';
|
||||
|
||||
import css from './EditListingPhotosForm.css';
|
||||
|
||||
const ACCEPT_IMAGES = 'image/*';
|
||||
|
||||
// Add image wrapper. Label is the only visible element, file input is hidden.
|
||||
const RenderAddImage = props => {
|
||||
const { accept, input, label, type, disabled } = props;
|
||||
const { name, onChange } = input;
|
||||
const inputProps = { accept, id: name, name, onChange, type };
|
||||
return (
|
||||
<div className={css.addImageWrapper}>
|
||||
<div className={css.aspectRatioWrapper}>
|
||||
{disabled ? null : <input {...inputProps} className={css.addImageInput} />}
|
||||
<label htmlFor={name} className={css.addImage}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { bool, func, node, object, shape, string } = PropTypes;
|
||||
|
||||
RenderAddImage.propTypes = {
|
||||
accept: string.isRequired,
|
||||
input: shape({
|
||||
value: object,
|
||||
onChange: func.isRequired,
|
||||
name: string.isRequired,
|
||||
}).isRequired,
|
||||
label: node.isRequired,
|
||||
type: string.isRequired,
|
||||
disabled: bool.isRequired,
|
||||
};
|
||||
|
||||
export class EditListingPhotosFormComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -54,14 +22,7 @@ export class EditListingPhotosFormComponent extends Component {
|
|||
this.onSortEnd = this.onSortEnd.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!isEqual(this.props.images, nextProps.images)) {
|
||||
nextProps.change('images', nextProps.images);
|
||||
}
|
||||
}
|
||||
|
||||
onImageUploadHandler(event) {
|
||||
const file = event.target.files[0];
|
||||
onImageUploadHandler(file) {
|
||||
if (file) {
|
||||
this.setState({ imageUploadRequested: true });
|
||||
this.props
|
||||
|
|
@ -81,138 +42,176 @@ export class EditListingPhotosFormComponent extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
disabled,
|
||||
errors,
|
||||
handleSubmit,
|
||||
images,
|
||||
intl,
|
||||
invalid,
|
||||
saveActionMsg,
|
||||
submitting,
|
||||
updated,
|
||||
ready,
|
||||
updateError,
|
||||
updateInProgress,
|
||||
onRemoveImage,
|
||||
} = this.props;
|
||||
|
||||
const chooseImageText = (
|
||||
<span className={css.chooseImageText}>
|
||||
<span className={css.chooseImage}>
|
||||
<FormattedMessage id="EditListingPhotosForm.chooseImage" />
|
||||
</span>
|
||||
<span className={css.imageTypes}>
|
||||
<FormattedMessage id="EditListingPhotosForm.imageTypes" />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
const imageRequiredMessage = intl.formatMessage({ id: 'EditListingPhotosForm.imageRequired' });
|
||||
|
||||
const { createListingsError, showListingsError, uploadImageError } = errors;
|
||||
const uploadOverLimit = isUploadListingImageOverLimitError(uploadImageError);
|
||||
|
||||
let uploadImageFailed = null;
|
||||
|
||||
if (uploadOverLimit) {
|
||||
uploadImageFailed = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadOverLimit" />
|
||||
</p>
|
||||
);
|
||||
} else if (uploadImageError) {
|
||||
uploadImageFailed = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadFailed" />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: These error messages are here since Photos panel is the last visible panel
|
||||
// before creating a new listing. If that order is changed, these should be changed too.
|
||||
// Create and show listing errors are shown above submit button
|
||||
const createListingFailed = createListingsError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.createListingFailed" />
|
||||
</p>
|
||||
) : null;
|
||||
const showListingFailed = showListingsError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.showListingFailed" />
|
||||
</p>
|
||||
) : null;
|
||||
|
||||
// Updating listing (edit mode) failed
|
||||
const errorMessage = updateError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.updateFailed" />
|
||||
</p>
|
||||
) : null;
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
|
||||
const submitReady = updated || ready;
|
||||
const submitInProgress = submitting || updateInProgress;
|
||||
const submitDisabled =
|
||||
invalid || disabled || submitInProgress || this.state.imageUploadRequested || ready;
|
||||
|
||||
return (
|
||||
<Form className={classes} onSubmit={handleSubmit}>
|
||||
{errorMessage}
|
||||
<AddImages
|
||||
className={css.imagesField}
|
||||
images={images}
|
||||
onSortEnd={this.onSortEnd}
|
||||
thumbnailClassName={css.thumbnail}
|
||||
savedImageAltText={intl.formatMessage({ id: 'EditListingPhotosForm.savedImageAltText' })}
|
||||
onRemoveImage={onRemoveImage}
|
||||
>
|
||||
<Field
|
||||
id="EditListingPhotosForm.AddImages"
|
||||
accept={ACCEPT_IMAGES}
|
||||
component={RenderAddImage}
|
||||
label={chooseImageText}
|
||||
name="addImage"
|
||||
onChange={this.onImageUploadHandler}
|
||||
type="file"
|
||||
disabled={this.state.imageUploadRequested}
|
||||
/>
|
||||
<FinalForm
|
||||
{...this.props}
|
||||
onImageUploadHandler={this.onImageUploadHandler}
|
||||
imageUploadRequested={this.state.imageUploadRequested}
|
||||
initialValues={{ images: this.props.images }}
|
||||
render={fieldRenderProps => {
|
||||
const {
|
||||
blur,
|
||||
change,
|
||||
className,
|
||||
disabled,
|
||||
errors,
|
||||
handleSubmit,
|
||||
images,
|
||||
imageUploadRequested,
|
||||
intl,
|
||||
invalid,
|
||||
onImageUploadHandler,
|
||||
onRemoveImage,
|
||||
ready,
|
||||
saveActionMsg,
|
||||
submitting,
|
||||
updated,
|
||||
updateError,
|
||||
updateInProgress,
|
||||
} = fieldRenderProps;
|
||||
|
||||
<Field
|
||||
component={props => {
|
||||
const { input, type, meta } = props;
|
||||
return (
|
||||
<div className={css.imageRequiredWrapper}>
|
||||
<input {...input} type={type} />
|
||||
<ValidationError fieldMeta={meta} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
name="images"
|
||||
type="hidden"
|
||||
validate={[nonEmptyArray(imageRequiredMessage)]}
|
||||
/>
|
||||
</AddImages>
|
||||
{uploadImageFailed}
|
||||
const chooseImageText = (
|
||||
<span className={css.chooseImageText}>
|
||||
<span className={css.chooseImage}>
|
||||
<FormattedMessage id="EditListingPhotosForm.chooseImage" />
|
||||
</span>
|
||||
<span className={css.imageTypes}>
|
||||
<FormattedMessage id="EditListingPhotosForm.imageTypes" />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
<p className={css.tip}>
|
||||
<FormattedMessage id="EditListingPhotosForm.addImagesTip" />
|
||||
</p>
|
||||
{createListingFailed}
|
||||
{showListingFailed}
|
||||
const imageRequiredMessage = intl.formatMessage({
|
||||
id: 'EditListingPhotosForm.imageRequired',
|
||||
});
|
||||
|
||||
<Button
|
||||
className={css.submitButton}
|
||||
type="submit"
|
||||
inProgress={submitInProgress}
|
||||
disabled={submitDisabled}
|
||||
ready={submitReady}
|
||||
>
|
||||
{saveActionMsg}
|
||||
</Button>
|
||||
</Form>
|
||||
const { createListingsError, showListingsError, uploadImageError } = errors;
|
||||
const uploadOverLimit = isUploadListingImageOverLimitError(uploadImageError);
|
||||
|
||||
let uploadImageFailed = null;
|
||||
|
||||
if (uploadOverLimit) {
|
||||
uploadImageFailed = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadOverLimit" />
|
||||
</p>
|
||||
);
|
||||
} else if (uploadImageError) {
|
||||
uploadImageFailed = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadFailed" />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: These error messages are here since Photos panel is the last visible panel
|
||||
// before creating a new listing. If that order is changed, these should be changed too.
|
||||
// Create and show listing errors are shown above submit button
|
||||
const createListingFailed = createListingsError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.createListingFailed" />
|
||||
</p>
|
||||
) : null;
|
||||
const showListingFailed = showListingsError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.showListingFailed" />
|
||||
</p>
|
||||
) : null;
|
||||
|
||||
const submitReady = updated || ready;
|
||||
const submitInProgress = submitting || updateInProgress;
|
||||
const submitDisabled =
|
||||
invalid || disabled || submitInProgress || imageUploadRequested || ready;
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} className={classes}>
|
||||
{updateError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.updateFailed" />
|
||||
</p>
|
||||
) : null}
|
||||
<AddImages
|
||||
className={css.imagesField}
|
||||
images={images}
|
||||
onSortEnd={this.onSortEnd}
|
||||
thumbnailClassName={css.thumbnail}
|
||||
savedImageAltText={intl.formatMessage({
|
||||
id: 'EditListingPhotosForm.savedImageAltText',
|
||||
})}
|
||||
onRemoveImage={onRemoveImage}
|
||||
>
|
||||
<Field
|
||||
id="addImage"
|
||||
name="addImage"
|
||||
accept={ACCEPT_IMAGES}
|
||||
form={null}
|
||||
label={chooseImageText}
|
||||
type="file"
|
||||
disabled={imageUploadRequested}
|
||||
>
|
||||
{fieldprops => {
|
||||
const { accept, input, label, type, disabled } = fieldprops;
|
||||
const { name } = input;
|
||||
const onChange = e => {
|
||||
const file = e.target.files[0];
|
||||
change(`addImage`, file);
|
||||
blur(`addImage`);
|
||||
onImageUploadHandler(file);
|
||||
};
|
||||
const inputProps = { accept, id: name, name, onChange, type };
|
||||
return (
|
||||
<div className={css.addImageWrapper}>
|
||||
<div className={css.aspectRatioWrapper}>
|
||||
{disabled ? null : (
|
||||
<input {...inputProps} className={css.addImageInput} />
|
||||
)}
|
||||
<label htmlFor={name} className={css.addImage}>
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
component={props => {
|
||||
const { input, type, meta } = props;
|
||||
return (
|
||||
<div className={css.imageRequiredWrapper}>
|
||||
<input {...input} type={type} />
|
||||
<ValidationError fieldMeta={meta} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
name="images"
|
||||
type="hidden"
|
||||
validate={composeValidators(nonEmptyArray(imageRequiredMessage))}
|
||||
/>
|
||||
</AddImages>
|
||||
{uploadImageFailed}
|
||||
|
||||
<p className={css.tip}>
|
||||
<FormattedMessage id="EditListingPhotosForm.addImagesTip" />
|
||||
</p>
|
||||
{createListingFailed}
|
||||
{showListingFailed}
|
||||
|
||||
<Button
|
||||
className={css.submitButton}
|
||||
type="submit"
|
||||
inProgress={submitInProgress}
|
||||
disabled={submitDisabled}
|
||||
ready={submitReady}
|
||||
>
|
||||
{saveActionMsg}
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -220,7 +219,6 @@ export class EditListingPhotosFormComponent extends Component {
|
|||
EditListingPhotosFormComponent.defaultProps = { errors: {}, updateError: null };
|
||||
|
||||
EditListingPhotosFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
errors: shape({
|
||||
createListingsError: object,
|
||||
showListingsError: object,
|
||||
|
|
@ -238,6 +236,4 @@ EditListingPhotosFormComponent.propTypes = {
|
|||
onRemoveImage: func.isRequired,
|
||||
};
|
||||
|
||||
const formName = 'EditListingPhotosForm';
|
||||
|
||||
export default compose(reduxForm({ form: formName }), injectIntl)(EditListingPhotosFormComponent);
|
||||
export default compose(injectIntl)(EditListingPhotosFormComponent);
|
||||
|
|
|
|||
|
|
@ -1,76 +1,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EditListingPhotosForm matches snapshot 1`] = `
|
||||
<Form
|
||||
className=""
|
||||
contentRef={null}
|
||||
<ReactFinalForm(4.3.1)(3.1.5)
|
||||
anyTouched={false}
|
||||
asyncValidate={[Function]}
|
||||
asyncValidating={false}
|
||||
autofill={[Function]}
|
||||
blur={[Function]}
|
||||
change={[Function]}
|
||||
clearAsyncError={[Function]}
|
||||
clearFields={[Function]}
|
||||
clearSubmit={[Function]}
|
||||
clearSubmitErrors={[Function]}
|
||||
destroy={[Function]}
|
||||
dirty={false}
|
||||
dispatch={[Function]}
|
||||
errors={Object {}}
|
||||
form="fakeTestForm"
|
||||
handleSubmit={[Function]}
|
||||
imageUploadRequested={false}
|
||||
initialValues={
|
||||
Object {
|
||||
"images": undefined,
|
||||
}
|
||||
}
|
||||
initialize={[Function]}
|
||||
initialized={true}
|
||||
intl={
|
||||
Object {
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"now": [Function],
|
||||
}
|
||||
}
|
||||
invalid={false}
|
||||
onImageUpload={[Function]}
|
||||
onImageUploadHandler={[Function]}
|
||||
onRemoveImage={[Function]}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<AddImages
|
||||
className={null}
|
||||
images={Array []}
|
||||
onRemoveImage={[Function]}
|
||||
onSortEnd={[Function]}
|
||||
savedImageAltText="EditListingPhotosForm.savedImageAltText"
|
||||
thumbnailClassName={null}
|
||||
>
|
||||
<Field
|
||||
accept="image/*"
|
||||
component={[Function]}
|
||||
disabled={false}
|
||||
id="EditListingPhotosForm.AddImages"
|
||||
label={
|
||||
<span
|
||||
className={undefined}
|
||||
>
|
||||
<span
|
||||
className={undefined}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="EditListingPhotosForm.chooseImage"
|
||||
values={Object {}}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={undefined}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="EditListingPhotosForm.imageTypes"
|
||||
values={Object {}}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
name="addImage"
|
||||
onChange={[Function]}
|
||||
type="file"
|
||||
/>
|
||||
<Field
|
||||
component={[Function]}
|
||||
name="images"
|
||||
type="hidden"
|
||||
validate={
|
||||
Array [
|
||||
[Function],
|
||||
]
|
||||
}
|
||||
/>
|
||||
</AddImages>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="EditListingPhotosForm.addImagesTip"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
<Button
|
||||
className={null}
|
||||
disabled={false}
|
||||
inProgress={false}
|
||||
ready={false}
|
||||
rootClassName={null}
|
||||
type="submit"
|
||||
>
|
||||
Save photos
|
||||
</Button>
|
||||
</Form>
|
||||
onUpdateImageOrder={[Function]}
|
||||
pristine={true}
|
||||
pure={true}
|
||||
ready={false}
|
||||
render={[Function]}
|
||||
reset={[Function]}
|
||||
resetSection={[Function]}
|
||||
saveActionMsg="Save photos"
|
||||
stripeConnected={false}
|
||||
submit={[Function]}
|
||||
submitFailed={false}
|
||||
submitSucceeded={false}
|
||||
submitting={false}
|
||||
touch={[Function]}
|
||||
untouch={[Function]}
|
||||
updateError={null}
|
||||
updateInProgress={false}
|
||||
updated={false}
|
||||
valid={true}
|
||||
/>
|
||||
`;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue