mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
New URL schema for creating a listing
The URL will now always have an id and a slug so that new/edit listing wizards can be generalised. When creating a new listing, temporary id and slug are put to the URL.
This commit is contained in:
parent
9378b6ff45
commit
f0ca0564dd
8 changed files with 75 additions and 82 deletions
|
|
@ -63,7 +63,7 @@ describe('Application', () => {
|
|||
const defaultAuthPath = '/signup';
|
||||
const urlRedirects = {
|
||||
'/l/new': defaultAuthPath,
|
||||
'/l/listing-title-slug/1234/edit': defaultAuthPath,
|
||||
'/l/listing-title-slug/1234/new/description': defaultAuthPath,
|
||||
'/l/listing-title-slug/1234/checkout': defaultAuthPath,
|
||||
'/u/1234/edit': defaultAuthPath,
|
||||
'/inbox': defaultAuthPath,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,15 @@ const noop = () => null;
|
|||
export const NoPhotos = {
|
||||
component: EditListingWizard,
|
||||
props: {
|
||||
params: {
|
||||
id: 'some-id',
|
||||
slug: 'some-slug',
|
||||
type: 'edit',
|
||||
tab: 'pricing',
|
||||
},
|
||||
fetchInProgress: false,
|
||||
flattenedRoutes: flattenRoutes(routesConfiguration),
|
||||
history: { push: noop },
|
||||
selectedTab: 'pricing',
|
||||
images: [],
|
||||
listing: createListing('listing1'),
|
||||
stripeConnected: true,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { PropTypes } from 'react';
|
|||
import classNames from 'classnames';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { ensureListing } from '../../util/data';
|
||||
import { createSlug } from '../../util/urlHelpers';
|
||||
import { createResourceLocatorString } from '../../util/routes';
|
||||
import {
|
||||
EditListingDescriptionPanel,
|
||||
|
|
@ -58,6 +57,7 @@ TestPanel.propTypes = {
|
|||
const EditListingWizard = props => {
|
||||
const {
|
||||
className,
|
||||
params,
|
||||
errors,
|
||||
fetchInProgress,
|
||||
flattenedRoutes,
|
||||
|
|
@ -71,34 +71,28 @@ const EditListingWizard = props => {
|
|||
onUpdateImageOrder,
|
||||
onUpdateListingDraft,
|
||||
rootClassName,
|
||||
selectedTab,
|
||||
currentUser,
|
||||
onManageDisableScrolling,
|
||||
} = props;
|
||||
|
||||
const selectedTab = params.tab;
|
||||
const rootClasses = rootClassName || css.root;
|
||||
const classes = classNames(rootClasses, className);
|
||||
const currentListing = ensureListing(listing);
|
||||
const stepsStatus = stepsActive(currentListing);
|
||||
|
||||
const tabParams = tab => {
|
||||
return { ...params, tab };
|
||||
};
|
||||
const tabLink = tab => {
|
||||
return { name: 'EditListingPage', params: tabParams(tab) };
|
||||
};
|
||||
|
||||
// If selectedStep is not active, redirect to the beginning of wizard
|
||||
if (!stepsStatus[selectedTab]) {
|
||||
if (currentListing.id) {
|
||||
const slug = currentListing.id ? createSlug(currentListing.attributes.title) : null;
|
||||
return (
|
||||
<NamedRedirect
|
||||
name="EditListingDescriptionPage"
|
||||
params={{ id: currentListing.id.uuid, slug }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <NamedRedirect name="NewListingPage" />;
|
||||
return <NamedRedirect name="EditListingPage" params={tabParams(DESCRIPTION)} />;
|
||||
}
|
||||
|
||||
const descriptionLinkProps = currentListing.id
|
||||
? { name: 'EditListingDescriptionPage' }
|
||||
: { name: 'NewListingPage' };
|
||||
|
||||
const onUpsertListingDraft = currentListing.id ? onUpdateListingDraft : onCreateListingDraft;
|
||||
|
||||
return (
|
||||
|
|
@ -106,22 +100,24 @@ const EditListingWizard = props => {
|
|||
<EditListingDescriptionPanel
|
||||
className={css.panel}
|
||||
tabLabel="Description"
|
||||
tabLinkProps={descriptionLinkProps}
|
||||
tabLinkProps={tabLink(DESCRIPTION)}
|
||||
selected={selectedTab === DESCRIPTION}
|
||||
disabled={!stepsStatus[DESCRIPTION]}
|
||||
listing={listing}
|
||||
onSubmit={values => {
|
||||
onUpsertListingDraft(values);
|
||||
// Redirect to EditListingLocationPage
|
||||
|
||||
const pathParams = tabParams(LOCATION);
|
||||
// Redirect to location tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingLocationPage', flattenedRoutes, {}, {})
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<EditListingLocationPanel
|
||||
className={css.panel}
|
||||
tabLabel="Location"
|
||||
tabLinkProps={{ name: 'EditListingLocationPage' }}
|
||||
tabLinkProps={tabLink(LOCATION)}
|
||||
selected={selectedTab === LOCATION}
|
||||
disabled={!stepsStatus[LOCATION]}
|
||||
listing={listing}
|
||||
|
|
@ -135,31 +131,34 @@ const EditListingWizard = props => {
|
|||
geolocation: origin,
|
||||
});
|
||||
|
||||
// Redirect to EditListingPricingPage
|
||||
const pathParams = tabParams(PRICING);
|
||||
// Redirect to pricing tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPricingPage', flattenedRoutes, {}, {})
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<EditListingPricingPanel
|
||||
className={css.panel}
|
||||
tabLabel="Pricing"
|
||||
tabLinkProps={{ name: 'EditListingPricingPage' }}
|
||||
tabLinkProps={tabLink(PRICING)}
|
||||
selected={selectedTab === PRICING}
|
||||
disabled={!stepsStatus[PRICING]}
|
||||
listing={listing}
|
||||
onSubmit={values => {
|
||||
onUpdateListingDraft(values);
|
||||
// Redirect to EditListingPhotosPage
|
||||
|
||||
const pathParams = tabParams(PHOTOS);
|
||||
// Redirect to photos tab
|
||||
history.push(
|
||||
createResourceLocatorString('EditListingPhotosPage', flattenedRoutes, {}, {})
|
||||
createResourceLocatorString('EditListingPage', flattenedRoutes, pathParams, {})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<EditListingPhotosPanel
|
||||
className={css.panel}
|
||||
tabLabel="Photos"
|
||||
tabLinkProps={{ name: 'EditListingPhotosPage' }}
|
||||
tabLinkProps={tabLink(PHOTOS)}
|
||||
selected={selectedTab === PHOTOS}
|
||||
disabled={!stepsStatus[PHOTOS]}
|
||||
errors={errors}
|
||||
|
|
@ -192,6 +191,12 @@ const { array, arrayOf, bool, func, object, oneOf, shape, string } = PropTypes;
|
|||
|
||||
EditListingWizard.propTypes = {
|
||||
className: string,
|
||||
params: shape({
|
||||
id: string.isRequired,
|
||||
slug: string.isRequired,
|
||||
type: oneOf(['new', 'edit']).isRequired,
|
||||
tab: oneOf(STEPS).isRequired,
|
||||
}).isRequired,
|
||||
errors: shape({
|
||||
createListingsError: object,
|
||||
showListingsError: object,
|
||||
|
|
@ -221,7 +226,6 @@ EditListingWizard.propTypes = {
|
|||
onUpdateImageOrder: func.isRequired,
|
||||
onUpdateListingDraft: func.isRequired,
|
||||
rootClassName: string,
|
||||
selectedTab: oneOf(STEPS).isRequired,
|
||||
currentUser: propTypes.currentUser,
|
||||
onManageDisableScrolling: func.isRequired,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -262,10 +262,7 @@ export class CheckoutPageComponent extends Component {
|
|||
<div className={css.detailsHeadings}>
|
||||
<h2 className={css.detailsTitle}>{listingTitle}</h2>
|
||||
<p className={css.detailsSubtitle}>
|
||||
<FormattedMessage
|
||||
id="CheckoutPage.hostedBy"
|
||||
values={{ name: authorFirstName }}
|
||||
/>
|
||||
<FormattedMessage id="CheckoutPage.hostedBy" values={{ name: authorFirstName }} />
|
||||
</p>
|
||||
</div>
|
||||
<h3 className={css.bookingBreakdownTitle}>
|
||||
|
|
|
|||
|
|
@ -69,13 +69,13 @@ export const EditListingPageComponent = props => {
|
|||
page,
|
||||
params,
|
||||
scrollingDisabled,
|
||||
tab,
|
||||
type,
|
||||
} = props;
|
||||
|
||||
const { id, type } = params;
|
||||
|
||||
const isNew = type === 'new';
|
||||
const hasIdParam = params && params.id;
|
||||
const id = page.submittedListingId || (hasIdParam ? new types.UUID(params.id) : null);
|
||||
const currentListing = getListing(id);
|
||||
const listingId = page.submittedListingId || (id ? new types.UUID(id) : null);
|
||||
const currentListing = getListing(listingId);
|
||||
|
||||
const shouldRedirect = page.submittedListingId && currentListing;
|
||||
const showForm = isNew || currentListing;
|
||||
|
|
@ -83,8 +83,8 @@ export const EditListingPageComponent = props => {
|
|||
if (shouldRedirect) {
|
||||
// If page has already listingId (after submit) and current listings exist
|
||||
// redirect to listing page
|
||||
const slug = currentListing ? createSlug(currentListing.attributes.title) : null;
|
||||
return <NamedRedirect name="ListingPage" params={{ id: id.uuid, slug }} />;
|
||||
const listingSlug = currentListing ? createSlug(currentListing.attributes.title) : null;
|
||||
return <NamedRedirect name="ListingPage" params={{ id: listingId.uuid, slug: listingSlug }} />;
|
||||
} else if (showForm) {
|
||||
const { createListingsError = null, showListingsError = null, uploadImageError = null } = page;
|
||||
const errors = { createListingsError, showListingsError, uploadImageError };
|
||||
|
|
@ -120,6 +120,7 @@ export const EditListingPageComponent = props => {
|
|||
/>
|
||||
<EditListingWizard
|
||||
className={css.wizard}
|
||||
params={params}
|
||||
disabled={disableForm}
|
||||
errors={errors}
|
||||
fetchInProgress={fetchInProgress}
|
||||
|
|
@ -133,7 +134,6 @@ export const EditListingPageComponent = props => {
|
|||
onPayoutDetailsSubmit={onPayoutDetailsSubmit}
|
||||
onImageUpload={onImageUpload}
|
||||
onUpdateImageOrder={onUpdateImageOrder}
|
||||
selectedTab={tab}
|
||||
currentUser={currentUser}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
/>
|
||||
|
|
@ -160,11 +160,9 @@ EditListingPageComponent.defaultProps = {
|
|||
listingDraft: null,
|
||||
logoutError: null,
|
||||
notificationCount: 0,
|
||||
params: null,
|
||||
type: 'edit',
|
||||
};
|
||||
|
||||
const { arrayOf, bool, func, instanceOf, number, object, shape, string } = PropTypes;
|
||||
const { arrayOf, bool, func, instanceOf, number, object, shape, string, oneOf } = PropTypes;
|
||||
|
||||
EditListingPageComponent.propTypes = {
|
||||
authInfoError: instanceOf(Error),
|
||||
|
|
@ -187,12 +185,12 @@ EditListingPageComponent.propTypes = {
|
|||
onUpdateListingDraft: func.isRequired,
|
||||
page: object.isRequired,
|
||||
params: shape({
|
||||
id: string,
|
||||
slug: string,
|
||||
}),
|
||||
id: string.isRequired,
|
||||
slug: string.isRequired,
|
||||
type: oneOf(['new', 'edit']).isRequired,
|
||||
tab: string.isRequired,
|
||||
}).isRequired,
|
||||
scrollingDisabled: bool.isRequired,
|
||||
tab: string.isRequired,
|
||||
type: string,
|
||||
|
||||
/* from withRouter */
|
||||
history: shape({
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ describe('EditListingPageComponent', () => {
|
|||
const getListing = () => null;
|
||||
const tree = renderShallow(
|
||||
<EditListingPageComponent
|
||||
params={{ id: 'id', slug: 'slug', type: 'new', tab: 'description' }}
|
||||
currentUserHasListings={false}
|
||||
isAuthenticated={false}
|
||||
authInProgress={false}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,14 @@ exports[`EditListingPageComponent matches snapshot 1`] = `
|
|||
onPayoutDetailsSubmit={[Function]}
|
||||
onUpdateImageOrder={[Function]}
|
||||
onUpdateListingDraft={[Function]}
|
||||
rootClassName={null}
|
||||
selectedTab="description" />
|
||||
params={
|
||||
Object {
|
||||
"id": "id",
|
||||
"slug": "slug",
|
||||
"tab": "description",
|
||||
"type": "new",
|
||||
}
|
||||
}
|
||||
rootClassName={null} />
|
||||
</withRouter(PageLayout)>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ import {
|
|||
EmailVerificationPage,
|
||||
} from './containers';
|
||||
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Nil_UUID
|
||||
const draftId = '00000000-0000-0000-0000-000000000000';
|
||||
const draftSlug = 'draft';
|
||||
|
||||
const RedirectToLandingPage = () => <NamedRedirect name="LandingPage" />;
|
||||
|
||||
const routesConfiguration = [
|
||||
|
|
@ -90,42 +94,19 @@ const routesConfiguration = [
|
|||
path: '/l/new',
|
||||
exact: true,
|
||||
name: 'NewListingPage',
|
||||
component: props => <EditListingPage {...props} type={'new'} tab={'description'} />,
|
||||
component: () => (
|
||||
<NamedRedirect
|
||||
name="EditListingPage"
|
||||
params={{ slug: draftSlug, id: draftId, type: 'new', tab: 'description' }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
path: '/l/new-description', // TODO should be l/:slug/:id/description
|
||||
exact: true,
|
||||
name: 'EditListingDescriptionPage',
|
||||
component: props => <EditListingPage {...props} type={'new'} tab={'description'} />,
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
path: '/l/new-location', // TODO should be l/:slug/:id/location
|
||||
exact: true,
|
||||
name: 'EditListingLocationPage',
|
||||
component: props => <EditListingPage {...props} type={'new'} tab={'location'} />,
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
path: '/l/new-pricing', // TODO should be l/:slug/:id/pricing
|
||||
exact: true,
|
||||
name: 'EditListingPricingPage',
|
||||
component: props => <EditListingPage {...props} type={'new'} tab={'pricing'} />,
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
path: '/l/new-photos', // TODO should be l/:slug/:id/photos
|
||||
exact: true,
|
||||
name: 'EditListingPhotosPage',
|
||||
component: props => <EditListingPage {...props} type={'new'} tab={'photos'} />,
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
path: '/l/:slug/:id/edit',
|
||||
path: '/l/:slug/:id/:type/:tab',
|
||||
exact: true,
|
||||
name: 'EditListingPage',
|
||||
component: props => <EditListingPage {...props} type={'edit'} />,
|
||||
component: props => <EditListingPage {...props} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue