Merge pull request #408 from sharetribe/handle-deleted-listing

Handle deleted listing
This commit is contained in:
Kimmo Puputti 2017-09-18 12:36:03 +03:00 committed by GitHub
commit 18e2d79109
10 changed files with 133 additions and 72 deletions

View file

@ -39,7 +39,7 @@ describe('Application', () => {
'/s/listings': 'Search page: listings',
'/s/filters': 'Search page: filters',
'/s/map': 'Search page: map',
'/l/listing-title-slug/1234': 'Loading listing data',
'/l/listing-title-slug/1234': 'Loading listing',
'/u/1234': 'Profile page with display name: 1234',
'/login': 'Login',
'/signup': 'Sign up',

View file

@ -8,6 +8,15 @@
}
}
.loadingText {
margin: 24px;
}
.errorText {
color: var(--failColor);
margin: 24px;
}
.threeToTwoWrapper {
/* Layout */
display: block;

View file

@ -52,6 +52,5 @@ export const showListing = listingId =>
})
.catch(e => {
dispatch(showListingError(e));
throw e;
});
};

View file

@ -22,6 +22,7 @@ import {
ResponsiveImage,
Topbar,
NamedLink,
NamedRedirect,
Modal,
ImageCarousel,
} from '../../components';
@ -175,12 +176,57 @@ export class ListingPageComponent extends Component {
title = '',
} = currentListing.attributes;
if (!currentListing.id && showListingError) {
const noDataMsg = { id: 'ListingPage.noListingData' };
return <PageLayout title={intl.formatMessage(noDataMsg)} />;
const topbar = (
<Topbar
isAuthenticated={isAuthenticated}
authInProgress={authInProgress}
currentUser={currentUser}
currentUserHasListings={currentUserHasListings}
currentUserHasOrders={currentUserHasOrders}
notificationCount={notificationCount}
history={history}
location={location}
onLogout={onLogout}
onManageDisableScrolling={onManageDisableScrolling}
onResendVerificationEmail={onResendVerificationEmail}
sendVerificationEmailInProgress={sendVerificationEmailInProgress}
sendVerificationEmailError={sendVerificationEmailError}
/>
);
const loadingTitle = intl.formatMessage({
id: 'ListingPage.loadingListingTitle',
});
const errorTitle = intl.formatMessage({
id: 'ListingPage.errorLoadingListingTitle',
});
if (showListingError && showListingError.status === 404) {
// 404 listing not found
return <NamedRedirect name="NotFoundPage" />;
} else if (showListingError) {
// Other error in fetching listing
return (
<PageLayout title={errorTitle}>
{topbar}
<p className={css.errorText}>
<FormattedMessage id="ListingPage.errorLoadingListingMessage" />
</p>
</PageLayout>
);
} else if (!currentListing.id) {
const loadingPageMsg = { id: 'ListingPage.loadingListingData' };
return <PageLayout title={intl.formatMessage(loadingPageMsg)} />;
// Still loading the listing
return (
<PageLayout title={loadingTitle}>
{topbar}
<p className={css.loadingText}>
<FormattedMessage id="ListingPage.loadingListingMessage" />
</p>
</PageLayout>
);
}
const hasImages = currentListing.images && currentListing.images.length > 0;
@ -291,21 +337,7 @@ export class ListingPageComponent extends Component {
title={`${title} ${formattedPrice}`}
scrollingDisabled={scrollingDisabled}
>
<Topbar
isAuthenticated={isAuthenticated}
authInProgress={authInProgress}
currentUser={currentUser}
currentUserHasListings={currentUserHasListings}
currentUserHasOrders={currentUserHasOrders}
notificationCount={notificationCount}
history={history}
location={location}
onLogout={onLogout}
onManageDisableScrolling={onManageDisableScrolling}
onResendVerificationEmail={onResendVerificationEmail}
sendVerificationEmailInProgress={sendVerificationEmailInProgress}
sendVerificationEmailError={sendVerificationEmailError}
/>
{topbar}
<div className={listingClasses}>
<div className={css.threeToTwoWrapper}>
<div className={css.aspectWrapper} onClick={handleViewPhotosClick}>

View file

@ -72,22 +72,16 @@ describe('ListingPage', () => {
// Calling sdk.listings.show is expected to fail now
return showListing(id)(dispatch, null, sdk).then(
() => {
throw new Error('sdk.listings.show was supposed to fail!');
},
e => {
expect(e).toEqual(error);
expect(show.mock.calls).toEqual([
[{ id, include: ['author', 'author.profileImage', 'images'] }],
]);
expect(dispatch.mock.calls).toEqual([
[showListingRequest(id)],
[expect.anything()], // fetchCurrentUser() call
[showListingError(e)],
]);
}
);
return showListing(id)(dispatch, null, sdk).then(data => {
expect(show.mock.calls).toEqual([
[{ id, include: ['author', 'author.profileImage', 'images'] }],
]);
expect(dispatch.mock.calls).toEqual([
[showListingRequest(id)],
[expect.anything()], // fetchCurrentUser() call
[showListingError(error)],
]);
});
});
});

View file

@ -0,0 +1,3 @@
.root {
padding: 0 24px;
}

View file

@ -1,4 +1,5 @@
import React, { Component, PropTypes } from 'react';
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
@ -6,7 +7,9 @@ import * as propTypes from '../../util/propTypes';
import { sendVerificationEmail } from '../../ducks/user.duck';
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
import { NamedLink, PageLayout, Topbar } from '../../components';
import { PageLayout, Topbar } from '../../components';
import css from './NotFoundPage.css';
export class NotFoundPageComponent extends Component {
componentWillMount() {
@ -34,10 +37,15 @@ export class NotFoundPageComponent extends Component {
sendVerificationEmailInProgress,
sendVerificationEmailError,
onResendVerificationEmail,
intl,
} = this.props;
const title = intl.formatMessage({
id: 'NotFoundPage.title',
});
return (
<PageLayout authInfoError={authInfoError} logoutError={logoutError} title="Page not found">
<PageLayout authInfoError={authInfoError} logoutError={logoutError} title={title}>
<Topbar
authInProgress={authInProgress}
currentUser={currentUser}
@ -53,7 +61,11 @@ export class NotFoundPageComponent extends Component {
sendVerificationEmailInProgress={sendVerificationEmailInProgress}
sendVerificationEmailError={sendVerificationEmailError}
/>
<NamedLink name="LandingPage">Home</NamedLink>
<div className={css.root}>
<h1>
<FormattedMessage id="NotFoundPage.heading" />
</h1>
</div>
</PageLayout>
);
}
@ -89,6 +101,9 @@ NotFoundPageComponent.propTypes = {
// context object from StaticRouter, injected by the withRouter wrapper
staticContext: object,
// from injectIntl
intl: intlShape.isRequired,
// from withRouter
history: shape({
push: func.isRequired,
@ -130,7 +145,7 @@ const mapDispatchToProps = dispatch => ({
onResendVerificationEmail: () => dispatch(sendVerificationEmail()),
});
const NotFoundPage = compose(connect(mapStateToProps, mapDispatchToProps), withRouter)(
const NotFoundPage = compose(connect(mapStateToProps, mapDispatchToProps), withRouter, injectIntl)(
NotFoundPageComponent
);

View file

@ -1,5 +1,6 @@
import React from 'react';
import { renderShallow } from '../../util/test-helpers';
import { fakeIntl } from '../../util/test-data';
import { NotFoundPageComponent } from './NotFoundPage';
const noop = () => null;
@ -19,6 +20,7 @@ describe('NotFoundPageComponent', () => {
onManageDisableScrolling={noop}
sendVerificationEmailInProgress={false}
onResendVerificationEmail={noop}
intl={fakeIntl}
/>
);
expect(tree).toMatchSnapshot();

View file

@ -2,7 +2,7 @@ exports[`NotFoundPageComponent matches snapshot 1`] = `
<withRouter(PageLayout)
authInfoError={null}
logoutError={null}
title="Page not found">
title="NotFoundPage.title">
<Topbar
authInProgress={false}
currentUser={null}
@ -25,9 +25,12 @@ exports[`NotFoundPageComponent matches snapshot 1`] = `
onResendVerificationEmail={[Function]}
sendVerificationEmailError={null}
sendVerificationEmailInProgress={false} />
<withFlattenedRoutes(withRouter(NamedLink))
name="LandingPage">
Home
</withFlattenedRoutes(withRouter(NamedLink))>
<div>
<h1>
<FormattedMessage
id="NotFoundPage.heading"
values={Object {}} />
</h1>
</div>
</withRouter(PageLayout)>
`;

View file

@ -157,10 +157,12 @@
"ListingPage.ctaButtonMessage": "Request to book",
"ListingPage.descriptionTitle": "About this sauna",
"ListingPage.editListing": "Edit listing",
"ListingPage.errorLoadingListingMessage": "Could not load listing. Please try again.",
"ListingPage.errorLoadingListingTitle": "Error in loading listing",
"ListingPage.hostedBy": "Hosted by {name}",
"ListingPage.loadingListingData": "Loading listing data",
"ListingPage.loadingListingMessage": "Loading listing…",
"ListingPage.loadingListingTitle": "Loading listing…",
"ListingPage.locationTitle": "Location",
"ListingPage.noListingData": "Could not find listing data",
"ListingPage.ownClosedListing": "Your listing has been closed and can't be booked.",
"ListingPage.ownListing": "This is your own listing.",
"ListingPage.perNight": "per night",
@ -169,8 +171,8 @@
"LoginForm.emailPlaceholder": "john.doe@example.com",
"LoginForm.emailRequired": "This field is required",
"LoginForm.forgotPassword": "Forgot your password?",
"LoginForm.logIn": "Log in",
"LoginForm.forgotPasswordHelp": "No problem.",
"LoginForm.logIn": "Log in",
"LoginForm.passwordLabel": "Password",
"LoginForm.passwordPlaceholder": "Enter your password…",
"LoginForm.passwordRequired": "This field is required",
@ -193,6 +195,8 @@
"MapPriceMarker.unsupportedPrice": "({currency})",
"Modal.close": "CLOSE",
"Modal.closeModal": "Close modal",
"NotFoundPage.heading": "Oops, could not find the page you were looking for.",
"NotFoundPage.title": "Page not found",
"OrderDetailsPanel.bannedUserDisplayName": "Banned user",
"OrderDetailsPanel.bookingBreakdownTitle": "Booking breakdown",
"OrderDetailsPanel.deletedListingOrderTitle": "a listing",
@ -219,6 +223,28 @@
"PaginationLinks.next": "Next page",
"PaginationLinks.previous": "Previous page",
"PaginationLinks.toPage": "Go to page {page}",
"PasswordRecoveryForm.emailLabel": "Email",
"PasswordRecoveryForm.emailNotFound": "Hmm. We didn't find an account with that email. Please double-check your email and try again.",
"PasswordRecoveryForm.emailPlaceholder": "john.doe@example.com",
"PasswordRecoveryForm.emailRequired": "This field is required",
"PasswordRecoveryForm.loginLinkInfo": "Suddenly remembered your password? {loginLink}",
"PasswordRecoveryForm.loginLinkText": "Go log in.",
"PasswordRecoveryForm.sendInstructions": "Send instructions",
"PasswordRecoveryPage.actionFailedMessage": "Something went wrong. Please refresh the page and try again.",
"PasswordRecoveryPage.actionFailedTitle": "Whoops!",
"PasswordRecoveryPage.emailNotVerifiedContactAdmin": "To resolve the issue, please contact Saunatime administrators.",
"PasswordRecoveryPage.emailNotVerifiedMessage": "Unfortunately, we cannot recover your password, because your email {initialEmailText} hasn't been verified.",
"PasswordRecoveryPage.emailNotVerifiedTitle": "We have bad news",
"PasswordRecoveryPage.emailSubmittedMessage": "We have sent the instructions for resetting your password to {submittedEmailText}.",
"PasswordRecoveryPage.emailSubmittedTitle": "Check your inbox",
"PasswordRecoveryPage.fixEmailInfo": "Whoops, typo in your email? {fixEmailLink}",
"PasswordRecoveryPage.fixEmailLinkText": "Fix it.",
"PasswordRecoveryPage.forgotPasswordMessage": "No worries! Please enter the email address you used when signing up and we'll send you instructions on how to set a new password.",
"PasswordRecoveryPage.forgotPasswordTitle": "Forgot your password?",
"PasswordRecoveryPage.resendEmailInfo": "Didn't get the email? {resendEmailLink}",
"PasswordRecoveryPage.resendEmailLinkText": "Send another email.",
"PasswordRecoveryPage.resendingEmailInfo": "Resending instructions...",
"PasswordRecoveryPage.title": "Request a new password",
"PasswordResetForm.passwordLabel": "Your new password",
"PasswordResetForm.passwordPlaceholder": "Enter your new password...",
"PasswordResetForm.passwordRequired": "This field is required",
@ -231,28 +257,6 @@
"PasswordResetPage.passwordChangedHelpText": "Your password has been changed successfully.",
"PasswordResetPage.resetFailed": "Reset failed. Please try again.",
"PasswordResetPage.title": "Reset password",
"PasswordRecoveryForm.emailLabel": "Email",
"PasswordRecoveryForm.emailNotFound": "Hmm. We didn't find an account with that email. Please double-check your email and try again.",
"PasswordRecoveryForm.emailPlaceholder": "john.doe@example.com",
"PasswordRecoveryForm.emailRequired": "This field is required",
"PasswordRecoveryForm.loginLinkText": "Go log in.",
"PasswordRecoveryForm.loginLinkInfo": "Suddenly remembered your password? {loginLink}",
"PasswordRecoveryForm.sendInstructions": "Send instructions",
"PasswordRecoveryPage.actionFailedTitle": "Whoops!",
"PasswordRecoveryPage.actionFailedMessage": "Something went wrong. Please refresh the page and try again.",
"PasswordRecoveryPage.emailNotVerifiedTitle": "We have bad news",
"PasswordRecoveryPage.emailNotVerifiedMessage": "Unfortunately, we cannot recover your password, because your email {initialEmailText} hasn't been verified.",
"PasswordRecoveryPage.emailNotVerifiedContactAdmin": "To resolve the issue, please contact Saunatime administrators.",
"PasswordRecoveryPage.emailSubmittedTitle": "Check your inbox",
"PasswordRecoveryPage.emailSubmittedMessage": "We have sent the instructions for resetting your password to {submittedEmailText}.",
"PasswordRecoveryPage.fixEmailLinkText": "Fix it.",
"PasswordRecoveryPage.fixEmailInfo": "Whoops, typo in your email? {fixEmailLink}",
"PasswordRecoveryPage.forgotPasswordTitle": "Forgot your password?",
"PasswordRecoveryPage.forgotPasswordMessage": "No worries! Please enter the email address you used when signing up and we'll send you instructions on how to set a new password.",
"PasswordRecoveryPage.resendEmailLinkText": "Send another email.",
"PasswordRecoveryPage.resendEmailInfo": "Didn't get the email? {resendEmailLink}",
"PasswordRecoveryPage.resendingEmailInfo": "Resending instructions...",
"PasswordRecoveryPage.title": "Request a new password",
"PayoutDetailsForm.addressTitle": "Address",
"PayoutDetailsForm.bankDetails": "Bank details",
"PayoutDetailsForm.birthdayDatePlaceholder": "dd",