From f7837e33690dff9cd375932d26b4d71629fff8a2 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 00:58:34 +0200 Subject: [PATCH 01/17] Fix API changes: description can be null. --- src/containers/ListingPage/ListingPage.js | 4 ++-- .../{SectionDescription.js => SectionDescriptionMaybe.js} | 8 ++++---- src/util/types.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/containers/ListingPage/{SectionDescription.js => SectionDescriptionMaybe.js} (84%) diff --git a/src/containers/ListingPage/ListingPage.js b/src/containers/ListingPage/ListingPage.js index 9e11022c9b..72ae458ce9 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -32,7 +32,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'; @@ -438,7 +438,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/util/types.js b/src/util/types.js index 5b643c825a..8b93a88170 100644 --- a/src/util/types.js +++ b/src/util/types.js @@ -146,7 +146,7 @@ const listingAttributes = shape({ const ownListingAttributes = shape({ title: string.isRequired, - description: string.isRequired, + description: string, geolocation: propTypes.latlng, deleted: propTypes.value(false).isRequired, state: oneOf(LISTING_STATES).isRequired, From 3b834e98d058d6fd9b257f937c79579bcf376d03 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 11:42:58 +0200 Subject: [PATCH 02/17] Add LISTING_STATE_DRAFT to types.js --- src/util/types.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/types.js b/src/util/types.js index 8b93a88170..2c595e4b77 100644 --- a/src/util/types.js +++ b/src/util/types.js @@ -124,11 +124,13 @@ propTypes.user = shape({ profileImage: propTypes.image, }); +export const LISTING_STATE_DRAFT = 'draft'; export const LISTING_STATE_PENDING_APPROVAL = 'pendingApproval'; export const LISTING_STATE_PUBLISHED = 'published'; export const LISTING_STATE_CLOSED = 'closed'; const LISTING_STATES = [ + LISTING_STATE_DRAFT, LISTING_STATE_PENDING_APPROVAL, LISTING_STATE_PUBLISHED, LISTING_STATE_CLOSED, From 77743a2e4b55706860b740715b5af195f9a15e7e Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 11:45:03 +0200 Subject: [PATCH 03/17] Add ListingPage path params to urlHelpers.js --- src/util/urlHelpers.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/util/urlHelpers.js b/src/util/urlHelpers.js index a873ba9680..3ca52e752d 100644 --- a/src/util/urlHelpers.js +++ b/src/util/urlHelpers.js @@ -4,6 +4,16 @@ import { types as sdkTypes } from './sdkLoader'; const { LatLng, LatLngBounds } = sdkTypes; export const LISTING_PAGE_PENDING_APPROVAL_VARIANT = 'pending-approval'; +export const LISTING_PAGE_DRAFT_VARIANT = 'draft'; + +export const LISTING_PAGE_PARAM_TYPE_NEW = 'new'; +export const LISTING_PAGE_PARAM_TYPE_DRAFT = 'draft'; +export const LISTING_PAGE_PARAM_TYPE_EDIT = 'edit'; +export const LISTING_PAGE_PARAM_TYPES = [ + LISTING_PAGE_PARAM_TYPE_NEW, + LISTING_PAGE_PARAM_TYPE_DRAFT, + LISTING_PAGE_PARAM_TYPE_EDIT, +]; // Create slug from random texts // From Gist thread: https://gist.github.com/mathewbyrne/1280286 From 1aaad1d4e1e2401dcdf4047971b4c3ee9a67055e Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 11:50:06 +0200 Subject: [PATCH 04/17] ListingLink: add draft as a path variant --- src/components/ListingLink/ListingLink.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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, }, } : { From 3a797fc6538b93cb93ecbf074b7f1f3d17568093 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 11:55:53 +0200 Subject: [PATCH 05/17] ListingPage: handle data loading of draft listings --- .../ListingPage/ListingPage.duck.js | 8 +++++-- src/containers/ListingPage/ListingPage.js | 21 +++++++++++++------ .../__snapshots__/ListingPage.test.js.snap | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) 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 72ae458ce9..b5af4fb6cb 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -9,7 +9,12 @@ 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, + createSlug, + parse, +} from '../../util/urlHelpers'; import { formatMoney } from '../../util/currency'; import { createResourceLocatorString, findRouteByRouteName } from '../../util/routes'; import { ensureListing, ensureOwnListing, ensureUser, userDisplayName } from '../../util/data'; @@ -206,9 +211,11 @@ 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 }; @@ -224,7 +231,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) { @@ -524,7 +533,7 @@ ListingPageComponent.propTypes = { params: shape({ id: string.isRequired, slug: string, - variant: oneOf([LISTING_PAGE_PENDING_APPROVAL_VARIANT]), + variant: oneOf([LISTING_PAGE_DRAFT_VARIANT, LISTING_PAGE_PENDING_APPROVAL_VARIANT]), }).isRequired, isAuthenticated: bool.isRequired, 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} /> - Date: Wed, 7 Nov 2018 16:20:46 +0200 Subject: [PATCH 06/17] Update dependencies: Flex SDK --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index cc004f00f4..e328af27b8 100644 --- a/package.json +++ b/package.json @@ -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/yarn.lock b/yarn.lock index d9cd27ba5a..32e04d64c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7486,9 +7486,9 @@ shallowequal@^0.2.2: dependencies: lodash.keys "^3.1.2" -sharetribe-flex-sdk@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sharetribe-flex-sdk/-/sharetribe-flex-sdk-1.0.0.tgz#ed3a6199e4755917fcd97355e5d7633a490f4ba6" +sharetribe-flex-sdk@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/sharetribe-flex-sdk/-/sharetribe-flex-sdk-1.1.0.tgz#13cc5120b6c0709b20a2ff275c1496184b2e7255" dependencies: axios "^0.18.0" js-cookie "^2.1.3" From bbb08d75ee7761304608ffc958a7777c211cfb1e Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 12:38:33 +0200 Subject: [PATCH 07/17] EditListingPage.duck: create listing draft, publish listing draft. Removing requestCreateListing and actions for storing local draft data to redux store. --- .../EditListingPage/EditListingPage.duck.js | 172 +++++++++--------- 1 file changed, 89 insertions(+), 83 deletions(-) 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 }); From f7efde7baff97c2530aa4c01b23bc4084c20abe4 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Tue, 6 Nov 2018 12:42:34 +0200 Subject: [PATCH 08/17] EditListingPage: use draft thunks and check listing draft state --- .../EditListingPage/EditListingPage.js | 81 +++++++++++-------- .../EditListingPage/EditListingPage.test.js | 2 +- .../EditListingPage.test.js.snap | 18 ++++- 3 files changed, 62 insertions(+), 39 deletions(-) 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", From 2a31ccc4583faf0832d6803676080ae020f2befc Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 7 Nov 2018 12:15:35 +0200 Subject: [PATCH 09/17] EditListingWizard panels: pass all errors as fetchErrors to Final Form --- .../EditListingDescriptionPanel.js | 11 ++++++----- .../EditListingFeaturesPanel.js | 6 ++++-- .../EditListingLocationPanel.js | 7 +++++-- .../EditListingPhotosPanel.js | 16 ++++++---------- .../EditListingPoliciesPanel.js | 6 ++++-- .../EditListingPricingPanel.js | 6 ++++-- 6 files changed, 29 insertions(+), 23 deletions(-) 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..28b8dfa7e7 100644 --- a/src/components/EditListingPhotosPanel/EditListingPhotosPanel.js +++ b/src/components/EditListingPhotosPanel/EditListingPhotosPanel.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes 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'; @@ -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 ? ( }} @@ -48,7 +51,7 @@ class EditListingPhotosPanel extends Component { className={css.form} disabled={fetchInProgress} ready={newListingCreated} - errors={errors} + fetchErrors={errors} initialValues={{ images }} images={images} onImageUpload={onImageUpload} @@ -61,7 +64,6 @@ class EditListingPhotosPanel extends Component { onRemoveImage={onRemoveImage} saveActionMsg={submitButtonText} updated={panelUpdated} - updateError={errors.updateListingError} updateInProgress={updateInProgress} /> @@ -82,13 +84,7 @@ 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, images: array, 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} /> ) : (
From 98e279542d27c7168b13c79ff7c09813231bd5cb Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 7 Nov 2018 12:17:45 +0200 Subject: [PATCH 10/17] EditListingWizard forms: handle errors from fetchErrors. Every form should show (at least) updateListingError and showListingsError. --- .../EditListingDescriptionForm.js | 32 +++++++++++++++---- .../EditListingFeaturesForm.js | 21 +++++++++--- .../EditListingLocationForm.js | 23 +++++++++---- .../EditListingLocationForm.test.js.snap | 2 +- .../EditListingPhotosForm.js | 28 ++++++++-------- .../EditListingPhotosForm.test.js.snap | 3 +- .../EditListingPoliciesForm.js | 20 ++++++++---- .../EditListingPricingForm.js | 21 ++++++++---- src/translations/en.json | 8 ++++- 9 files changed, 109 insertions(+), 49 deletions(-) diff --git a/src/forms/EditListingDescriptionForm/EditListingDescriptionForm.js b/src/forms/EditListingDescriptionForm/EditListingDescriptionForm.js index 1fd63e719a..6d94f725a1 100644 --- a/src/forms/EditListingDescriptionForm/EditListingDescriptionForm.js +++ b/src/forms/EditListingDescriptionForm/EditListingDescriptionForm.js @@ -1,5 +1,5 @@ import React from 'react'; -import { bool, func, string, arrayOf, shape } from 'prop-types'; +import { arrayOf, 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 @@ const EditListingDescriptionFormComponent = props => ( 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} -
- - ); - - 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/__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/translations/en.json b/src/translations/en.json index 303c1fbbf9..118f96544f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -324,10 +324,14 @@ "ManageListingCard.actionFailed": "Whoops, something went wrong. Please refresh the page and try again.", "ManageListingCard.closeListing": "Close listing", "ManageListingCard.closedListing": "This listing is closed and not visible on the marketplace.", - "ManageListingCard.edit": "Edit", + "ManageListingCard.draftOverlayText": "{listingTitle} is a draft and can't be booked.", + "ManageListingCard.editListing": "Edit listing", + "ManageListingCard.finishListingDraft": "Finish listing", + "ManageListingCard.manageAvailability": "Manage availability", "ManageListingCard.openListing": "Open listing", "ManageListingCard.pendingApproval": "{listingTitle} is pending admin approval and can't be booked.", "ManageListingCard.perUnit": "per night", + "ManageListingCard.priceNotSet": "Price per night not set", "ManageListingCard.unsupportedPrice": "({currency})", "ManageListingCard.unsupportedPriceTitle": "Unsupported currency ({currency})", "ManageListingCard.viewListing": "View listing", From 75f295cbfc57c39cfbd88e0a4b9ea7523dbd96d3 Mon Sep 17 00:00:00 2001 From: Jenni Nurmi Date: Wed, 7 Nov 2018 15:04:35 +0200 Subject: [PATCH 15/17] ListingPage: ActionBar links to draft edit mode if listing is a draft. --- src/containers/ListingPage/ActionBarMaybe.js | 14 ++++++++++++-- src/containers/ListingPage/ListingPage.js | 11 +++++++++-- src/translations/en.json | 2 ++ 3 files changed, 23 insertions(+), 4 deletions(-) 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.js b/src/containers/ListingPage/ListingPage.js index b5af4fb6cb..f5a4266712 100644 --- a/src/containers/ListingPage/ListingPage.js +++ b/src/containers/ListingPage/ListingPage.js @@ -12,6 +12,8 @@ import { types as sdkTypes } from '../../util/sdkLoader'; 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'; @@ -220,6 +222,11 @@ export class ListingPageComponent extends Component { 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; @@ -427,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 })} diff --git a/src/translations/en.json b/src/translations/en.json index 118f96544f..7e3ac40980 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -295,12 +295,14 @@ "ListingPage.errorLoadingListingMessage": "Could not load listing. Please try again.", "ListingPage.errorLoadingListingTitle": "Error in loading listing", "ListingPage.featuresTitle": "Amenities", + "ListingPage.finishListing": "Finish listing", "ListingPage.hostedBy": "Hosted by {name}", "ListingPage.loadingListingMessage": "Loading listing…", "ListingPage.loadingListingTitle": "Loading listing…", "ListingPage.locationTitle": "Location", "ListingPage.ownClosedListing": "Your listing has been closed and can't be booked.", "ListingPage.ownListing": "This is your own listing.", + "ListingPage.ownListingDraft": "This listing is a draft.", "ListingPage.ownListingPendingApproval": "This listing is pending approval.", "ListingPage.perUnit": "per night", "ListingPage.reviewsError": "Loading reviews failed.", From 7d9d96cf00fa214b382aee60bf25ec5eb4d8843a Mon Sep 17 00:00:00 2001 From: Jenni Nurmi Date: Wed, 7 Nov 2018 17:05:13 +0200 Subject: [PATCH 16/17] Changelog updated --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) 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) From 516c486e817d78559e512a53e671622b7d3407bc Mon Sep 17 00:00:00 2001 From: Jenni Nurmi Date: Tue, 13 Nov 2018 09:59:30 +0200 Subject: [PATCH 17/17] Version updated --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e328af27b8..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": {