mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Implement listing editing
This commit is contained in:
parent
b8b12c1c40
commit
d827128703
29 changed files with 359 additions and 61 deletions
|
|
@ -23,12 +23,16 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Firefox doesn't support image aspect ratio inside flexbox */
|
||||
.aspectRatioWrapper {
|
||||
padding-bottom: 60%; /* 5:3 Aspect Ratio = 100% / (w / h) */
|
||||
.threeToTwoWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.thumbnailImage {
|
||||
.aspectWrapper {
|
||||
padding-bottom: calc(100% * (2 / 3));
|
||||
}
|
||||
|
||||
.rootForImage {
|
||||
/* Layout - image will take space defined by aspect ratio wrapper */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import React, { Component, PropTypes } from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import { SortableContainer } from 'react-sortable-hoc';
|
||||
import classNames from 'classnames';
|
||||
import { Promised } from '../../components';
|
||||
import { Promised, ResponsiveImage } from '../../components';
|
||||
import { uuid } from '../../util/propTypes';
|
||||
import css from './AddImages.css';
|
||||
|
||||
|
|
@ -52,8 +52,8 @@ class Thumbnail extends Component {
|
|||
renderFulfilled={dataURL => {
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={css.aspectRatioWrapper}>
|
||||
<img src={dataURL} alt={file.name} className={css.thumbnailImage} />
|
||||
<div className={css.aspectWrapper}>
|
||||
<img src={dataURL} alt={file.name} className={css.rootForImage} />
|
||||
</div>
|
||||
{uploadingOverlay}
|
||||
</div>
|
||||
|
|
@ -69,7 +69,7 @@ class Thumbnail extends Component {
|
|||
|
||||
Thumbnail.defaultProps = { className: null, imageId: null };
|
||||
|
||||
const { any, array, func, node, string } = PropTypes;
|
||||
const { any, array, func, node, string, object } = PropTypes;
|
||||
|
||||
Thumbnail.propTypes = {
|
||||
className: string,
|
||||
|
|
@ -78,6 +78,40 @@ Thumbnail.propTypes = {
|
|||
imageId: uuid,
|
||||
};
|
||||
|
||||
const ThumbnailWrapper = props => {
|
||||
const { className, image } = props;
|
||||
if (image.file) {
|
||||
return <Thumbnail className={className} {...image} />;
|
||||
} else {
|
||||
const classes = classNames(css.thumbnail, className);
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={css.threeToTwoWrapper}>
|
||||
<div className={css.aspectWrapper}>
|
||||
<ResponsiveImage
|
||||
rootClassName={css.rootForImage}
|
||||
image={image}
|
||||
alt="TODO: alt text"
|
||||
nameSet={[
|
||||
{ name: 'landscape-crop', size: '400w' },
|
||||
{ name: 'landscape-crop2x', size: '800w' },
|
||||
]}
|
||||
sizes="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ThumbnailWrapper.defaultProps = { className: null };
|
||||
|
||||
ThumbnailWrapper.propTypes = {
|
||||
className: string,
|
||||
image: object.isRequired,
|
||||
};
|
||||
|
||||
// Sorting is disabled temporarily.
|
||||
//
|
||||
// The issue with sorting is that in touch devices it makes
|
||||
|
|
@ -89,9 +123,7 @@ Thumbnail.propTypes = {
|
|||
//
|
||||
// TODO Think what to do with the scrolling issue when sorting is in use
|
||||
//
|
||||
// TODO Enable me:
|
||||
// const SortableImage = SortableElement(Thumbnail);
|
||||
const SortableImage = Thumbnail;
|
||||
const SortableImage = ThumbnailWrapper;
|
||||
|
||||
// Create container where there are sortable images and passed children like "Add image" input etc.
|
||||
const SortableImages = SortableContainer(props => {
|
||||
|
|
@ -101,7 +133,12 @@ const SortableImages = SortableContainer(props => {
|
|||
<div className={classes}>
|
||||
{images.map((image, index) => {
|
||||
return (
|
||||
<SortableImage {...image} index={index} key={image.id} className={thumbnailClassName} />
|
||||
<SortableImage
|
||||
image={image}
|
||||
index={index}
|
||||
key={image.id.uuid || image.id}
|
||||
className={thumbnailClassName}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ import { EditListingDescriptionForm } from '../../containers';
|
|||
import css from './EditListingDescriptionPanel.css';
|
||||
|
||||
const EditListingDescriptionPanel = props => {
|
||||
const { className, rootClassName, listing, onSubmit, submitButtonText } = props;
|
||||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
listing,
|
||||
onSubmit,
|
||||
onChange,
|
||||
submitButtonText,
|
||||
panelUpdated,
|
||||
errors,
|
||||
} = props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const { attributes: { description, title } } = listing || { attributes: {} };
|
||||
|
|
@ -19,12 +28,15 @@ const EditListingDescriptionPanel = props => {
|
|||
initialValues={{ title, description }}
|
||||
saveActionMsg={submitButtonText}
|
||||
onSubmit={onSubmit}
|
||||
onChange={onChange}
|
||||
updated={panelUpdated}
|
||||
updateError={errors.updateListingError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { func, object, string } = PropTypes;
|
||||
const { func, object, string, bool } = PropTypes;
|
||||
|
||||
EditListingDescriptionPanel.defaultProps = {
|
||||
className: null,
|
||||
|
|
@ -37,7 +49,10 @@ EditListingDescriptionPanel.propTypes = {
|
|||
rootClassName: string,
|
||||
listing: object, // TODO Should be propTypes.listing after API support is added.
|
||||
onSubmit: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
submitButtonText: string.isRequired,
|
||||
panelUpdated: bool.isRequired,
|
||||
errors: object.isRequired,
|
||||
};
|
||||
|
||||
export default EditListingDescriptionPanel;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ import { EditListingLocationForm } from '../../containers';
|
|||
import css from './EditListingLocationPanel.css';
|
||||
|
||||
const EditListingLocationPanel = props => {
|
||||
const { className, rootClassName, listing, onSubmit, submitButtonText } = props;
|
||||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
listing,
|
||||
onSubmit,
|
||||
onChange,
|
||||
submitButtonText,
|
||||
panelUpdated,
|
||||
errors,
|
||||
} = props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const { attributes: { address, geolocation } } = listing || { attributes: {} };
|
||||
|
|
@ -44,13 +53,16 @@ const EditListingLocationPanel = props => {
|
|||
className={css.form}
|
||||
initialValues={initialSearchFormValues}
|
||||
onSubmit={onSubmit}
|
||||
onChange={onChange}
|
||||
saveActionMsg={submitButtonText}
|
||||
updated={panelUpdated}
|
||||
updateError={errors.updateListingError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { func, object, string } = PropTypes;
|
||||
const { func, object, string, bool } = PropTypes;
|
||||
|
||||
EditListingLocationPanel.defaultProps = {
|
||||
className: null,
|
||||
|
|
@ -63,7 +75,10 @@ EditListingLocationPanel.propTypes = {
|
|||
rootClassName: string,
|
||||
listing: object, // TODO Should be propTypes.listing after API support is added.
|
||||
onSubmit: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
submitButtonText: string.isRequired,
|
||||
panelUpdated: bool.isRequired,
|
||||
errors: object.isRequired,
|
||||
};
|
||||
|
||||
export default EditListingLocationPanel;
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ class EditListingPhotosPanel extends Component {
|
|||
onUpdateImageOrder,
|
||||
onManageDisableScrolling,
|
||||
submitButtonText,
|
||||
panelUpdated,
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
const rootClass = rootClassName || css.root;
|
||||
|
|
@ -100,8 +102,11 @@ class EditListingPhotosPanel extends Component {
|
|||
images={images}
|
||||
onImageUpload={onImageUpload}
|
||||
onSubmit={this.handlePhotosSubmit}
|
||||
onChange={onChange}
|
||||
onUpdateImageOrder={onUpdateImageOrder}
|
||||
saveActionMsg={submitButtonText}
|
||||
updated={panelUpdated}
|
||||
updateError={errors.updateListingError}
|
||||
/>
|
||||
<Modal
|
||||
id="EditListingPhotosPanel.payoutModal"
|
||||
|
|
@ -148,6 +153,7 @@ EditListingPhotosPanel.propTypes = {
|
|||
currentUser: propTypes.currentUser,
|
||||
errors: shape({
|
||||
createListingsError: object,
|
||||
updateListingError: object,
|
||||
showListingsError: object,
|
||||
uploadImageError: object,
|
||||
}),
|
||||
|
|
@ -157,8 +163,10 @@ EditListingPhotosPanel.propTypes = {
|
|||
onPayoutDetailsSubmit: func.isRequired,
|
||||
onUpdateImageOrder: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
submitButtonText: string.isRequired,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
panelUpdated: bool.isRequired,
|
||||
};
|
||||
|
||||
export default EditListingPhotosPanel;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ import { EditListingPricingForm } from '../../containers';
|
|||
import css from './EditListingPricingPanel.css';
|
||||
|
||||
const EditListingPricingPanel = props => {
|
||||
const { className, rootClassName, listing, onSubmit, submitButtonText } = props;
|
||||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
listing,
|
||||
onSubmit,
|
||||
onChange,
|
||||
submitButtonText,
|
||||
panelUpdated,
|
||||
errors,
|
||||
} = props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const { attributes: { price } } = listing || { attributes: {} };
|
||||
|
|
@ -18,13 +27,16 @@ const EditListingPricingPanel = props => {
|
|||
className={css.form}
|
||||
initialValues={{ price }}
|
||||
onSubmit={onSubmit}
|
||||
onChange={onChange}
|
||||
saveActionMsg={submitButtonText}
|
||||
updated={panelUpdated}
|
||||
updateError={errors.updateListingError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { func, object, string } = PropTypes;
|
||||
const { func, object, string, bool } = PropTypes;
|
||||
|
||||
EditListingPricingPanel.defaultProps = {
|
||||
className: null,
|
||||
|
|
@ -37,7 +49,10 @@ EditListingPricingPanel.propTypes = {
|
|||
rootClassName: string,
|
||||
listing: object, // TODO Should be propTypes.listing after API support is added.
|
||||
onSubmit: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
submitButtonText: string.isRequired,
|
||||
panelUpdated: bool.isRequired,
|
||||
errors: object.isRequired,
|
||||
};
|
||||
|
||||
export default EditListingPricingPanel;
|
||||
|
|
|
|||
|
|
@ -22,11 +22,14 @@ export const NoPhotos = {
|
|||
stripeConnected: true,
|
||||
onImageUpload: noop,
|
||||
onUpdateImageOrder: noop,
|
||||
onUpdateListing: noop,
|
||||
onCreateListing: noop,
|
||||
onCreateListingDraft: noop,
|
||||
onPayoutDetailsSubmit: noop,
|
||||
onUpdateListingDraft: noop,
|
||||
onManageDisableScrolling: noop,
|
||||
onChange: noop,
|
||||
errors: {},
|
||||
},
|
||||
useDefaultWrapperStyles: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -80,14 +80,17 @@ const EditListingWizard = props => {
|
|||
images,
|
||||
listing,
|
||||
onCreateListing,
|
||||
onUpdateListing,
|
||||
onCreateListingDraft,
|
||||
onImageUpload,
|
||||
onPayoutDetailsSubmit,
|
||||
onUpdateImageOrder,
|
||||
onUpdateListingDraft,
|
||||
onChange,
|
||||
rootClassName,
|
||||
currentUser,
|
||||
onManageDisableScrolling,
|
||||
updatedTab,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
|
|
@ -111,6 +114,9 @@ const EditListingWizard = props => {
|
|||
}
|
||||
|
||||
const onUpsertListingDraft = currentListing.id ? onUpdateListingDraft : onCreateListingDraft;
|
||||
const update = (tab, values) => {
|
||||
onUpdateListing(tab, { ...values, id: currentListing.id });
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs rootClassName={classes} navRootClassName={css.nav} tabRootClassName={css.tab}>
|
||||
|
|
@ -119,17 +125,23 @@ const EditListingWizard = props => {
|
|||
tabLabel="Description"
|
||||
tabLinkProps={tabLink(DESCRIPTION)}
|
||||
selected={selectedTab === DESCRIPTION}
|
||||
panelUpdated={updatedTab === DESCRIPTION}
|
||||
disabled={!stepsStatus[DESCRIPTION]}
|
||||
errors={errors}
|
||||
listing={listing}
|
||||
submitButtonText={submitText(intl, isNew, DESCRIPTION)}
|
||||
onChange={onChange}
|
||||
onSubmit={values => {
|
||||
onUpsertListingDraft(values);
|
||||
|
||||
const pathParams = tabParams(LOCATION);
|
||||
// Redirect to location tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
if (isNew) {
|
||||
onUpsertListingDraft(values);
|
||||
const pathParams = tabParams(LOCATION);
|
||||
// Redirect to location tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
} else {
|
||||
update(DESCRIPTION, values);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EditListingLocationPanel
|
||||
|
|
@ -137,24 +149,31 @@ const EditListingWizard = props => {
|
|||
tabLabel="Location"
|
||||
tabLinkProps={tabLink(LOCATION)}
|
||||
selected={selectedTab === LOCATION}
|
||||
panelUpdated={updatedTab === LOCATION}
|
||||
disabled={!stepsStatus[LOCATION]}
|
||||
errors={errors}
|
||||
listing={listing}
|
||||
submitButtonText={submitText(intl, isNew, LOCATION)}
|
||||
onChange={onChange}
|
||||
onSubmit={values => {
|
||||
const { building, location } = values;
|
||||
const { selectedPlace: { address, origin } } = location;
|
||||
|
||||
// TODO When API supports building number, etc. change this to use those fields instead.
|
||||
onUpdateListingDraft({
|
||||
const updateValues = {
|
||||
address: JSON.stringify({ locationAddress: address, building }),
|
||||
geolocation: origin,
|
||||
});
|
||||
};
|
||||
|
||||
const pathParams = tabParams(PRICING);
|
||||
// Redirect to pricing tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
if (isNew) {
|
||||
// TODO When API supports building number, etc. change this to use those fields instead.
|
||||
onUpdateListingDraft(updateValues);
|
||||
const pathParams = tabParams(PRICING);
|
||||
// Redirect to pricing tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
} else {
|
||||
update(LOCATION, updateValues);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EditListingPricingPanel
|
||||
|
|
@ -162,17 +181,23 @@ const EditListingWizard = props => {
|
|||
tabLabel="Pricing"
|
||||
tabLinkProps={tabLink(PRICING)}
|
||||
selected={selectedTab === PRICING}
|
||||
panelUpdated={updatedTab === PRICING}
|
||||
disabled={!stepsStatus[PRICING]}
|
||||
errors={errors}
|
||||
listing={listing}
|
||||
submitButtonText={submitText(intl, isNew, PRICING)}
|
||||
onChange={onChange}
|
||||
onSubmit={values => {
|
||||
onUpdateListingDraft(values);
|
||||
|
||||
const pathParams = tabParams(PHOTOS);
|
||||
// Redirect to photos tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
if (isNew) {
|
||||
onUpdateListingDraft(values);
|
||||
const pathParams = tabParams(PHOTOS);
|
||||
// Redirect to photos tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
} else {
|
||||
update(PRICING, values);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<EditListingPhotosPanel
|
||||
|
|
@ -181,6 +206,7 @@ const EditListingWizard = props => {
|
|||
tabLinkProps={tabLink(PHOTOS)}
|
||||
selected={selectedTab === PHOTOS}
|
||||
disabled={!stepsStatus[PHOTOS]}
|
||||
panelUpdated={updatedTab === PHOTOS}
|
||||
errors={errors}
|
||||
fetchInProgress={fetchInProgress}
|
||||
listing={listing}
|
||||
|
|
@ -188,9 +214,16 @@ const EditListingWizard = props => {
|
|||
onImageUpload={onImageUpload}
|
||||
onPayoutDetailsSubmit={onPayoutDetailsSubmit}
|
||||
submitButtonText={submitText(intl, isNew, PHOTOS)}
|
||||
onChange={onChange}
|
||||
onSubmit={values => {
|
||||
const { country, images: updatedImages } = values;
|
||||
onCreateListing({ ...listing.attributes, country, images: updatedImages });
|
||||
const updateValues = { ...listing.attributes, country, images: updatedImages };
|
||||
if (isNew) {
|
||||
onCreateListing(updateValues);
|
||||
} else {
|
||||
const imageIds = updatedImages.map(img => img.imageId || img.id);
|
||||
update(PHOTOS, { images: imageIds });
|
||||
}
|
||||
}}
|
||||
onUpdateImageOrder={onUpdateImageOrder}
|
||||
currentUser={currentUser}
|
||||
|
|
@ -206,6 +239,7 @@ EditListingWizard.defaultProps = {
|
|||
listing: null,
|
||||
rootClassName: null,
|
||||
currentUser: null,
|
||||
updatedTab: null,
|
||||
};
|
||||
|
||||
const { array, arrayOf, bool, func, object, oneOf, shape, string } = PropTypes;
|
||||
|
|
@ -220,9 +254,10 @@ EditListingWizard.propTypes = {
|
|||
}).isRequired,
|
||||
errors: shape({
|
||||
createListingsError: object,
|
||||
updateListingError: object,
|
||||
showListingsError: object,
|
||||
uploadImageError: object,
|
||||
}),
|
||||
}).isRequired,
|
||||
fetchInProgress: bool.isRequired,
|
||||
flattenedRoutes: arrayOf(propTypes.route).isRequired,
|
||||
history: shape({
|
||||
|
|
@ -241,14 +276,17 @@ EditListingWizard.propTypes = {
|
|||
images: array,
|
||||
}),
|
||||
onCreateListing: func.isRequired,
|
||||
onUpdateListing: func.isRequired,
|
||||
onCreateListingDraft: func.isRequired,
|
||||
onImageUpload: func.isRequired,
|
||||
onPayoutDetailsSubmit: func.isRequired,
|
||||
onUpdateImageOrder: func.isRequired,
|
||||
onUpdateListingDraft: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
rootClassName: string,
|
||||
currentUser: propTypes.currentUser,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
updatedTab: string,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--failColor);
|
||||
}
|
||||
|
||||
.description {
|
||||
flex-shrink: 0;
|
||||
margin-top: 24px;
|
||||
|
|
@ -27,3 +31,8 @@
|
|||
margin-top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.loaderIcon {
|
||||
/* width: 100%; */
|
||||
/* height: 40px; */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const Empty = {
|
|||
console.log('Submit EditListingDescriptionForm with (unformatted) values:', values);
|
||||
},
|
||||
saveActionMsg: 'Save description',
|
||||
updated: false,
|
||||
},
|
||||
group: 'forms',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { intlShape, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { maxLength, required } from '../../util/validators';
|
||||
import { Button, TextInputField } from '../../components';
|
||||
|
|
@ -20,6 +20,8 @@ const EditListingDescriptionFormComponent = props => {
|
|||
invalid,
|
||||
saveActionMsg,
|
||||
submitting,
|
||||
updated,
|
||||
updateError,
|
||||
} = props;
|
||||
|
||||
const titleMessage = intl.formatMessage({ id: 'EditListingDescriptionForm.title' });
|
||||
|
|
@ -45,9 +47,20 @@ const EditListingDescriptionFormComponent = props => {
|
|||
id: 'EditListingDescriptionForm.descriptionRequired',
|
||||
});
|
||||
|
||||
const errorMessage = updateError
|
||||
? <p className={css.error}>
|
||||
<FormattedMessage id="EditListingDescriptionForm.updateFailed" />
|
||||
</p>
|
||||
: null;
|
||||
|
||||
const buttonContent = updated
|
||||
? <FormattedMessage id="EditListingDescriptionForm.updated" />
|
||||
: saveActionMsg;
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
return (
|
||||
<form className={classes} onSubmit={handleSubmit}>
|
||||
{errorMessage}
|
||||
<TextInputField
|
||||
type="text"
|
||||
name="title"
|
||||
|
|
@ -73,15 +86,15 @@ const EditListingDescriptionFormComponent = props => {
|
|||
type="submit"
|
||||
disabled={invalid || submitting || disabled}
|
||||
>
|
||||
{saveActionMsg}
|
||||
{buttonContent}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
EditListingDescriptionFormComponent.defaultProps = { className: null };
|
||||
EditListingDescriptionFormComponent.defaultProps = { className: null, updateError: null };
|
||||
|
||||
const { func, string } = PropTypes;
|
||||
const { func, string, bool, instanceOf } = PropTypes;
|
||||
|
||||
EditListingDescriptionFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
|
|
@ -89,6 +102,8 @@ EditListingDescriptionFormComponent.propTypes = {
|
|||
intl: intlShape.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
saveActionMsg: string.isRequired,
|
||||
updated: bool.isRequired,
|
||||
updateError: instanceOf(Error),
|
||||
};
|
||||
|
||||
const formName = 'EditListingDescriptionForm';
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ describe('EditListingDescriptionForm', () => {
|
|||
dispatch={noop}
|
||||
onSubmit={v => v}
|
||||
saveActionMsg="Save description"
|
||||
updated={false}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--failColor);
|
||||
}
|
||||
|
||||
.locationAutocompleteInput {
|
||||
height: 36px;
|
||||
padding-left: 0;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const Empty = {
|
|||
console.log('Submit EditListingLocationForm with (unformatted) values:', values);
|
||||
},
|
||||
saveActionMsg: 'Save location',
|
||||
updated: false,
|
||||
},
|
||||
group: 'forms',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
|
|||
import { compose } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { reduxForm, formValueSelector, propTypes as formPropTypes } from 'redux-form';
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { intlShape, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { autocompleteSearchRequired, autocompletePlaceSelected } from '../../util/validators';
|
||||
|
|
@ -20,6 +20,8 @@ export const EditListingLocationFormComponent = props => {
|
|||
invalid,
|
||||
saveActionMsg,
|
||||
submitting,
|
||||
updated,
|
||||
updateError,
|
||||
} = props;
|
||||
|
||||
const titleRequiredMessage = intl.formatMessage({ id: 'EditListingLocationForm.address' });
|
||||
|
|
@ -38,10 +40,17 @@ export const EditListingLocationFormComponent = props => {
|
|||
id: 'EditListingLocationForm.buildingPlaceholder',
|
||||
});
|
||||
|
||||
const errorMessage = updateError
|
||||
? <p className={css.error}>
|
||||
<FormattedMessage id="EditListingLocationForm.updateFailed" />
|
||||
</p>
|
||||
: null;
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
|
||||
return (
|
||||
<form className={classes} onSubmit={handleSubmit}>
|
||||
{errorMessage}
|
||||
<LocationAutocompleteInputField
|
||||
inputClassName={css.locationAutocompleteInput}
|
||||
iconClassName={css.locationAutocompleteInputIcon}
|
||||
|
|
@ -72,7 +81,7 @@ export const EditListingLocationFormComponent = props => {
|
|||
type="submit"
|
||||
disabled={invalid || submitting || disabled}
|
||||
>
|
||||
{saveActionMsg}
|
||||
{updated ? <FormattedMessage id="EditListingLocationForm.updated" /> : saveActionMsg}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
|
|
@ -80,9 +89,10 @@ export const EditListingLocationFormComponent = props => {
|
|||
|
||||
EditListingLocationFormComponent.defaultProps = {
|
||||
selectedPlace: null,
|
||||
updateError: null,
|
||||
};
|
||||
|
||||
const { func, string } = PropTypes;
|
||||
const { func, string, bool, instanceOf } = PropTypes;
|
||||
|
||||
EditListingLocationFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
|
|
@ -90,6 +100,8 @@ EditListingLocationFormComponent.propTypes = {
|
|||
onSubmit: func.isRequired,
|
||||
saveActionMsg: string.isRequired,
|
||||
selectedPlace: propTypes.place,
|
||||
updated: bool.isRequired,
|
||||
updateError: instanceOf(Error),
|
||||
};
|
||||
|
||||
const formName = 'EditListingLocationForm';
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ describe('EditListingLocationForm', () => {
|
|||
dispatch={noop}
|
||||
onSubmit={v => v}
|
||||
saveActionMsg="Save location"
|
||||
updated={false}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -11,10 +11,17 @@ const errorAction = actionType => error => ({ type: actionType, payload: error,
|
|||
|
||||
// ================ Action types ================ //
|
||||
|
||||
export const MARK_TAB_UPDATED = 'app/EditListingPage/MARK_TAB_UPDATED';
|
||||
export const CLEAR_UPDATED_TAB = 'app/EditListingPage/CLEAR_UPDATED_TAB';
|
||||
|
||||
export const CREATE_LISTING_REQUEST = 'app/EditListingPage/CREATE_LISTING_REQUEST';
|
||||
export const CREATE_LISTING_SUCCESS = 'app/EditListingPage/CREATE_LISTING_SUCCESS';
|
||||
export const CREATE_LISTING_ERROR = 'app/EditListingPage/CREATE_LISTING_ERROR';
|
||||
|
||||
export const UPDATE_LISTING_REQUEST = 'app/EditListingPage/UPDATE_LISTING_REQUEST';
|
||||
export const UPDATE_LISTING_SUCCESS = 'app/EditListingPage/UPDATE_LISTING_SUCCESS';
|
||||
export const UPDATE_LISTING_ERROR = 'app/EditListingPage/UPDATE_LISTING_ERROR';
|
||||
|
||||
export const SHOW_LISTINGS_REQUEST = 'app/EditListingPage/SHOW_LISTINGS_REQUEST';
|
||||
export const SHOW_LISTINGS_SUCCESS = 'app/EditListingPage/SHOW_LISTINGS_SUCCESS';
|
||||
export const SHOW_LISTINGS_ERROR = 'app/EditListingPage/SHOW_LISTINGS_ERROR';
|
||||
|
|
@ -33,6 +40,7 @@ export const UPDATE_LISTING_DRAFT = 'app/EditListingPage/UPDATE_LISTING_DRAFT';
|
|||
const initialState = {
|
||||
// Error instance placeholders for each endpoint
|
||||
createListingsError: null,
|
||||
updateListingError: null,
|
||||
showListingsError: null,
|
||||
uploadImageError: null,
|
||||
submittedListingId: null,
|
||||
|
|
@ -40,11 +48,17 @@ const initialState = {
|
|||
images: {},
|
||||
imageOrder: [],
|
||||
listingDraft: null,
|
||||
updatedTab: null,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case MARK_TAB_UPDATED:
|
||||
return { ...state, updatedTab: payload };
|
||||
case CLEAR_UPDATED_TAB:
|
||||
return { ...state, updatedTab: null, updateListingError: null };
|
||||
|
||||
case CREATE_LISTING_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
|
|
@ -59,6 +73,15 @@ export default function reducer(state = initialState, action = {}) {
|
|||
console.error(payload);
|
||||
return { ...state, createListingsError: payload, redirectToListing: false };
|
||||
|
||||
case UPDATE_LISTING_REQUEST:
|
||||
return { ...state, updateListingError: null };
|
||||
case UPDATE_LISTING_SUCCESS:
|
||||
return state;
|
||||
case UPDATE_LISTING_ERROR:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(payload);
|
||||
return { ...state, updateListingError: payload };
|
||||
|
||||
case SHOW_LISTINGS_REQUEST:
|
||||
return { ...state, showListingsError: null };
|
||||
case SHOW_LISTINGS_SUCCESS:
|
||||
|
|
@ -118,6 +141,15 @@ export default function reducer(state = initialState, action = {}) {
|
|||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const markTabUpdated = tab => ({
|
||||
type: MARK_TAB_UPDATED,
|
||||
payload: tab,
|
||||
});
|
||||
|
||||
export const clearUpdatedTab = () => ({
|
||||
type: CLEAR_UPDATED_TAB,
|
||||
});
|
||||
|
||||
export const updateImageOrder = imageOrder => ({
|
||||
type: UPDATE_IMAGE_ORDER,
|
||||
payload: { imageOrder },
|
||||
|
|
@ -149,6 +181,11 @@ export const createListing = requestAction(CREATE_LISTING_REQUEST);
|
|||
export const createListingSuccess = successAction(CREATE_LISTING_SUCCESS);
|
||||
export const createListingError = errorAction(CREATE_LISTING_ERROR);
|
||||
|
||||
// SDK method: listings.update
|
||||
export const updateListing = requestAction(UPDATE_LISTING_REQUEST);
|
||||
export const updateListingSuccess = successAction(UPDATE_LISTING_SUCCESS);
|
||||
export const updateListingError = errorAction(UPDATE_LISTING_ERROR);
|
||||
|
||||
// SDK method: listings.show
|
||||
export const showListings = requestAction(SHOW_LISTINGS_REQUEST);
|
||||
export const showListingsSuccess = successAction(SHOW_LISTINGS_SUCCESS);
|
||||
|
|
@ -214,8 +251,29 @@ export function requestImageUpload(actionPayload) {
|
|||
};
|
||||
}
|
||||
|
||||
export function requestUpdateListing(tab, data) {
|
||||
return (dispatch, getState, sdk) => {
|
||||
dispatch(updateListing(data));
|
||||
const { id } = data;
|
||||
let updateResponse;
|
||||
return sdk.listings
|
||||
.update(data)
|
||||
.then(response => {
|
||||
updateResponse = response;
|
||||
const payload = { id, include: ['author', 'images'] };
|
||||
return dispatch(requestShowListing(payload));
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(markTabUpdated(tab));
|
||||
dispatch(updateListingSuccess(updateResponse));
|
||||
})
|
||||
.catch(e => dispatch(updateListingError(e)));
|
||||
};
|
||||
}
|
||||
|
||||
export function loadData(params) {
|
||||
return dispatch => {
|
||||
dispatch(clearUpdatedTab());
|
||||
const { id, type } = params;
|
||||
if (type === 'new') {
|
||||
return Promise.resolve(null);
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ import {
|
|||
createListingDraft,
|
||||
updateListingDraft,
|
||||
requestCreateListing,
|
||||
requestUpdateListing,
|
||||
requestImageUpload,
|
||||
updateImageOrder,
|
||||
loadData,
|
||||
clearUpdatedTab,
|
||||
} from './EditListingPage.duck';
|
||||
|
||||
import css from './EditListingPage.css';
|
||||
|
|
@ -59,6 +61,7 @@ export const EditListingPageComponent = props => {
|
|||
logoutError,
|
||||
notificationCount,
|
||||
onCreateListing,
|
||||
onUpdateListing,
|
||||
onCreateListingDraft,
|
||||
onImageUpload,
|
||||
onLogout,
|
||||
|
|
@ -66,6 +69,7 @@ export const EditListingPageComponent = props => {
|
|||
onPayoutDetailsSubmit,
|
||||
onUpdateImageOrder,
|
||||
onUpdateListingDraft,
|
||||
onChange,
|
||||
page,
|
||||
params,
|
||||
scrollingDisabled,
|
||||
|
|
@ -86,15 +90,21 @@ export const EditListingPageComponent = props => {
|
|||
const listingSlug = currentListing ? createSlug(currentListing.attributes.title) : null;
|
||||
return <NamedRedirect name="ListingPage" params={{ id: listingId.uuid, slug: listingSlug }} />;
|
||||
} else if (showForm) {
|
||||
const { createListingsError = null, showListingsError = null, uploadImageError = null } = page;
|
||||
const errors = { createListingsError, showListingsError, uploadImageError };
|
||||
const {
|
||||
createListingsError = null,
|
||||
updateListingError = null,
|
||||
showListingsError = null,
|
||||
uploadImageError = null,
|
||||
} = page;
|
||||
const errors = { createListingsError, updateListingError, showListingsError, uploadImageError };
|
||||
|
||||
// Show form if user is posting a new listing or editing existing one
|
||||
const disableForm = page.redirectToListing && !showListingsError;
|
||||
|
||||
// Images are passed to EditListingForm so that it can generate thumbnails out of them
|
||||
const currentListingImages = currentListing ? currentListing.images : [];
|
||||
const images = isNew ? page.imageOrder.map(i => page.images[i]) : currentListingImages;
|
||||
const draftImages = page.imageOrder.map(i => page.images[i]);
|
||||
const images = currentListingImages.concat(draftImages);
|
||||
|
||||
const title = isNew
|
||||
? intl.formatMessage({ id: 'EditListingPage.titleCreateListing' })
|
||||
|
|
@ -130,13 +140,16 @@ export const EditListingPageComponent = props => {
|
|||
images={images}
|
||||
listing={isNew ? page.listingDraft : currentListing}
|
||||
onCreateListing={onCreateListing}
|
||||
onUpdateListing={onUpdateListing}
|
||||
onCreateListingDraft={onCreateListingDraft}
|
||||
onUpdateListingDraft={onUpdateListingDraft}
|
||||
onPayoutDetailsSubmit={onPayoutDetailsSubmit}
|
||||
onImageUpload={onImageUpload}
|
||||
onUpdateImageOrder={onUpdateImageOrder}
|
||||
onChange={onChange}
|
||||
currentUser={currentUser}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
updatedTab={page.updatedTab}
|
||||
/>
|
||||
</PageLayout>
|
||||
);
|
||||
|
|
@ -180,6 +193,8 @@ EditListingPageComponent.propTypes = {
|
|||
onPayoutDetailsSubmit: func.isRequired,
|
||||
onUpdateImageOrder: func.isRequired,
|
||||
onUpdateListingDraft: func.isRequired,
|
||||
onUpdateListing: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
page: object.isRequired,
|
||||
params: shape({
|
||||
id: string.isRequired,
|
||||
|
|
@ -231,6 +246,7 @@ const mapStateToProps = state => {
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onCreateListing: values => dispatch(requestCreateListing(formatRequestData(values))),
|
||||
onUpdateListing: (tab, values) => dispatch(requestUpdateListing(tab, values)),
|
||||
onCreateListingDraft: values => dispatch(createListingDraft(values)),
|
||||
onImageUpload: data => dispatch(requestImageUpload(data)),
|
||||
onLogout: historyPush => dispatch(logout(historyPush)),
|
||||
|
|
@ -239,6 +255,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
onPayoutDetailsSubmit: values => dispatch(createStripeAccount(values)),
|
||||
onUpdateImageOrder: imageOrder => dispatch(updateImageOrder(imageOrder)),
|
||||
onUpdateListingDraft: values => dispatch(updateListingDraft(values)),
|
||||
onChange: () => dispatch(clearUpdatedTab()),
|
||||
});
|
||||
|
||||
const EditListingPage = compose(connect(mapStateToProps, mapDispatchToProps), withRouter)(
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@ describe('EditListingPageComponent', () => {
|
|||
onCreateListing={noop}
|
||||
onCreateListingDraft={noop}
|
||||
onUpdateListingDraft={noop}
|
||||
onUpdateListing={noop}
|
||||
onImageUpload={noop}
|
||||
onManageDisableScrolling={noop}
|
||||
onPayoutDetailsSubmit={noop}
|
||||
onUpdateImageOrder={noop}
|
||||
onChange={noop}
|
||||
page={{ imageOrder: [], images: {} }}
|
||||
scrollingDisabled={false}
|
||||
tab="description"
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ exports[`EditListingPageComponent matches snapshot 1`] = `
|
|||
Object {
|
||||
"createListingsError": null,
|
||||
"showListingsError": null,
|
||||
"updateListingError": null,
|
||||
"uploadImageError": null,
|
||||
}
|
||||
}
|
||||
|
|
@ -39,12 +40,14 @@ exports[`EditListingPageComponent matches snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
images={Array []}
|
||||
onChange={[Function]}
|
||||
onCreateListing={[Function]}
|
||||
onCreateListingDraft={[Function]}
|
||||
onImageUpload={[Function]}
|
||||
onManageDisableScrolling={[Function]}
|
||||
onPayoutDetailsSubmit={[Function]}
|
||||
onUpdateImageOrder={[Function]}
|
||||
onUpdateListing={[Function]}
|
||||
onUpdateListingDraft={[Function]}
|
||||
params={
|
||||
Object {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
/* Firefox doesn't support image aspect ratio inside flexbox */
|
||||
.aspectRatioWrapper {
|
||||
padding-bottom: 60%; /* 5:3 Aspect Ratio = 100% / (w / h) */
|
||||
padding-bottom: calc(100% * (2 / 3));
|
||||
}
|
||||
|
||||
.addImage {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const Empty = {
|
|||
console.log('Submit EditListingPhotosForm with (unformatted) values:', values);
|
||||
},
|
||||
saveActionMsg: 'Save photos',
|
||||
updated: false,
|
||||
onUpdateImageOrder: imageOrder => {
|
||||
console.log('onUpdateImageOrder with new imageOrder:', imageOrder);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const RenderAddImage = props => {
|
|||
);
|
||||
};
|
||||
|
||||
const { func, node, object, shape, string } = PropTypes;
|
||||
const { func, node, object, shape, string, bool, instanceOf } = PropTypes;
|
||||
|
||||
RenderAddImage.propTypes = {
|
||||
accept: string.isRequired,
|
||||
|
|
@ -85,6 +85,8 @@ export class EditListingPhotosFormComponent extends Component {
|
|||
invalid,
|
||||
saveActionMsg,
|
||||
submitting,
|
||||
updated,
|
||||
updateError,
|
||||
} = this.props;
|
||||
|
||||
const chooseImageText = (
|
||||
|
|
@ -124,13 +126,19 @@ export class EditListingPhotosFormComponent extends Component {
|
|||
</p>
|
||||
: null;
|
||||
|
||||
const errorMessage = updateError
|
||||
? <p className={css.error}>
|
||||
<FormattedMessage id="EditListingPhotosForm.updateFailed" />
|
||||
</p>
|
||||
: null;
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
|
||||
const disableForm = invalid || submitting || disabled || this.state.imageUploadRequested;
|
||||
|
||||
return (
|
||||
<form className={classes} onSubmit={handleSubmit}>
|
||||
|
||||
{errorMessage}
|
||||
<AddImages
|
||||
className={css.imagesField}
|
||||
images={images}
|
||||
|
|
@ -171,14 +179,14 @@ export class EditListingPhotosFormComponent extends Component {
|
|||
{showListingFailed}
|
||||
|
||||
<Button className={css.submitButton} type="submit" disabled={disableForm}>
|
||||
{saveActionMsg}
|
||||
{updated ? <FormattedMessage id="EditListingPhotosForm.updated" /> : saveActionMsg}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditListingPhotosFormComponent.defaultProps = { errors: {} };
|
||||
EditListingPhotosFormComponent.defaultProps = { errors: {}, updateError: null };
|
||||
|
||||
EditListingPhotosFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
|
|
@ -192,6 +200,8 @@ EditListingPhotosFormComponent.propTypes = {
|
|||
onUpdateImageOrder: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
saveActionMsg: string.isRequired,
|
||||
updated: bool.isRequired,
|
||||
updateError: instanceOf(Error),
|
||||
};
|
||||
|
||||
const formName = 'EditListingPhotosForm';
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ describe('EditListingPhotosForm', () => {
|
|||
saveActionMsg="Save photos"
|
||||
onUpdateImageOrder={v => v}
|
||||
stripeConnected={false}
|
||||
updated={false}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--failColor);
|
||||
}
|
||||
|
||||
.priceInput {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const Empty = {
|
|||
console.log('Submit EditListingPricingForm with (unformatted) values:', values);
|
||||
},
|
||||
saveActionMsg: 'Save price',
|
||||
updated: false,
|
||||
},
|
||||
group: 'forms',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { reduxForm, propTypes as formPropTypes } from 'redux-form';
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
import { intlShape, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import config from '../../config';
|
||||
import { required } from '../../util/validators';
|
||||
|
|
@ -18,6 +18,8 @@ export const EditListingPricingFormComponent = props => {
|
|||
invalid,
|
||||
saveActionMsg,
|
||||
submitting,
|
||||
updated,
|
||||
updateError,
|
||||
} = props;
|
||||
|
||||
const pricePerNightMessage = intl.formatMessage({ id: 'EditListingPricingForm.pricePerNight' });
|
||||
|
|
@ -26,10 +28,17 @@ export const EditListingPricingFormComponent = props => {
|
|||
id: 'EditListingPricingForm.priceInputPlaceholder',
|
||||
});
|
||||
|
||||
const errorMessage = updateError
|
||||
? <p className={css.error}>
|
||||
<FormattedMessage id="EditListingPricingForm.updateFailed" />
|
||||
</p>
|
||||
: null;
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
|
||||
return (
|
||||
<form className={classes} onSubmit={handleSubmit}>
|
||||
{errorMessage}
|
||||
<CurrencyInputField
|
||||
id="EditListingPricingForm.CurrencyInputField"
|
||||
className={css.priceInput}
|
||||
|
|
@ -46,19 +55,23 @@ export const EditListingPricingFormComponent = props => {
|
|||
type="submit"
|
||||
disabled={invalid || submitting || disabled}
|
||||
>
|
||||
{saveActionMsg}
|
||||
{updated ? <FormattedMessage id="EditListingPricingForm.updated" /> : saveActionMsg}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const { func, string } = PropTypes;
|
||||
EditListingPricingFormComponent.defaultProps = { updateError: null };
|
||||
|
||||
const { func, string, bool, instanceOf } = PropTypes;
|
||||
|
||||
EditListingPricingFormComponent.propTypes = {
|
||||
...formPropTypes,
|
||||
intl: intlShape.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
saveActionMsg: string.isRequired,
|
||||
updated: bool.isRequired,
|
||||
updateError: instanceOf(Error),
|
||||
};
|
||||
|
||||
const formName = 'EditListingPricingForm';
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ describe('EditListingPricingForm', () => {
|
|||
dispatch={noop}
|
||||
onSubmit={v => v}
|
||||
saveActionMsg="Save price"
|
||||
updated={false}
|
||||
/>
|
||||
);
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
"EditListingDescriptionForm.title": "Name of your sauna",
|
||||
"EditListingDescriptionForm.titlePlaceholder": "What's the name of your sauna?",
|
||||
"EditListingDescriptionForm.titleRequired": "You need to add a name.",
|
||||
"EditListingDescriptionForm.updateFailed": "Failed to update listing. Please try again.",
|
||||
"EditListingDescriptionForm.updated": "Updated!",
|
||||
"EditListingDescriptionPanel.title": "Add your sauna",
|
||||
"EditListingLocationForm.address": "Address",
|
||||
"EditListingLocationForm.addressNotRecognized": "We didn't recognize this location. Please try another location.",
|
||||
|
|
@ -58,6 +60,8 @@
|
|||
"EditListingLocationForm.addressRequired": "You need to provide a location",
|
||||
"EditListingLocationForm.building": "Apt, suite, building # (optional)",
|
||||
"EditListingLocationForm.buildingPlaceholder": "E.g. A 42",
|
||||
"EditListingLocationForm.updateFailed": "Failed to update listing. Please try again.",
|
||||
"EditListingLocationForm.updated": "Updated!",
|
||||
"EditListingLocationPanel.title": "Where's your sauna?",
|
||||
"EditListingPage.titleCreateListing": "Create a listing",
|
||||
"EditListingPage.titleEditListing": "Edit listing",
|
||||
|
|
@ -69,6 +73,8 @@
|
|||
"EditListingPhotosForm.imageTypes": ".JPG, .GIF or .PNG max. 10 MB",
|
||||
"EditListingPhotosForm.imageUploadFailed.uploadOverLimit": " Image was too big. Maximum size is 10 MB.",
|
||||
"EditListingPhotosForm.showListingFailed": "Fetching listing data failed",
|
||||
"EditListingPhotosForm.updateFailed": "Failed to update listing. Please try again.",
|
||||
"EditListingPhotosForm.updated": "Updated!",
|
||||
"EditListingPhotosPanel.payoutModalInfo": "Since this was the first listing you created, we need to know bit more information about you in order to send you money. We only ask these once.",
|
||||
"EditListingPhotosPanel.payoutModalTitleOneMoreThing": "One more thing:",
|
||||
"EditListingPhotosPanel.payoutModalTitlePayoutPreferences": "Payout preferences",
|
||||
|
|
@ -76,6 +82,8 @@
|
|||
"EditListingPricingForm.priceInputPlaceholder": "Choose your price…",
|
||||
"EditListingPricingForm.pricePerNight": "Price per night in euros",
|
||||
"EditListingPricingForm.priceRequired": "You need to add a valid price.",
|
||||
"EditListingPricingForm.updateFailed": "Failed to update listing. Please try again.",
|
||||
"EditListingPricingForm.updated": "Updated!",
|
||||
"EditListingPricingPanel.title": "How much does it cost?",
|
||||
"EditListingWizard.saveEditDescription": "Save description",
|
||||
"EditListingWizard.saveEditLocation": "Save location",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue