diff --git a/CHANGELOG.md b/CHANGELOG.md index d968eab05d..2e7a30281a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ way to update this template, but currently, we follow a pattern: ## Upcoming version 2018-11-XX +## v2.3.0 2018-11-13 + +* [add] Draft listing is used in EditListingWizard, ManageListingCard and ListingPage. + From now on description panel creates a draft listing and photos panel publishes it. You can also view your current draft listings from 'your listings' page. + [#947](https://github.com/sharetribe/flex-template-web/pull/947) * [fix] Firefox showed select options with the same color as select itself. Now options have their own color set and *placeholder option needs to be disabled*. [#946](https://github.com/sharetribe/flex-template-web/pull/946) diff --git a/package.json b/package.json index cc004f00f4..651be013d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "2.2.0", + "version": "2.3.0", "private": true, "license": "Apache-2.0", "dependencies": { @@ -49,7 +49,7 @@ "redux-thunk": "^2.2.0", "sanitize.css": "^5.0.0", "seedrandom": "^2.4.3", - "sharetribe-flex-sdk": "^1.0.0", + "sharetribe-flex-sdk": "^1.1.0", "sharetribe-scripts": "1.1.5", "smoothscroll-polyfill": "^0.4.0", "source-map-support": "^0.5.4", diff --git a/src/components/EditListingDescriptionPanel/EditListingDescriptionPanel.js b/src/components/EditListingDescriptionPanel/EditListingDescriptionPanel.js index 17761fbc7e..3b1dc8cfe0 100644 --- a/src/components/EditListingDescriptionPanel/EditListingDescriptionPanel.js +++ b/src/components/EditListingDescriptionPanel/EditListingDescriptionPanel.js @@ -1,9 +1,10 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import { bool, func, object, string } from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; import { ensureOwnListing } from '../../util/data'; import { ListingLink } from '../../components'; +import { LISTING_STATE_DRAFT } from '../../util/types'; import { EditListingDescriptionForm } from '../../forms'; import config from '../../config'; @@ -26,7 +27,8 @@ const EditListingDescriptionPanel = props => { const currentListing = ensureOwnListing(listing); const { description, title, publicData } = currentListing.attributes; - const panelTitle = currentListing.id ? ( + const isPublished = currentListing.id && currentListing.attributes.state !== LISTING_STATE_DRAFT; + const panelTitle = isPublished ? ( }} @@ -54,19 +56,18 @@ const EditListingDescriptionPanel = props => { }} onChange={onChange} updated={panelUpdated} - updateError={errors.updateListingError} updateInProgress={updateInProgress} + fetchErrors={errors} categories={config.custom.categories} /> ); }; -const { func, object, string, bool } = PropTypes; - EditListingDescriptionPanel.defaultProps = { className: null, rootClassName: null, + errors: null, listing: null, }; diff --git a/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js b/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js index 0f306083b4..f64e8b9420 100644 --- a/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js +++ b/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; +import { LISTING_STATE_DRAFT } from '../../util/types'; import { ensureListing } from '../../util/data'; import { EditListingFeaturesForm } from '../../forms'; import { ListingLink } from '../../components'; @@ -28,7 +29,8 @@ const EditListingFeaturesPanel = props => { const currentListing = ensureListing(listing); const { publicData } = currentListing.attributes; - const panelTitle = currentListing.id ? ( + const isPublished = currentListing.id && currentListing.attributes.state !== LISTING_STATE_DRAFT; + const panelTitle = isPublished ? ( }} @@ -58,8 +60,8 @@ const EditListingFeaturesPanel = props => { onChange={onChange} saveActionMsg={submitButtonText} updated={panelUpdated} - updateError={errors.updateListingError} updateInProgress={updateInProgress} + fetchErrors={errors} /> ); diff --git a/src/components/EditListingLocationPanel/EditListingLocationPanel.js b/src/components/EditListingLocationPanel/EditListingLocationPanel.js index 0075ab0e2c..67a1d48fa7 100644 --- a/src/components/EditListingLocationPanel/EditListingLocationPanel.js +++ b/src/components/EditListingLocationPanel/EditListingLocationPanel.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; +import { LISTING_STATE_DRAFT } from '../../util/types'; import { ensureOwnListing } from '../../util/data'; import { ListingLink } from '../../components'; import { EditListingLocationForm } from '../../forms'; @@ -58,7 +59,9 @@ class EditListingLocationPanel extends Component { const classes = classNames(rootClassName || css.root, className); const currentListing = ensureOwnListing(listing); - const panelTitle = currentListing.id ? ( + const isPublished = + currentListing.id && currentListing.attributes.state !== LISTING_STATE_DRAFT; + const panelTitle = isPublished ? ( }} @@ -95,8 +98,8 @@ class EditListingLocationPanel extends Component { onChange={onChange} saveActionMsg={submitButtonText} updated={panelUpdated} - updateError={errors.updateListingError} updateInProgress={updateInProgress} + fetchErrors={errors} /> ); diff --git a/src/components/EditListingPhotosPanel/EditListingPhotosPanel.js b/src/components/EditListingPhotosPanel/EditListingPhotosPanel.js index ae77eaf699..282f09468c 100644 --- a/src/components/EditListingPhotosPanel/EditListingPhotosPanel.js +++ b/src/components/EditListingPhotosPanel/EditListingPhotosPanel.js @@ -1,7 +1,8 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import { array, bool, func, object, string } from 'prop-types'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { LISTING_STATE_DRAFT } from '../../util/types'; import { EditListingPhotosForm } from '../../forms'; import { ensureOwnListing } from '../../util/data'; import { ListingLink } from '../../components'; @@ -15,7 +16,7 @@ class EditListingPhotosPanel extends Component { rootClassName, errors, fetchInProgress, - newListingCreated, + newListingPublished, images, listing, onImageUpload, @@ -32,7 +33,9 @@ class EditListingPhotosPanel extends Component { const classes = classNames(rootClass, className); const currentListing = ensureOwnListing(listing); - const panelTitle = currentListing.id ? ( + const isPublished = + currentListing.id && currentListing.attributes.state !== LISTING_STATE_DRAFT; + const panelTitle = isPublished ? ( }} @@ -47,8 +50,8 @@ class EditListingPhotosPanel extends Component { @@ -69,8 +71,6 @@ class EditListingPhotosPanel extends Component { } } -const { array, bool, func, object, shape, string } = PropTypes; - EditListingPhotosPanel.defaultProps = { className: null, rootClassName: null, @@ -82,15 +82,9 @@ EditListingPhotosPanel.defaultProps = { EditListingPhotosPanel.propTypes = { className: string, rootClassName: string, - errors: shape({ - createListingsError: object, - updateListingError: object, - showListingsError: object, - uploadImageError: object, - createStripeAccountError: object, - }), + errors: object, fetchInProgress: bool.isRequired, - newListingCreated: bool.isRequired, + newListingPublished: bool.isRequired, images: array, // We cannot use propTypes.listing since the listing might be a draft. diff --git a/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js b/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js index d3b549b144..44ac7b7c73 100644 --- a/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js +++ b/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; +import { LISTING_STATE_DRAFT } from '../../util/types'; import { ensureOwnListing } from '../../util/data'; import { ListingLink } from '../../components'; import { EditListingPoliciesForm } from '../../forms'; @@ -25,7 +26,8 @@ const EditListingPoliciesPanel = props => { const currentListing = ensureOwnListing(listing); const { publicData } = currentListing.attributes; - const panelTitle = currentListing.id ? ( + const isPublished = currentListing.id && currentListing.attributes.state !== LISTING_STATE_DRAFT; + const panelTitle = isPublished ? ( }} @@ -53,8 +55,8 @@ const EditListingPoliciesPanel = props => { onChange={onChange} saveActionMsg={submitButtonText} updated={panelUpdated} - updateError={errors.updateListingError} updateInProgress={updateInProgress} + fetchErrors={errors} /> ); diff --git a/src/components/EditListingPricingPanel/EditListingPricingPanel.js b/src/components/EditListingPricingPanel/EditListingPricingPanel.js index 78a667ed71..de457593e8 100644 --- a/src/components/EditListingPricingPanel/EditListingPricingPanel.js +++ b/src/components/EditListingPricingPanel/EditListingPricingPanel.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { FormattedMessage } from 'react-intl'; +import { LISTING_STATE_DRAFT } from '../../util/types'; import { ListingLink } from '../../components'; import { EditListingPricingForm } from '../../forms'; import { ensureOwnListing } from '../../util/data'; @@ -29,7 +30,8 @@ const EditListingPricingPanel = props => { const currentListing = ensureOwnListing(listing); const { price } = currentListing.attributes; - const panelTitle = currentListing.id ? ( + const isPublished = currentListing.id && currentListing.attributes.state !== LISTING_STATE_DRAFT; + const panelTitle = isPublished ? ( }} @@ -47,8 +49,8 @@ const EditListingPricingPanel = props => { onChange={onChange} saveActionMsg={submitButtonText} updated={panelUpdated} - updateError={errors.updateListingError} updateInProgress={updateInProgress} + fetchErrors={errors} /> ) : (
diff --git a/src/components/EditListingWizard/EditListingWizard.js b/src/components/EditListingWizard/EditListingWizard.js index d9f3771e5b..eb041819e1 100644 --- a/src/components/EditListingWizard/EditListingWizard.js +++ b/src/components/EditListingWizard/EditListingWizard.js @@ -4,6 +4,11 @@ import { compose } from 'redux'; import { FormattedMessage, injectIntl, intlShape } from 'react-intl'; import classNames from 'classnames'; import { withViewport } from '../../util/contextHelpers'; +import { + LISTING_PAGE_PARAM_TYPE_DRAFT, + LISTING_PAGE_PARAM_TYPE_NEW, + LISTING_PAGE_PARAM_TYPES, +} from '../../util/urlHelpers'; import { ensureListing } from '../../util/data'; import { PayoutDetailsForm } from '../../forms'; import { Modal, NamedRedirect, Tabs } from '../../components'; @@ -111,11 +116,11 @@ class EditListingWizard extends Component { this.hasScrolledToTab = false; this.state = { - submittedValues: null, + draftId: null, showPayoutDetails: false, }; this.handleCreateFlowTabScrolling = this.handleCreateFlowTabScrolling.bind(this); - this.handleCreateListing = this.handleCreateListing.bind(this); + this.handlePublishListing = this.handlePublishListing.bind(this); this.handlePayoutModalClose = this.handlePayoutModalClose.bind(this); this.handlePayoutSubmit = this.handlePayoutSubmit.bind(this); } @@ -124,15 +129,15 @@ class EditListingWizard extends Component { this.hasScrolledToTab = shouldScroll; } - handleCreateListing(values) { - const { onCreateListing, currentUser } = this.props; + handlePublishListing(id) { + const { onPublishListingDraft, currentUser } = this.props; const stripeConnected = currentUser && currentUser.attributes && currentUser.attributes.stripeConnected; if (stripeConnected) { - onCreateListing(values); + onPublishListingDraft(id); } else { this.setState({ - submittedValues: values, + draftId: id, showPayoutDetails: true, }); } @@ -149,7 +154,7 @@ class EditListingWizard extends Component { .then(() => { this.setState({ showPayoutDetails: false }); this.props.onManageDisableScrolling('EditListingWizard.payoutModal', false); - this.props.onCreateListing(this.state.submittedValues); + this.props.onPublishListingDraft(this.state.draftId); }) .catch(() => { // do nothing @@ -173,15 +178,22 @@ class EditListingWizard extends Component { } = this.props; const selectedTab = params.tab; - const isNew = params.type === 'new'; + const isNewListingFlow = [LISTING_PAGE_PARAM_TYPE_NEW, LISTING_PAGE_PARAM_TYPE_DRAFT].includes( + params.type + ); const rootClasses = rootClassName || css.root; const classes = classNames(rootClasses, className); const currentListing = ensureListing(listing); - const tabsStatus = tabsActive(isNew, currentListing); + const tabsStatus = tabsActive(isNewListingFlow, currentListing); // If selectedTab is not active, redirect to the beginning of wizard if (!tabsStatus[selectedTab]) { - return ; + const currentTabIndex = TABS.indexOf(selectedTab); + const nearestActiveTab = TABS.slice(0, currentTabIndex) + .reverse() + .find(t => tabsStatus[t]); + + return ; } const { width } = viewport; @@ -220,7 +232,7 @@ class EditListingWizard extends Component { tabLabel={tabLabel(intl, tab)} tabLinkProps={tabLink(tab)} selected={selectedTab === tab} - disabled={isNew && !tabsStatus[tab]} + disabled={isNewListingFlow && !tabsStatus[tab]} tab={tab} intl={intl} params={params} @@ -228,7 +240,7 @@ class EditListingWizard extends Component { marketplaceTabs={TABS} errors={errors} handleCreateFlowTabScrolling={this.handleCreateFlowTabScrolling} - handleCreateListing={this.handleCreateListing} + handlePublishListing={this.handlePublishListing} fetchInProgress={fetchInProgress} /> ); @@ -276,7 +288,7 @@ EditListingWizard.propTypes = { params: shape({ id: string.isRequired, slug: string.isRequired, - type: oneOf(['new', 'edit']).isRequired, + type: oneOf(LISTING_PAGE_PARAM_TYPES).isRequired, tab: oneOf(TABS).isRequired, }).isRequired, @@ -293,14 +305,14 @@ EditListingWizard.propTypes = { }), errors: shape({ - createListingsError: object, + createListingDraftError: object, updateListingError: object, + publishListingError: object, showListingsError: object, uploadImageError: object, createStripeAccountError: object, - }), + }).isRequired, fetchInProgress: bool.isRequired, - onCreateListing: func.isRequired, onPayoutDetailsFormChange: func.isRequired, onPayoutDetailsSubmit: func.isRequired, onManageDisableScrolling: func.isRequired, diff --git a/src/components/EditListingWizard/EditListingWizardTab.js b/src/components/EditListingWizard/EditListingWizardTab.js index 5468d58388..a73fe7d22f 100644 --- a/src/components/EditListingWizard/EditListingWizardTab.js +++ b/src/components/EditListingWizard/EditListingWizardTab.js @@ -2,6 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { intlShape } from 'react-intl'; import routeConfiguration from '../../routeConfiguration'; +import { + LISTING_PAGE_PARAM_TYPE_DRAFT, + LISTING_PAGE_PARAM_TYPE_NEW, + LISTING_PAGE_PARAM_TYPES, +} from '../../util/urlHelpers'; import { ensureListing } from '../../util/data'; import { createResourceLocatorString } from '../../util/routes'; import { @@ -34,6 +39,28 @@ const pathParamsToNextTab = (params, tab, marketplaceTabs) => { return { ...params, tab: nextTab }; }; +// When user has update draft listing, he should be redirected to next EditListingWizardTab +const redirectAfterDraftUpdate = (listingId, params, tab, marketplaceTabs, history) => { + const currentPathParams = { + ...params, + type: LISTING_PAGE_PARAM_TYPE_DRAFT, + id: listingId, + }; + const routes = routeConfiguration(); + + // Replace current "new" path to "draft" path. + // Browser's back button should lead to editing current draft instead of creating a new one. + if (params.type === LISTING_PAGE_PARAM_TYPE_NEW) { + const draftURI = createResourceLocatorString('EditListingPage', routes, currentPathParams, {}); + history.replace(draftURI); + } + + // Redirect to next tab + const nextPathParams = pathParamsToNextTab(currentPathParams, tab, marketplaceTabs); + const to = createResourceLocatorString('EditListingPage', routes, nextPathParams, {}); + history.push(to); +}; + const EditListingWizardTab = props => { const { tab, @@ -41,55 +68,66 @@ const EditListingWizardTab = props => { params, errors, fetchInProgress, - newListingCreated, + newListingPublished, history, images, listing, handleCreateFlowTabScrolling, - handleCreateListing, + handlePublishListing, onUpdateListing, onCreateListingDraft, onImageUpload, onUpdateImageOrder, onRemoveImage, - onUpdateListingDraft, onChange, updatedTab, updateInProgress, intl, } = props; - const isNew = params.type === 'new'; + const { type } = params; + const isNewURI = type === LISTING_PAGE_PARAM_TYPE_NEW; + const isDraftURI = type === LISTING_PAGE_PARAM_TYPE_DRAFT; + const isNewListingFlow = isNewURI || isDraftURI; + const currentListing = ensureListing(listing); const imageIds = images => { return images ? images.map(img => img.imageId || img.id) : null; }; const onCompleteEditListingWizardTab = (tab, updateValues) => { - if (isNew) { - const onUpsertListingDraft = - tab !== marketplaceTabs[0] ? onUpdateListingDraft : onCreateListingDraft; - onUpsertListingDraft(updateValues); + // Normalize images for API call + const { images: updatedImages, ...otherValues } = updateValues; + const imageProperty = + typeof updatedImages !== 'undefined' ? { images: imageIds(updatedImages) } : {}; + const updateValuesWithImages = { ...otherValues, ...imageProperty }; + + if (isNewListingFlow) { + const onUpsertListingDraft = isNewURI + ? (tab, updateValues) => onCreateListingDraft(updateValues) + : onUpdateListing; + + const upsertValues = isNewURI + ? updateValuesWithImages + : { ...updateValuesWithImages, id: currentListing.id }; + + onUpsertListingDraft(tab, upsertValues) + .then(r => { + if (tab !== marketplaceTabs[marketplaceTabs.length - 1]) { + // Create listing flow: smooth scrolling polyfill to scroll to correct tab + handleCreateFlowTabScrolling(false); - if (tab !== marketplaceTabs[marketplaceTabs.length - 1]) { - // Create listing flow: smooth scrolling polyfill to scroll to correct tab - handleCreateFlowTabScrolling(false); - // Redirect to next tab - const pathParams = pathParamsToNextTab(params, tab, marketplaceTabs); - history.push( - createResourceLocatorString('EditListingPage', routeConfiguration(), pathParams, {}) - ); - } else { - // Normalize images for API call - const imageIdArray = imageIds(updateValues.images); - handleCreateListing({ ...listing.attributes, images: imageIdArray }); - } + // After successful saving of draft data, user should be redirected to next tab + redirectAfterDraftUpdate(r.data.data.id.uuid, params, tab, marketplaceTabs, history); + } else { + handlePublishListing(currentListing.id); + } + }) + .catch(e => { + // No need for extra actions + }); } else { - const { images: updatedImages, ...rest } = updateValues; - // Normalize images for API call - const imageProperty = - typeof updatedImages !== 'undefined' ? { images: imageIds(updatedImages) } : {}; - onUpdateListing(tab, { ...rest, id: currentListing.id, ...imageProperty }); + onUpdateListing(tab, { ...updateValuesWithImages, id: currentListing.id }); } }; @@ -106,7 +144,7 @@ const EditListingWizardTab = props => { switch (tab) { case DESCRIPTION: { - const submitButtonTranslationKey = isNew + const submitButtonTranslationKey = isNewListingFlow ? 'EditListingWizard.saveNewDescription' : 'EditListingWizard.saveEditDescription'; return ( @@ -120,7 +158,7 @@ const EditListingWizardTab = props => { ); } case FEATURES: { - const submitButtonTranslationKey = isNew + const submitButtonTranslationKey = isNewListingFlow ? 'EditListingWizard.saveNewFeatures' : 'EditListingWizard.saveEditFeatures'; return ( @@ -134,7 +172,7 @@ const EditListingWizardTab = props => { ); } case POLICY: { - const submitButtonTranslationKey = isNew + const submitButtonTranslationKey = isNewListingFlow ? 'EditListingWizard.saveNewPolicies' : 'EditListingWizard.saveEditPolicies'; return ( @@ -148,7 +186,7 @@ const EditListingWizardTab = props => { ); } case LOCATION: { - const submitButtonTranslationKey = isNew + const submitButtonTranslationKey = isNewListingFlow ? 'EditListingWizard.saveNewLocation' : 'EditListingWizard.saveEditLocation'; return ( @@ -162,7 +200,7 @@ const EditListingWizardTab = props => { ); } case PRICING: { - const submitButtonTranslationKey = isNew + const submitButtonTranslationKey = isNewListingFlow ? 'EditListingWizard.saveNewPricing' : 'EditListingWizard.saveEditPricing'; return ( @@ -176,16 +214,16 @@ const EditListingWizardTab = props => { ); } case PHOTOS: { - const submitButtonTranslationKey = isNew + const submitButtonTranslationKey = isNewListingFlow ? 'EditListingWizard.saveNewPhotos' : 'EditListingWizard.saveEditPhotos'; - // newListingCreated and fetchInProgress are flags for the last wizard tab + // newListingPublished and fetchInProgress are flags for the last wizard tab return ( { }; EditListingWizardTab.defaultProps = { - errors: null, listing: null, updatedTab: null, }; @@ -214,19 +251,21 @@ EditListingWizardTab.propTypes = { params: shape({ id: string.isRequired, slug: string.isRequired, - type: oneOf(['new', 'edit']).isRequired, + type: oneOf(LISTING_PAGE_PARAM_TYPES).isRequired, tab: oneOf(SUPPORTED_TABS).isRequired, }).isRequired, errors: shape({ - createListingsError: object, + createListingDraftError: object, + publishListingError: object, updateListingError: object, showListingsError: object, uploadImageError: object, }).isRequired, fetchInProgress: bool.isRequired, - newListingCreated: bool.isRequired, + newListingPublished: bool.isRequired, history: shape({ push: func.isRequired, + replace: func.isRequired, }).isRequired, images: array.isRequired, @@ -243,13 +282,12 @@ EditListingWizardTab.propTypes = { }), handleCreateFlowTabScrolling: func.isRequired, - handleCreateListing: func.isRequired, + handlePublishListing: func.isRequired, onUpdateListing: func.isRequired, onCreateListingDraft: func.isRequired, onImageUpload: func.isRequired, onUpdateImageOrder: func.isRequired, onRemoveImage: func.isRequired, - onUpdateListingDraft: func.isRequired, onChange: func.isRequired, updatedTab: string, updateInProgress: bool.isRequired, diff --git a/src/components/ListingLink/ListingLink.js b/src/components/ListingLink/ListingLink.js index b4cb66fced..e456778b4e 100644 --- a/src/components/ListingLink/ListingLink.js +++ b/src/components/ListingLink/ListingLink.js @@ -9,8 +9,12 @@ import React from 'react'; import { string, oneOfType, node } from 'prop-types'; import { richText } from '../../util/richText'; -import { LISTING_STATE_PENDING_APPROVAL, propTypes } from '../../util/types'; -import { LISTING_PAGE_PENDING_APPROVAL_VARIANT, createSlug } from '../../util/urlHelpers'; +import { LISTING_STATE_DRAFT, LISTING_STATE_PENDING_APPROVAL, propTypes } from '../../util/types'; +import { + LISTING_PAGE_DRAFT_VARIANT, + LISTING_PAGE_PENDING_APPROVAL_VARIANT, + createSlug, +} from '../../util/urlHelpers'; import { NamedLink } from '../../components'; import css from './ListingLink.css'; @@ -36,13 +40,19 @@ const ListingLink = props => { ); const isPendingApproval = state === LISTING_STATE_PENDING_APPROVAL; - const linkProps = isPendingApproval + const isDraft = state === LISTING_STATE_DRAFT; + const variant = isPendingApproval + ? LISTING_PAGE_PENDING_APPROVAL_VARIANT + : isDraft + ? LISTING_PAGE_DRAFT_VARIANT + : null; + const linkProps = !!variant ? { name: 'ListingPageVariant', params: { id, slug, - variant: LISTING_PAGE_PENDING_APPROVAL_VARIANT, + variant, }, } : { diff --git a/src/components/ManageListingCard/ManageListingCard.css b/src/components/ManageListingCard/ManageListingCard.css index f5893e4de9..962ceeabe8 100644 --- a/src/components/ManageListingCard/ManageListingCard.css +++ b/src/components/ManageListingCard/ManageListingCard.css @@ -24,7 +24,6 @@ display: flex; flex-direction: column; position: relative; - cursor: pointer; /* Remove link's hover effect */ &:hover { @@ -77,6 +76,7 @@ width: 100%; position: relative; transition: var(--transitionStyleButton); + cursor: pointer; &:hover { transform: scale(1.02); @@ -203,22 +203,33 @@ .info { /* Layout */ display: flex; - flex-direction: row; - padding: 16px 0 2px 0; + flex-direction: column; + padding: 8px 0 2px 0; } .price { /* Layout */ display: flex; - flex-direction: column; - justify-content: flex-start; + flex-direction: row; + align-items: baseline; flex-shrink: 0; - margin-right: 18px; + + padding-top: 4px; + padding-bottom: 2px; + + @media (--viewportMedium) { + padding-top: 5px; + padding-bottom: 3px; + } } .priceValue { /* Font */ - @apply --marketplaceH3FontStyles; + @apply --marketplaceH4FontStyles; + color: var(--marketplaceColor); + font-weight: var(--fontWeightSemiBold); + + margin-right: 4px; /* Remove default margins from font */ margin-top: 0; @@ -238,29 +249,46 @@ /* Remove default margins from font */ margin-top: 0; margin-bottom: 0; +} - @media (--viewportMedium) { +@media (--viewportMedium) { + .perUnit { margin-top: 0; margin-bottom: 0; } } +.noPrice { + composes: perUnit; + padding: 5px 0 3px 0; +} + .mainInfo { display: flex; flex-direction: column; flex-grow: 1; } +.titleWrapper { +} + .title { /* Font */ @apply --marketplaceH3FontStyles; color: var(--matterColor); + text-align: left; + + padding-top: 3px; + padding-bottom: 3px; /* Remove default margins from font */ margin-top: 0; margin-bottom: 0; @media (--viewportMedium) { + padding-top: 4px; + padding-bottom: 4px; + margin-top: 0; margin-bottom: 0; } @@ -270,6 +298,20 @@ color: var(--attentionColor); } +.titleDraft { + margin-right: 8px; +} + +/* Solid gray background for draft listings without image */ +.draftNoImage { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: var(--matterColor); +} + .edit { @apply --marketplaceButtonStylesSecondary; @@ -291,95 +333,58 @@ background-position: 15px center; } -.loadingOverlayWrapper, -.errorOverlayWrapper, -.closedOverlayWrapper, -.pendingApprovalOverlayWrapper { - /* Positioning */ - @apply --ManageListingCard_coverEverything; -} +.openListingButton, +.finishListingDraftLink { + @apply --marketplaceButtonStylesPrimary; + @apply --marketplaceH5FontStyles; -.loadingOverlay, -.errorOverlay, -.closedOverlay, -.pendingApprovalOverlay { - /* Positioning */ - @apply --ManageListingCard_coverEverything; + /* Reset min-height from button styles */ + min-height: 0; - /* Overlay background style */ - background-color: var(--matterColorLight); - opacity: 0.9; + width: 114px; + padding: 8px; + border-radius: 4px; } -.pendingApprovalOverlay { - cursor: default; +.menu { + display: none; } -.loadingOverlayContent, -.errorOverlayContent, -.closedOverlayContent, -.pendingApprovalOverlayContent { - @apply --marketplaceH4FontStyles; - color: var(--matterColor); - - /* Positioning */ - @apply --ManageListingCard_coverEverything; +.cardIsOpen { + display: block; +} - /* Layout */ - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - cursor: default; +.manageLinks { + @apply --marketplaceH5FontStyles; + line-height: 18px; + padding-top: 2px; + padding-bottom: 4px; /* Remove default margins from font */ margin-top: 0; margin-bottom: 0; @media (--viewportMedium) { + line-height: 24px; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; margin-bottom: 0; } } -.errorOverlayContent { - color: var(--failColor); - justify-content: flex-start; - margin-top: 90px; -} - -.pendingApprovalOverlayContent { - justify-content: flex-start; - margin-top: 90px; -} - -.closedMessage { - max-width: 220px; - text-align: center; -} - -.pendingMessage { - max-width: 300px; - text-align: center; -} - -.openListingButton { - @apply --marketplaceButtonStylesPrimary; - @apply --marketplaceH5FontStyles; - - /* Reset min-height from button styles */ - min-height: 0; - - width: 114px; - height: 41px; - padding: 6px 0 8px; - border-radius: 4px; -} +.manageLink { + text-decoration: none; + color: var(--matterColor); + white-space: pre; -.menu { - display: none; + &:hover { + text-decoration: underline; + color: var(--matterColorDark); + } } -.cardIsOpen { - display: block; +.manageLinksSeparator { + margin: 0 3px 0 3px; } diff --git a/src/components/ManageListingCard/ManageListingCard.example.js b/src/components/ManageListingCard/ManageListingCard.example.js index e517508dbf..467107e9d4 100644 --- a/src/components/ManageListingCard/ManageListingCard.example.js +++ b/src/components/ManageListingCard/ManageListingCard.example.js @@ -1,7 +1,11 @@ /* eslint-disable no-console */ import React from 'react'; import ManageListingCard from './ManageListingCard'; -import { LISTING_STATE_CLOSED, LISTING_STATE_PENDING_APPROVAL } from '../../util/types'; +import { + LISTING_STATE_CLOSED, + LISTING_STATE_PENDING_APPROVAL, + LISTING_STATE_DRAFT, +} from '../../util/types'; import { createOwnListing, fakeIntl } from '../../util/test-data'; const noop = () => null; @@ -60,3 +64,20 @@ export const PendingApproval = { history: { push: noop }, }, }; + +export const Draft = { + component: ManageListingCardWrapper, + props: { + hasClosingError: false, + hasOpeningError: false, + intl: fakeIntl, + listing: createOwnListing('listing-draft', { + state: LISTING_STATE_DRAFT, + }), + isMenuOpen: false, + onCloseListing: noop, + onOpenListing: noop, + onToggleMenu: noop, + history: { push: noop }, + }, +}; diff --git a/src/components/ManageListingCard/ManageListingCard.js b/src/components/ManageListingCard/ManageListingCard.js index c087ceb9fb..ca43cc10d8 100644 --- a/src/components/ManageListingCard/ManageListingCard.js +++ b/src/components/ManageListingCard/ManageListingCard.js @@ -5,23 +5,36 @@ import { withRouter } from 'react-router-dom'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; import classNames from 'classnames'; import routeConfiguration from '../../routeConfiguration'; -import { LISTING_STATE_PENDING_APPROVAL, LISTING_STATE_CLOSED, propTypes } from '../../util/types'; +import { + LISTING_STATE_PENDING_APPROVAL, + LISTING_STATE_CLOSED, + LISTING_STATE_DRAFT, + propTypes, +} from '../../util/types'; import { formatMoney } from '../../util/currency'; import { ensureOwnListing } from '../../util/data'; -import { LISTING_PAGE_PENDING_APPROVAL_VARIANT, createSlug } from '../../util/urlHelpers'; +import { + LISTING_PAGE_PENDING_APPROVAL_VARIANT, + LISTING_PAGE_DRAFT_VARIANT, + LISTING_PAGE_PARAM_TYPE_DRAFT, + LISTING_PAGE_PARAM_TYPE_EDIT, + createSlug, +} from '../../util/urlHelpers'; import { createResourceLocatorString } from '../../util/routes'; +import config from '../../config'; import { InlineTextButton, Menu, MenuLabel, MenuContent, MenuItem, + NamedLink, IconSpinner, ResponsiveImage, } from '../../components'; -import config from '../../config'; import MenuIcon from './MenuIcon'; +import Overlay from './Overlay'; import css from './ManageListingCard.css'; // Menu content needs the same padding @@ -47,31 +60,31 @@ const priceData = (price, intl) => { return {}; }; -const createEditListingURL = (routes, listing) => { - const id = listing.id.uuid; - const slug = createSlug(listing.attributes.title); - const pathParams = { id, slug, type: 'edit', tab: 'description' }; - - return createResourceLocatorString('EditListingPage', routes, pathParams, {}); -}; - const createListingURL = (routes, listing) => { const id = listing.id.uuid; const slug = createSlug(listing.attributes.title); const isPendingApproval = listing.attributes.state === LISTING_STATE_PENDING_APPROVAL; - const linkProps = isPendingApproval - ? { - name: 'ListingPageVariant', - params: { - id, - slug, - variant: LISTING_PAGE_PENDING_APPROVAL_VARIANT, - }, - } - : { - name: 'ListingPage', - params: { id, slug }, - }; + const isDraft = listing.attributes.state === LISTING_STATE_DRAFT; + const variant = isDraft + ? LISTING_PAGE_DRAFT_VARIANT + : isPendingApproval + ? LISTING_PAGE_PENDING_APPROVAL_VARIANT + : null; + + const linkProps = + isPendingApproval || isDraft + ? { + name: 'ListingPageVariant', + params: { + id, + slug, + variant, + }, + } + : { + name: 'ListingPage', + params: { id, slug }, + }; return createResourceLocatorString(linkProps.name, routes, linkProps.params, {}); }; @@ -113,8 +126,10 @@ export const ManageListingCardComponent = props => { const currentListing = ensureOwnListing(listing); const id = currentListing.id.uuid; const { title = '', price, state } = currentListing.attributes; + const slug = createSlug(title); const isPendingApproval = state === LISTING_STATE_PENDING_APPROVAL; const isClosed = state === LISTING_STATE_CLOSED; + const isDraft = state === LISTING_STATE_DRAFT; const firstImage = currentListing.images && currentListing.images.length > 0 ? currentListing.images[0] : null; @@ -124,115 +139,36 @@ export const ManageListingCardComponent = props => { const { formattedPrice, priceTitle } = priceData(price, intl); - /* eslint-disable jsx-a11y/no-static-element-interactions */ - const closedOverlay = !isClosed ? null : ( -
{ - event.preventDefault(); - event.stopPropagation(); - }} - > -
-
-
- -
- -
-
- ); - - const errorOverlay = - hasOpeningError || hasClosingError ? ( -
{ - event.preventDefault(); - event.stopPropagation(); - }} - > -
-
-
- -
-
-
- ) : null; - - const pendingApprovalOverlay = isPendingApproval ? ( -
{ - event.preventDefault(); - event.stopPropagation(); - }} - > -
-
-
- -
-
-
- ) : null; - - const thisInProgress = actionsInProgressListingId && actionsInProgressListingId.uuid === id; - const loadingOrErrorOverlay = thisInProgress ? ( -
{ - event.preventDefault(); - event.stopPropagation(); - }} - > -
-
- -
-
- ) : ( - errorOverlay - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ + const hasError = hasOpeningError || hasClosingError; + const thisListingInProgress = + actionsInProgressListingId && actionsInProgressListingId.uuid === id; const titleClasses = classNames(css.title, { [css.titlePending]: isPendingApproval, + [css.titleDraft]: isDraft, }); + const editListingLinkType = isDraft + ? LISTING_PAGE_PARAM_TYPE_DRAFT + : LISTING_PAGE_PARAM_TYPE_EDIT; + return ( -
{ - event.preventDefault(); - event.stopPropagation(); +
+
{ + event.preventDefault(); + event.stopPropagation(); - // ManageListingCard contains links, buttons and elements that are working with routing. - // This card doesn't work if or
- {closedOverlay} - {pendingApprovalOverlay} - {loadingOrErrorOverlay} + {isDraft ? ( + +
+ + + + + + + ) : null} + {isClosed ? ( + + + + ) : null} + {isPendingApproval ? ( + + ) : null} + {thisListingInProgress ? ( + + + + ) : hasError ? ( + + ) : null}
+
-
- {formattedPrice} -
-
- -
+ {formattedPrice ? ( + +
+ {formattedPrice} +
+
+ +
+
+ ) : ( +
+ +
+ )}
+
-
{formatTitle(title, MAX_LENGTH_FOR_WORDS_IN_TITLE)}
+
+ { + event.preventDefault(); + event.stopPropagation(); + history.push(createListingURL(routeConfiguration(), listing)); + }} + > + {formatTitle(title, MAX_LENGTH_FOR_WORDS_IN_TITLE)} + +
+
+ +
+ + +
-
); diff --git a/src/components/ManageListingCard/Overlay.css b/src/components/ManageListingCard/Overlay.css new file mode 100644 index 0000000000..13f9a11a70 --- /dev/null +++ b/src/components/ManageListingCard/Overlay.css @@ -0,0 +1,70 @@ +@import '../../marketplace.css'; + +:root { + /* + These variables are available in global scope through ":root" + element ( tag). Variables with the same names are going to + overwrite each other if CSS Properties' (PostCSS plugin) + configuration "preserve: true" is used - meaning that variables + are left to CSS bundle. We are planning to enable it in the future + since browsers support CSS Properties already. + */ + + --Overlay_coverEverything: { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +} + +.root { + /* Positioning */ + @apply --Overlay_coverEverything; +} + +.overlay { + /* Positioning */ + @apply --Overlay_coverEverything; + + /* Overlay background style */ + background-color: var(--matterColorLight); + opacity: 0.9; +} + +.overlayContent { + @apply --marketplaceH4FontStyles; + color: var(--matterColor); + + /* Positioning */ + @apply --Overlay_coverEverything; + + /* Layout */ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: default; + + /* Remove default margins from font */ + margin-top: 0; + margin-bottom: 0; +} + +@media (--viewportMedium) { + .overlayContent { + margin-top: 0; + margin-bottom: 0; + } +} + +.message { + max-width: 220px; + text-align: center; +} + +.errorMessage { + composes: message; + color: var(--failColor); +} diff --git a/src/components/ManageListingCard/Overlay.js b/src/components/ManageListingCard/Overlay.js new file mode 100644 index 0000000000..d5b337471c --- /dev/null +++ b/src/components/ManageListingCard/Overlay.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { node, string } from 'prop-types'; +import classNames from 'classnames'; + +import css from './Overlay.css'; + +const Overlay = props => { + const { className, rootClassName, message, errorMessage, children } = props; + + const classes = classNames(rootClassName || css.root, className); + + return ( +
{ + event.preventDefault(); + event.stopPropagation(); + }} + > +
+
+ {errorMessage ?
{errorMessage}
: null} + {message ?
{message}
: null} + {children} +
+
+ ); +}; + +Overlay.defaultProps = { + className: null, + rootClassName: null, + message: null, + errorMessage: null, + children: null, +}; + +Overlay.propTypes = { + className: string, + rootClassName: string, + message: string, + errorMessage: string, + children: node, +}; + +export default Overlay; diff --git a/src/components/ManageListingCard/__snapshots__/ManageListingCard.test.js.snap b/src/components/ManageListingCard/__snapshots__/ManageListingCard.test.js.snap index d54462e662..247cc468e8 100644 --- a/src/components/ManageListingCard/__snapshots__/ManageListingCard.test.js.snap +++ b/src/components/ManageListingCard/__snapshots__/ManageListingCard.test.js.snap @@ -3,10 +3,11 @@ exports[`ManageListingCard matches snapshot 1`] = `
-
+
-
- 55 -
+ +
+ 55 +
+
+ +
+
+
+
- + + + listing1 + + + title +
-
- - listing1 - - - title -
+ } + > + +
-
`; diff --git a/src/containers/EditListingPage/EditListingPage.duck.js b/src/containers/EditListingPage/EditListingPage.duck.js index ca821c67d2..76885dca4b 100644 --- a/src/containers/EditListingPage/EditListingPage.duck.js +++ b/src/containers/EditListingPage/EditListingPage.duck.js @@ -1,13 +1,9 @@ import omit from 'lodash/omit'; -import omitBy from 'lodash/omitBy'; -import isUndefined from 'lodash/isUndefined'; -import mergeWith from 'lodash/mergeWith'; import { types as sdkTypes } from '../../util/sdkLoader'; import { storableError } from '../../util/errors'; import { addMarketplaceEntities } from '../../ducks/marketplaceData.duck'; import * as log from '../../util/log'; import { fetchCurrentUserHasListingsSuccess } from '../../ducks/user.duck'; -import { overrideArrays } from '../../util/data'; const { UUID } = sdkTypes; @@ -22,9 +18,13 @@ const errorAction = actionType => error => ({ type: actionType, payload: error, 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 CREATE_LISTING_DRAFT_REQUEST = 'app/EditListingPage/CREATE_LISTING_DRAFT_REQUEST'; +export const CREATE_LISTING_DRAFT_SUCCESS = 'app/EditListingPage/CREATE_LISTING_DRAFT_SUCCESS'; +export const CREATE_LISTING_DRAFT_ERROR = 'app/EditListingPage/CREATE_LISTING_DRAFT_ERROR'; + +export const PUBLISH_LISTING_REQUEST = 'app/EditListingPage/PUBLISH_LISTING_REQUEST'; +export const PUBLISH_LISTING_SUCCESS = 'app/EditListingPage/PUBLISH_LISTING_SUCCESS'; +export const PUBLISH_LISTING_ERROR = 'app/EditListingPage/PUBLISH_LISTING_ERROR'; export const UPDATE_LISTING_REQUEST = 'app/EditListingPage/UPDATE_LISTING_REQUEST'; export const UPDATE_LISTING_SUCCESS = 'app/EditListingPage/UPDATE_LISTING_SUCCESS'; @@ -40,20 +40,19 @@ export const UPLOAD_IMAGE_ERROR = 'app/EditListingPage/UPLOAD_IMAGE_ERROR'; export const UPDATE_IMAGE_ORDER = 'app/EditListingPage/UPDATE_IMAGE_ORDER'; -export const CREATE_LISTING_DRAFT = 'app/EditListingPage/CREATE_LISTING_DRAFT'; -export const UPDATE_LISTING_DRAFT = 'app/EditListingPage/UPDATE_LISTING_DRAFT'; - export const REMOVE_LISTING_IMAGE = 'app/EditListingPage/REMOVE_LISTING_IMAGE'; // ================ Reducer ================ // const initialState = { // Error instance placeholders for each endpoint - createListingsError: null, + createListingDraftError: null, + publishingListing: null, + publishListingError: null, updateListingError: null, showListingsError: null, uploadImageError: null, - createListingInProgress: false, + createListingDraftInProgress: false, submittedListingId: null, redirectToListing: false, images: {}, @@ -72,28 +71,52 @@ export default function reducer(state = initialState, action = {}) { case CLEAR_UPDATED_TAB: return { ...state, updatedTab: null, updateListingError: null }; - case CREATE_LISTING_REQUEST: + case CREATE_LISTING_DRAFT_REQUEST: return { ...state, - createListingInProgress: true, - createListingsError: null, + createListingDraftInProgress: true, + createListingDraftError: null, submittedListingId: null, - redirectToListing: false, + listingDraft: null, }; - case CREATE_LISTING_SUCCESS: + + case CREATE_LISTING_DRAFT_SUCCESS: return { ...state, - createListingInProgress: false, + createListingDraftInProgress: false, submittedListingId: payload.data.id, + listingDraft: payload.data, + }; + case CREATE_LISTING_DRAFT_ERROR: + return { + ...state, + createListingDraftInProgress: false, + createListingDraftError: payload, + }; + + case PUBLISH_LISTING_REQUEST: + return { + ...state, + publishingListing: payload.listingId, + publishListingError: null, + }; + case PUBLISH_LISTING_SUCCESS: + return { redirectToListing: true, + publishingListing: null, }; - case CREATE_LISTING_ERROR: + case PUBLISH_LISTING_ERROR: { + // eslint-disable-next-line no-console + console.error(payload); return { ...state, - createListingInProgress: false, - createListingsError: payload, - redirectToListing: false, + publishingListing: null, + publishListingError: { + listingId: state.publishingListing, + error: payload, + }, }; + } case UPDATE_LISTING_REQUEST: return { ...state, updateInProgress: true, updateListingError: null }; @@ -141,23 +164,6 @@ export default function reducer(state = initialState, action = {}) { case UPDATE_IMAGE_ORDER: return { ...state, imageOrder: payload.imageOrder }; - case CREATE_LISTING_DRAFT: - case UPDATE_LISTING_DRAFT: { - const { attributes, images } = state.listingDraft || {}; - const updatedImages = payload.images || images; - return { - ...state, - listingDraft: { - attributes: mergeWith( - attributes, - omitBy(payload.attributes, isUndefined), - overrideArrays - ), - images: updatedImages, - }, - }; - } - case REMOVE_LISTING_IMAGE: { const id = payload.imageId; @@ -198,28 +204,6 @@ export const updateImageOrder = imageOrder => ({ payload: { imageOrder }, }); -export const createListingDraft = listingData => { - const { images, ...attributes } = listingData; - return { - type: CREATE_LISTING_DRAFT, - payload: { - attributes, - images, - }, - }; -}; - -export const updateListingDraft = listingData => { - const { images, ...attributes } = listingData; - return { - type: UPDATE_LISTING_DRAFT, - payload: { - attributes, - images, - }, - }; -}; - export const removeListingImage = imageId => ({ type: REMOVE_LISTING_IMAGE, payload: { imageId }, @@ -230,9 +214,14 @@ export const removeListingImage = imageId => ({ // expects. // SDK method: ownListings.create -export const createListing = requestAction(CREATE_LISTING_REQUEST); -export const createListingSuccess = successAction(CREATE_LISTING_SUCCESS); -export const createListingError = errorAction(CREATE_LISTING_ERROR); +export const createListingDraft = requestAction(CREATE_LISTING_DRAFT_REQUEST); +export const createListingDraftSuccess = successAction(CREATE_LISTING_DRAFT_SUCCESS); +export const createListingDraftError = errorAction(CREATE_LISTING_DRAFT_ERROR); + +// SDK method: ownListings.publish +export const publishListing = requestAction(PUBLISH_LISTING_REQUEST); +export const publishListingSuccess = successAction(PUBLISH_LISTING_SUCCESS); +export const publishListingError = errorAction(PUBLISH_LISTING_ERROR); // SDK method: ownListings.update export const updateListing = requestAction(UPDATE_LISTING_REQUEST); @@ -267,40 +256,56 @@ export function requestShowListing(actionPayload) { }; } -export function requestCreateListing(data) { +export function requestCreateListingDraft(data) { return (dispatch, getState, sdk) => { - dispatch(createListing(data)); + dispatch(createListingDraft(data)); + + const queryParams = { + expand: true, + include: ['author', 'images'], + 'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'], + }; return sdk.ownListings - .create(data) - .then(response => { - const id = response.data.data.id.uuid; - // Modify store to understand that we have created listing and can redirect away - dispatch(createListingSuccess(response)); - // Fetch listing data so that redirection is smooth - dispatch( - requestShowListing({ - id, - include: ['author', 'images'], - 'fields.image': ['variants.landscape-crop', 'variants.landscape-crop2x'], - }) - ); - return response; - }) + .createDraft(data, queryParams) .then(response => { + //const id = response.data.data.id.uuid; + + // Add the created listing to the marketplace data + dispatch(addMarketplaceEntities(response)); + // We must update the user duck since this might be the first // listing for the user, therefore changing the // currentUserHasListings flag in the store. dispatch(fetchCurrentUserHasListingsSuccess(true)); + + // Modify store to understand that we have created listing and can redirect away + dispatch(createListingDraftSuccess(response)); return response; }) .catch(e => { - log.error(e, 'create-listing-failed', { listingData: data }); - return dispatch(createListingError(storableError(e))); + log.error(e, 'create-listing-draft-failed', { listingData: data }); + return dispatch(createListingDraftError(storableError(e))); }); }; } +export const requestPublishListingDraft = listingId => (dispatch, getState, sdk) => { + dispatch(publishListing(listingId)); + + return sdk.ownListings + .publishDraft({ id: listingId }, { expand: true }) + .then(response => { + // Add the created listing to the marketplace data + dispatch(addMarketplaceEntities(response)); + dispatch(publishListingSuccess(response)); + return response; + }) + .catch(e => { + dispatch(publishListingError(storableError(e))); + }); +}; + // Images return imageId which we need to map with previously generated temporary id export function requestImageUpload(actionPayload) { return (dispatch, getState, sdk) => { @@ -335,6 +340,7 @@ export function requestUpdateListing(tab, data) { .then(() => { dispatch(markTabUpdated(tab)); dispatch(updateListingSuccess(updateResponse)); + return updateResponse; }) .catch(e => { log.error(e, 'update-listing-failed', { listingData: data }); diff --git a/src/containers/EditListingPage/EditListingPage.js b/src/containers/EditListingPage/EditListingPage.js index 3e066b38d1..800f3974c0 100644 --- a/src/containers/EditListingPage/EditListingPage.js +++ b/src/containers/EditListingPage/EditListingPage.js @@ -5,8 +5,15 @@ import { withRouter } from 'react-router-dom'; import { intlShape, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { types as sdkTypes } from '../../util/sdkLoader'; -import { LISTING_PAGE_PENDING_APPROVAL_VARIANT, createSlug } from '../../util/urlHelpers'; -import { LISTING_STATE_PENDING_APPROVAL, propTypes } from '../../util/types'; +import { + LISTING_PAGE_PARAM_TYPE_DRAFT, + LISTING_PAGE_PARAM_TYPE_NEW, + LISTING_PAGE_PARAM_TYPES, + LISTING_PAGE_PENDING_APPROVAL_VARIANT, + createSlug, +} from '../../util/urlHelpers'; +import { LISTING_STATE_DRAFT, LISTING_STATE_PENDING_APPROVAL, propTypes } from '../../util/types'; +import { ensureOwnListing } from '../../util/data'; import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck'; import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck'; import { stripeAccountClearError, createStripeAccount } from '../../ducks/user.duck'; @@ -14,9 +21,8 @@ import { EditListingWizard, NamedRedirect, Page } from '../../components'; import { TopbarContainer } from '../../containers'; import { - createListingDraft, - updateListingDraft, - requestCreateListing, + requestCreateListingDraft, + requestPublishListingDraft, requestUpdateListing, requestImageUpload, updateImageOrder, @@ -38,16 +44,15 @@ export const EditListingPageComponent = props => { getOwnListing, history, intl, - onCreateListing, - onUpdateListing, onCreateListingDraft, + onPublishListingDraft, + onUpdateListing, onImageUpload, onRemoveListingImage, onManageDisableScrolling, onPayoutDetailsSubmit, onPayoutDetailsFormChange, onUpdateImageOrder, - onUpdateListingDraft, onChange, page, params, @@ -55,18 +60,21 @@ export const EditListingPageComponent = props => { } = props; const { id, type } = params; + const isNewURI = type === LISTING_PAGE_PARAM_TYPE_NEW; + const isDraftURI = type === LISTING_PAGE_PARAM_TYPE_DRAFT; - const isNew = type === 'new'; - const newListingCreated = isNew && !!page.submittedListingId; const listingId = page.submittedListingId || (id ? new UUID(id) : null); - const currentListing = getOwnListing(listingId); - const isPendingApproval = - currentListing && currentListing.attributes.state === LISTING_STATE_PENDING_APPROVAL; + const currentListing = ensureOwnListing(getOwnListing(listingId)); + const { state: currentListingState } = currentListing.attributes; - const shouldRedirect = page.submittedListingId && currentListing; - const showForm = isNew || currentListing; + const isPastDraft = currentListingState && currentListingState !== LISTING_STATE_DRAFT; + const shouldRedirect = (isNewURI || isDraftURI) && listingId && isPastDraft; + const showForm = isNewURI || currentListing.id; if (shouldRedirect) { + const isPendingApproval = + currentListing && currentListingState === LISTING_STATE_PENDING_APPROVAL; + // If page has already listingId (after submit) and current listings exist // redirect to listing page const listingSlug = currentListing ? createSlug(currentListing.attributes.title) : null; @@ -91,36 +99,44 @@ export const EditListingPageComponent = props => { return ; } else if (showForm) { const { - createListingsError = null, + createListingDraftError = null, + publishListingError = null, updateListingError = null, showListingsError = null, uploadImageError = null, } = page; const errors = { - createListingsError, + createListingDraftError, + publishListingError, updateListingError, showListingsError, uploadImageError, createStripeAccountError, }; + const newListingPublished = + isDraftURI && currentListing && currentListingState !== LISTING_STATE_DRAFT; // 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 currentListingImages = + currentListing && currentListing.images ? currentListing.images : []; // Images not yet connected to the listing - const unattachedImages = page.imageOrder.map(i => page.images[i]); + const imageOrder = page.imageOrder || []; + const unattachedImages = imageOrder.map(i => page.images[i]); const allImages = currentListingImages.concat(unattachedImages); + const removedImageIds = page.removedImageIds || []; const images = allImages.filter(img => { - return !page.removedImageIds.includes(img.id); + return !removedImageIds.includes(img.id); }); - const title = isNew - ? intl.formatMessage({ id: 'EditListingPage.titleCreateListing' }) - : intl.formatMessage({ id: 'EditListingPage.titleEditListing' }); + const title = + isNewURI || isDraftURI + ? intl.formatMessage({ id: 'EditListingPage.titleCreateListing' }) + : intl.formatMessage({ id: 'EditListingPage.titleEditListing' }); return ( @@ -137,14 +153,13 @@ export const EditListingPageComponent = props => { disabled={disableForm} errors={errors} fetchInProgress={fetchInProgress} - newListingCreated={newListingCreated} + newListingPublished={newListingPublished} history={history} images={images} - listing={isNew ? page.listingDraft : currentListing} - onCreateListing={onCreateListing} + listing={currentListing} onUpdateListing={onUpdateListing} onCreateListingDraft={onCreateListingDraft} - onUpdateListingDraft={onUpdateListingDraft} + onPublishListingDraft={onPublishListingDraft} onPayoutDetailsFormChange={onPayoutDetailsFormChange} onPayoutDetailsSubmit={onPayoutDetailsSubmit} onImageUpload={onImageUpload} @@ -154,7 +169,7 @@ export const EditListingPageComponent = props => { currentUser={currentUser} onManageDisableScrolling={onManageDisableScrolling} updatedTab={page.updatedTab} - updateInProgress={page.updateInProgress || page.createListingInProgress} + updateInProgress={page.updateInProgress || page.createListingDraftInProgress} /> ); @@ -187,22 +202,21 @@ EditListingPageComponent.propTypes = { currentUser: propTypes.currentUser, fetchInProgress: bool.isRequired, getOwnListing: func.isRequired, - onCreateListing: func.isRequired, onCreateListingDraft: func.isRequired, + onPublishListingDraft: func.isRequired, onImageUpload: func.isRequired, onManageDisableScrolling: func.isRequired, onPayoutDetailsFormChange: func.isRequired, onPayoutDetailsSubmit: func.isRequired, onUpdateImageOrder: func.isRequired, onRemoveListingImage: func.isRequired, - onUpdateListingDraft: func.isRequired, onUpdateListing: func.isRequired, onChange: func.isRequired, page: object.isRequired, params: shape({ id: string.isRequired, slug: string.isRequired, - type: oneOf(['new', 'edit']).isRequired, + type: oneOf(LISTING_PAGE_PARAM_TYPES).isRequired, tab: string.isRequired, }).isRequired, scrollingDisabled: bool.isRequired, @@ -238,9 +252,9 @@ const mapStateToProps = state => { }; const mapDispatchToProps = dispatch => ({ - onCreateListing: values => dispatch(requestCreateListing(values)), onUpdateListing: (tab, values) => dispatch(requestUpdateListing(tab, values)), - onCreateListingDraft: values => dispatch(createListingDraft(values)), + onCreateListingDraft: values => dispatch(requestCreateListingDraft(values)), + onPublishListingDraft: listingId => dispatch(requestPublishListingDraft(listingId)), onImageUpload: data => dispatch(requestImageUpload(data)), onManageDisableScrolling: (componentId, disableScrolling) => dispatch(manageDisableScrolling(componentId, disableScrolling)), @@ -248,7 +262,6 @@ const mapDispatchToProps = dispatch => ({ onPayoutDetailsSubmit: values => dispatch(createStripeAccount(values)), onUpdateImageOrder: imageOrder => dispatch(updateImageOrder(imageOrder)), onRemoveListingImage: imageId => dispatch(removeListingImage(imageId)), - onUpdateListingDraft: values => dispatch(updateListingDraft(values)), onChange: () => dispatch(clearUpdatedTab()), }); diff --git a/src/containers/EditListingPage/EditListingPage.test.js b/src/containers/EditListingPage/EditListingPage.test.js index 7137f06628..5a9637fc97 100644 --- a/src/containers/EditListingPage/EditListingPage.test.js +++ b/src/containers/EditListingPage/EditListingPage.test.js @@ -24,7 +24,7 @@ describe('EditListingPageComponent', () => { onManageDisableScrolling={noop} onCreateListing={noop} onCreateListingDraft={noop} - onUpdateListingDraft={noop} + onPublishListingDraft={noop} onUpdateListing={noop} onImageUpload={noop} onRemoveListingImage={noop} diff --git a/src/containers/EditListingPage/__snapshots__/EditListingPage.test.js.snap b/src/containers/EditListingPage/__snapshots__/EditListingPage.test.js.snap index 08f5d6d533..9a7f6d6b3d 100644 --- a/src/containers/EditListingPage/__snapshots__/EditListingPage.test.js.snap +++ b/src/containers/EditListingPage/__snapshots__/EditListingPage.test.js.snap @@ -10,8 +10,9 @@ exports[`EditListingPageComponent matches snapshot 1`] = ` currentUser={null} errors={ Object { - "createListingsError": null, + "createListingDraftError": null, "createStripeAccountError": null, + "publishListingError": null, "showListingsError": null, "updateListingError": null, "uploadImageError": null, @@ -25,18 +26,27 @@ exports[`EditListingPageComponent matches snapshot 1`] = ` } id="EditListingWizard" images={Array []} - newListingCreated={false} + listing={ + Object { + "attributes": Object { + "publicData": Object {}, + }, + "id": null, + "images": Array [], + "type": "ownListing", + } + } + newListingPublished={false} onChange={[Function]} - onCreateListing={[Function]} onCreateListingDraft={[Function]} onImageUpload={[Function]} onManageDisableScrolling={[Function]} onPayoutDetailsFormChange={[Function]} onPayoutDetailsSubmit={[Function]} + onPublishListingDraft={[Function]} onRemoveImage={[Function]} onUpdateImageOrder={[Function]} onUpdateListing={[Function]} - onUpdateListingDraft={[Function]} params={ Object { "id": "id", diff --git a/src/containers/ListingPage/ActionBarMaybe.js b/src/containers/ListingPage/ActionBarMaybe.js index 823bd665c4..5b07d19a42 100644 --- a/src/containers/ListingPage/ActionBarMaybe.js +++ b/src/containers/ListingPage/ActionBarMaybe.js @@ -2,7 +2,12 @@ import React from 'react'; import { bool, oneOfType, object } from 'prop-types'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; -import { LISTING_STATE_PENDING_APPROVAL, LISTING_STATE_CLOSED, propTypes } from '../../util/types'; +import { + LISTING_STATE_PENDING_APPROVAL, + LISTING_STATE_CLOSED, + LISTING_STATE_DRAFT, + propTypes, +} from '../../util/types'; import { NamedLink } from '../../components'; import EditIcon from './EditIcon'; @@ -13,6 +18,7 @@ export const ActionBarMaybe = props => { const state = listing.attributes.state; const isPendingApproval = state === LISTING_STATE_PENDING_APPROVAL; const isClosed = state === LISTING_STATE_CLOSED; + const isDraft = state === LISTING_STATE_DRAFT; if (isOwnListing) { let ownListingTextTranslationId = 'ListingPage.ownListing'; @@ -21,8 +27,12 @@ export const ActionBarMaybe = props => { ownListingTextTranslationId = 'ListingPage.ownListingPendingApproval'; } else if (isClosed) { ownListingTextTranslationId = 'ListingPage.ownClosedListing'; + } else if (isDraft) { + ownListingTextTranslationId = 'ListingPage.ownListingDraft'; } + const message = isDraft ? 'ListingPage.finishListing' : 'ListingPage.editListing'; + const ownListingTextClasses = classNames(css.ownListingText, { [css.ownListingTextPendingApproval]: isPendingApproval, }); @@ -34,7 +44,7 @@ export const ActionBarMaybe = props => {

- +
); diff --git a/src/containers/ListingPage/ListingPage.duck.js b/src/containers/ListingPage/ListingPage.duck.js index 1728586c3c..56fbdeeb10 100644 --- a/src/containers/ListingPage/ListingPage.duck.js +++ b/src/containers/ListingPage/ListingPage.duck.js @@ -6,7 +6,10 @@ import { storableError } from '../../util/errors'; import { addMarketplaceEntities } from '../../ducks/marketplaceData.duck'; import { denormalisedResponseEntities } from '../../util/data'; import { TRANSITION_ENQUIRE } from '../../util/types'; -import { LISTING_PAGE_PENDING_APPROVAL_VARIANT } from '../../util/urlHelpers'; +import { + LISTING_PAGE_DRAFT_VARIANT, + LISTING_PAGE_PENDING_APPROVAL_VARIANT, +} from '../../util/urlHelpers'; import { fetchCurrentUser, fetchCurrentUserHasOrdersSuccess } from '../../ducks/user.duck'; const { UUID } = sdkTypes; @@ -269,7 +272,8 @@ export const sendEnquiry = (listingId, message) => (dispatch, getState, sdk) => export const loadData = (params, search) => dispatch => { const listingId = new UUID(params.id); - if (params.variant === LISTING_PAGE_PENDING_APPROVAL_VARIANT) { + const ownListingVariants = [LISTING_PAGE_DRAFT_VARIANT, LISTING_PAGE_PENDING_APPROVAL_VARIANT]; + if (ownListingVariants.includes(params.variant)) { return dispatch(showListing(listingId, true)); } diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 9e11022c9b..f5a4266712 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -9,7 +9,14 @@ import config from '../../config'; import routeConfiguration from '../../routeConfiguration'; import { LISTING_STATE_PENDING_APPROVAL, LISTING_STATE_CLOSED, propTypes } from '../../util/types'; import { types as sdkTypes } from '../../util/sdkLoader'; -import { LISTING_PAGE_PENDING_APPROVAL_VARIANT, createSlug, parse } from '../../util/urlHelpers'; +import { + LISTING_PAGE_DRAFT_VARIANT, + LISTING_PAGE_PENDING_APPROVAL_VARIANT, + LISTING_PAGE_PARAM_TYPE_DRAFT, + LISTING_PAGE_PARAM_TYPE_EDIT, + createSlug, + parse, +} from '../../util/urlHelpers'; import { formatMoney } from '../../util/currency'; import { createResourceLocatorString, findRouteByRouteName } from '../../util/routes'; import { ensureListing, ensureOwnListing, ensureUser, userDisplayName } from '../../util/data'; @@ -32,7 +39,7 @@ import { sendEnquiry, loadData, setInitialValues } from './ListingPage.duck'; import SectionImages from './SectionImages'; import SectionAvatar from './SectionAvatar'; import SectionHeading from './SectionHeading'; -import SectionDescription from './SectionDescription'; +import SectionDescriptionMaybe from './SectionDescriptionMaybe'; import SectionFeatures from './SectionFeatures'; import SectionReviews from './SectionReviews'; import SectionHost from './SectionHost'; @@ -206,13 +213,20 @@ export class ListingPageComponent extends Component { const isBook = !!parse(location.search).book; const listingId = new UUID(rawParams.id); const isPendingApprovalVariant = rawParams.variant === LISTING_PAGE_PENDING_APPROVAL_VARIANT; - const currentListing = isPendingApprovalVariant - ? ensureOwnListing(getOwnListing(listingId)) - : ensureListing(getListing(listingId)); + const isDraftVariant = rawParams.variant === LISTING_PAGE_DRAFT_VARIANT; + const currentListing = + isPendingApprovalVariant || isDraftVariant + ? ensureOwnListing(getOwnListing(listingId)) + : ensureListing(getListing(listingId)); const listingSlug = rawParams.slug || createSlug(currentListing.attributes.title || ''); const params = { slug: listingSlug, ...rawParams }; + const listingType = isDraftVariant + ? LISTING_PAGE_PARAM_TYPE_DRAFT + : LISTING_PAGE_PARAM_TYPE_EDIT; + const listingTab = isDraftVariant ? 'photos' : 'description'; + const isApproved = currentListing.id && currentListing.attributes.state !== LISTING_STATE_PENDING_APPROVAL; @@ -224,7 +238,9 @@ export class ListingPageComponent extends Component { // another user. We use this information to try to fetch the // public listing. const pendingOtherUsersListing = - isPendingApprovalVariant && showListingError && showListingError.status === 403; + (isPendingApprovalVariant || isDraftVariant) && + showListingError && + showListingError.status === 403; const shouldShowPublicListingPage = pendingIsApproved || pendingOtherUsersListing; if (shouldShowPublicListingPage) { @@ -418,8 +434,8 @@ export class ListingPageComponent extends Component { editParams={{ id: listingId.uuid, slug: listingSlug, - type: 'edit', - tab: 'description', + type: listingType, + tab: listingTab, }} imageCarouselOpen={this.state.imageCarouselOpen} onImageCarouselClose={() => this.setState({ imageCarouselOpen: false })} @@ -438,7 +454,7 @@ export class ListingPageComponent extends Component { showContactUser={showContactUser} onContactUser={this.onContactUser} /> - + { +const SectionDescriptionMaybe = props => { const { description } = props; - return ( + return description ? (

@@ -20,7 +20,7 @@ const SectionDescription = props => { })}

- ); + ) : null; }; -export default SectionDescription; +export default SectionDescriptionMaybe; diff --git a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap index 3d19b6dedc..072c0e2a4a 100644 --- a/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap +++ b/src/containers/ListingPage/__snapshots__/ListingPage.test.js.snap @@ -147,7 +147,7 @@ exports[`ListingPage matches snapshot 1`] = ` } showContactUser={true} /> - ( pristine, saveActionMsg, updated, - updateError, updateInProgress, + fetchErrors, } = fieldRenderProps; const titleMessage = intl.formatMessage({ id: 'EditListingDescriptionForm.title' }); @@ -56,12 +56,26 @@ const EditListingDescriptionFormComponent = props => ( id: 'EditListingDescriptionForm.descriptionRequired', }); - const errorMessage = updateError ? ( + const { updateListingError, createListingDraftError, showListingsError } = fetchErrors || {}; + const errorMessageUpdateListing = updateListingError ? (

) : null; + // This error happens only on first tab (of EditListingWizard) + const errorMessageCreateListingDraft = createListingDraftError ? ( +

+ +

+ ) : null; + + const errorMessageShowListing = showListingsError ? ( +

+ +

+ ) : null; + const classes = classNames(css.root, className); const submitReady = updated && pristine; const submitInProgress = updateInProgress; @@ -69,7 +83,9 @@ const EditListingDescriptionFormComponent = props => ( return (
- {errorMessage} + {errorMessageCreateListingDraft} + {errorMessageUpdateListing} + {errorMessageShowListing} ( /> ); -EditListingDescriptionFormComponent.defaultProps = { className: null, updateError: null }; +EditListingDescriptionFormComponent.defaultProps = { className: null, fetchErrors: null }; EditListingDescriptionFormComponent.propTypes = { className: string, @@ -122,8 +138,12 @@ EditListingDescriptionFormComponent.propTypes = { onSubmit: func.isRequired, saveActionMsg: string.isRequired, updated: bool.isRequired, - updateError: propTypes.error, updateInProgress: bool.isRequired, + fetchErrors: shape({ + createListingDraftError: propTypes.error, + showListingsError: propTypes.error, + updateListingError: propTypes.error, + }), categories: arrayOf( shape({ key: string.isRequired, diff --git a/src/forms/EditListingFeaturesForm/EditListingFeaturesForm.js b/src/forms/EditListingFeaturesForm/EditListingFeaturesForm.js index 64607dd421..1820910df7 100644 --- a/src/forms/EditListingFeaturesForm/EditListingFeaturesForm.js +++ b/src/forms/EditListingFeaturesForm/EditListingFeaturesForm.js @@ -1,5 +1,5 @@ import React from 'react'; -import { bool, func, string } from 'prop-types'; +import { bool, func, shape, string } from 'prop-types'; import classNames from 'classnames'; import { Form as FinalForm } from 'react-final-form'; import arrayMutators from 'final-form-arrays'; @@ -25,8 +25,8 @@ const EditListingFeaturesFormComponent = props => ( pristine, saveActionMsg, updated, - updateError, updateInProgress, + fetchErrors, } = fieldRenderProps; const classes = classNames(rootClassName || css.root, className); @@ -34,15 +34,23 @@ const EditListingFeaturesFormComponent = props => ( const submitInProgress = updateInProgress; const submitDisabled = disabled || submitInProgress; - const errorMessage = updateError ? ( + const { updateListingError, showListingsError } = fetchErrors || {}; + const errorMessage = updateListingError ? (

) : null; + const errorMessageShowListing = showListingsError ? ( +

+ +

+ ) : null; + return ( {errorMessage} + {errorMessageShowListing} ( EditListingFeaturesFormComponent.defaultProps = { rootClassName: null, className: null, - updateError: null, + fetchErrors: null, }; EditListingFeaturesFormComponent.propTypes = { @@ -79,8 +87,11 @@ EditListingFeaturesFormComponent.propTypes = { onSubmit: func.isRequired, saveActionMsg: string.isRequired, updated: bool.isRequired, - updateError: propTypes.error, updateInProgress: bool.isRequired, + fetchErrors: shape({ + showListingsError: propTypes.error, + updateListingError: propTypes.error, + }), }; const EditListingFeaturesForm = EditListingFeaturesFormComponent; diff --git a/src/forms/EditListingLocationForm/EditListingLocationForm.js b/src/forms/EditListingLocationForm/EditListingLocationForm.js index 295269f2a6..5d40fb0abf 100644 --- a/src/forms/EditListingLocationForm/EditListingLocationForm.js +++ b/src/forms/EditListingLocationForm/EditListingLocationForm.js @@ -1,5 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import { bool, func, shape, string } from 'prop-types'; import { compose } from 'redux'; import { Form as FinalForm } from 'react-final-form'; import { intlShape, injectIntl, FormattedMessage } from 'react-intl'; @@ -27,8 +27,8 @@ export const EditListingLocationFormComponent = props => ( pristine, saveActionMsg, updated, - updateError, updateInProgress, + fetchErrors, values, } = fieldRenderProps; @@ -48,12 +48,19 @@ export const EditListingLocationFormComponent = props => ( id: 'EditListingLocationForm.buildingPlaceholder', }); - const errorMessage = updateError ? ( + const { updateListingError, showListingsError } = fetchErrors || {}; + const errorMessage = updateListingError ? (

) : null; + const errorMessageShowListing = showListingsError ? ( +

+ +

+ ) : null; + const classes = classNames(css.root, className); const submitReady = updated && pristine; const submitInProgress = updateInProgress; @@ -62,6 +69,7 @@ export const EditListingLocationFormComponent = props => ( return ( {errorMessage} + {errorMessageShowListing} ( EditListingLocationFormComponent.defaultProps = { selectedPlace: null, - updateError: null, + fetchErrors: null, }; -const { func, string, bool } = PropTypes; - EditListingLocationFormComponent.propTypes = { intl: intlShape.isRequired, onSubmit: func.isRequired, saveActionMsg: string.isRequired, selectedPlace: propTypes.place, updated: bool.isRequired, - updateError: propTypes.error, updateInProgress: bool.isRequired, + fetchErrors: shape({ + showListingsError: propTypes.error, + updateListingError: propTypes.error, + }), }; export default compose(injectIntl)(EditListingLocationFormComponent); diff --git a/src/forms/EditListingLocationForm/__snapshots__/EditListingLocationForm.test.js.snap b/src/forms/EditListingLocationForm/__snapshots__/EditListingLocationForm.test.js.snap index d2f442f451..ef131e56a9 100644 --- a/src/forms/EditListingLocationForm/__snapshots__/EditListingLocationForm.test.js.snap +++ b/src/forms/EditListingLocationForm/__snapshots__/EditListingLocationForm.test.js.snap @@ -3,6 +3,7 @@ exports[`EditListingLocationForm matches snapshot 1`] = ` diff --git a/src/forms/EditListingPhotosForm/EditListingPhotosForm.js b/src/forms/EditListingPhotosForm/EditListingPhotosForm.js index d0bdb801b6..2cb7389e80 100644 --- a/src/forms/EditListingPhotosForm/EditListingPhotosForm.js +++ b/src/forms/EditListingPhotosForm/EditListingPhotosForm.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { array, bool, func, object, shape, string } from 'prop-types'; +import { array, bool, func, shape, string } from 'prop-types'; import { compose } from 'redux'; import { Form as FinalForm, Field } from 'react-final-form'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; @@ -48,7 +48,7 @@ export class EditListingPhotosFormComponent extends Component { form, className, disabled, - errors, + fetchErrors, handleSubmit, images, imageUploadRequested, @@ -59,7 +59,6 @@ export class EditListingPhotosFormComponent extends Component { ready, saveActionMsg, updated, - updateError, updateInProgress, } = fieldRenderProps; @@ -78,7 +77,8 @@ export class EditListingPhotosFormComponent extends Component { id: 'EditListingPhotosForm.imageRequired', }); - const { createListingsError, showListingsError, uploadImageError } = errors; + const { publishListingError, showListingsError, updateListingError, uploadImageError } = + fetchErrors || {}; const uploadOverLimit = isUploadImageOverLimitError(uploadImageError); let uploadImageFailed = null; @@ -100,9 +100,9 @@ export class EditListingPhotosFormComponent extends Component { // 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 ? ( + const publishListingFailed = publishListingError ? (

- +

) : null; const showListingFailed = showListingsError ? ( @@ -135,7 +135,7 @@ export class EditListingPhotosFormComponent extends Component { handleSubmit(e); }} > - {updateError ? ( + {updateListingError ? (

@@ -203,7 +203,7 @@ export class EditListingPhotosFormComponent extends Component {

- {createListingFailed} + {publishListingFailed} {showListingFailed}