mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 13:06:03 +10:00
Add reviews to ProfilePage
This commit is contained in:
parent
a7125173c5
commit
893427a78e
7 changed files with 367 additions and 122 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import TabNavHorizontal from './TabNavHorizontal';
|
||||
import { TYPE_BUTTON, TYPE_LINK } from './TabNavHorizontal';
|
||||
import { TAB_TYPE_BUTTON, TAB_TYPE_LINK } from './TabNavHorizontal';
|
||||
|
||||
const selfLinkProps = {
|
||||
name: 'StyleguideComponent',
|
||||
|
|
@ -13,7 +13,7 @@ export const LinkTabs = {
|
|||
{ text: 'Normal', linkProps: selfLinkProps },
|
||||
{ text: 'Selected', linkProps: selfLinkProps, selected: true },
|
||||
],
|
||||
type: TYPE_LINK,
|
||||
type: TAB_TYPE_LINK,
|
||||
},
|
||||
group: 'navigation',
|
||||
};
|
||||
|
|
@ -24,7 +24,7 @@ export const ButtonTabs = {
|
|||
component: TabNavHorizontal,
|
||||
props: {
|
||||
tabs: [{ text: 'Normal', onClick: noop }, { text: 'Selected', onClick: noop, selected: true }],
|
||||
type: TYPE_BUTTON,
|
||||
type: TAB_TYPE_BUTTON,
|
||||
},
|
||||
group: 'navigation',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import css from './TabNavHorizontal.css';
|
|||
export const LIGHT_SKIN = 'light';
|
||||
export const DARK_SKIN = 'dark';
|
||||
|
||||
export const TYPE_LINK = 'link';
|
||||
export const TYPE_BUTTON = 'button';
|
||||
export const TAB_TYPE_LINK = 'link';
|
||||
export const TAB_TYPE_BUTTON = 'button';
|
||||
|
||||
const { arrayOf, bool, func, node, object, oneOf, string } = PropTypes;
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ LinkTab.propTypes = {
|
|||
const TabNavHorizontal = props => {
|
||||
const { className, rootClassName, tabRootClassName, tabs, skin, type } = props;
|
||||
const isDark = skin === DARK_SKIN;
|
||||
const isButton = type === TYPE_BUTTON;
|
||||
const isButton = type === TAB_TYPE_BUTTON;
|
||||
const classes = classNames(rootClassName || css.root, { [css.darkSkin]: isDark }, className);
|
||||
const tabClasses = tabRootClassName || css.tab;
|
||||
return (
|
||||
|
|
@ -128,7 +128,7 @@ TabNavHorizontal.defaultProps = {
|
|||
tabRootClassName: null,
|
||||
tabClassName: null,
|
||||
skin: LIGHT_SKIN,
|
||||
type: TYPE_LINK,
|
||||
type: TAB_TYPE_LINK,
|
||||
};
|
||||
|
||||
TabNavHorizontal.propTypes = {
|
||||
|
|
@ -137,7 +137,7 @@ TabNavHorizontal.propTypes = {
|
|||
tabRootClassName: string,
|
||||
tabs: arrayOf(object).isRequired,
|
||||
skin: oneOf([LIGHT_SKIN, DARK_SKIN]),
|
||||
type: oneOf([TYPE_LINK, TYPE_BUTTON]),
|
||||
type: oneOf([TAB_TYPE_LINK, TAB_TYPE_BUTTON]),
|
||||
};
|
||||
|
||||
export default TabNavHorizontal;
|
||||
|
|
|
|||
|
|
@ -99,7 +99,14 @@
|
|||
}
|
||||
|
||||
.listingsContainer {
|
||||
@apply --clearfix;
|
||||
border-top: 1px solid var(--matterColorNegative);
|
||||
border-bottom: 1px solid var(--matterColorNegative);
|
||||
padding-bottom: 23px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
padding-bottom: 58px;
|
||||
}
|
||||
}
|
||||
|
||||
.listingsTitle {
|
||||
|
|
@ -158,3 +165,33 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobileReviews {
|
||||
@media (--viewportMedium) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mobileReviewsTitle {
|
||||
@apply --marketplaceH3FontStyles;
|
||||
color: var(--matterColorAnti);
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.desktopReviews {
|
||||
display: none;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
display: block;
|
||||
margin-top: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.desktopReviewsTitle {
|
||||
@apply --marketplaceH3FontStyles;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.desktopReviewsTabNav {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
||||
import { compose } from 'redux';
|
||||
|
|
@ -20,7 +20,10 @@ import {
|
|||
AvatarLarge,
|
||||
NamedLink,
|
||||
ListingCard,
|
||||
Reviews,
|
||||
TabNavHorizontal,
|
||||
} from '../../components';
|
||||
import { TAB_TYPE_BUTTON } from '../../components/TabNavHorizontal/TabNavHorizontal';
|
||||
import { TopbarContainer } from '../../containers';
|
||||
import { loadData } from './ProfilePage.duck';
|
||||
import config from '../../config';
|
||||
|
|
@ -29,127 +32,241 @@ import css from './ProfilePage.css';
|
|||
|
||||
const { UUID } = types;
|
||||
|
||||
export const ProfilePageComponent = props => {
|
||||
const {
|
||||
scrollingDisabled,
|
||||
currentUser,
|
||||
user,
|
||||
userShowError,
|
||||
queryListingsError,
|
||||
listings,
|
||||
intl,
|
||||
} = props;
|
||||
const ensuredCurrentUser = ensureCurrentUser(currentUser);
|
||||
const profileUser = ensureUser(user);
|
||||
const isCurrentUser =
|
||||
ensuredCurrentUser.id && profileUser.id && ensuredCurrentUser.id.uuid === profileUser.id.uuid;
|
||||
const displayName = profileUser.attributes.profile.displayName;
|
||||
const bio = profileUser.attributes.profile.bio;
|
||||
const hasBio = !!bio;
|
||||
const hasListings = listings.length > 0;
|
||||
export class ProfilePageComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const editLinkMobile = isCurrentUser ? (
|
||||
<NamedLink className={css.editLinkMobile} name="ProfileSettingsPage">
|
||||
<FormattedMessage id="ProfilePage.editProfileLinkMobile" />
|
||||
</NamedLink>
|
||||
) : null;
|
||||
const editLinkDesktop = isCurrentUser ? (
|
||||
<NamedLink className={css.editLinkDesktop} name="ProfileSettingsPage">
|
||||
<FormattedMessage id="ProfilePage.editProfileLinkDesktop" />
|
||||
</NamedLink>
|
||||
) : null;
|
||||
this.state = {
|
||||
reviews: propTypes.REVIEW_TYPE_OF_PROVIDER,
|
||||
};
|
||||
|
||||
const asideContent = (
|
||||
<div className={css.asideContent}>
|
||||
<AvatarLarge className={css.avatar} user={user} disableProfileLink />
|
||||
<h1 className={css.mobileHeading}>
|
||||
{displayName ? (
|
||||
<FormattedMessage id="ProfilePage.mobileHeading" values={{ name: displayName }} />
|
||||
) : null}
|
||||
</h1>
|
||||
{editLinkMobile}
|
||||
{editLinkDesktop}
|
||||
</div>
|
||||
);
|
||||
|
||||
const listingsContainerClasses = classNames(css.listingsContainer, {
|
||||
[css.withBioMissingAbove]: !hasBio,
|
||||
});
|
||||
|
||||
const mainContent = (
|
||||
<div>
|
||||
<h1 className={css.desktopHeading}>
|
||||
<FormattedMessage id="ProfilePage.desktopHeading" values={{ name: displayName }} />
|
||||
</h1>
|
||||
{hasBio ? <p className={css.bio}>{bio}</p> : null}
|
||||
{hasListings ? (
|
||||
<div className={listingsContainerClasses}>
|
||||
<h2 className={css.listingsTitle}>
|
||||
<FormattedMessage id="ProfilePage.listingsTitle" values={{ count: listings.length }} />
|
||||
</h2>
|
||||
<ul className={css.listings}>
|
||||
{listings.map(l => (
|
||||
<li className={css.listing} key={l.id.uuid}>
|
||||
<ListingCard listing={l} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
let content;
|
||||
|
||||
if (userShowError || queryListingsError) {
|
||||
content = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="ProfilePage.loadingDataFailed" />
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
content = mainContent;
|
||||
this.showOfProviderReviews = this.showOfProviderReviews.bind(this);
|
||||
this.showOfCustomerReviews = this.showOfCustomerReviews.bind(this);
|
||||
}
|
||||
|
||||
const schemaTitle = intl.formatMessage(
|
||||
{
|
||||
id: 'ProfilePage.schemaTitle',
|
||||
},
|
||||
{
|
||||
name: displayName,
|
||||
siteTitle: config.siteTitle,
|
||||
}
|
||||
);
|
||||
showOfProviderReviews() {
|
||||
this.setState({
|
||||
reviews: propTypes.REVIEW_TYPE_OF_PROVIDER,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
scrollingDisabled={scrollingDisabled}
|
||||
title={schemaTitle}
|
||||
schema={{
|
||||
'@context': 'http://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
name: schemaTitle,
|
||||
}}
|
||||
>
|
||||
<LayoutSideNavigation>
|
||||
<LayoutWrapperTopbar>
|
||||
<TopbarContainer currentPage="ProfilePage" />
|
||||
</LayoutWrapperTopbar>
|
||||
<LayoutWrapperSideNav className={css.aside}>{asideContent}</LayoutWrapperSideNav>
|
||||
<LayoutWrapperMain>{content}</LayoutWrapperMain>
|
||||
<LayoutWrapperFooter>
|
||||
<Footer />
|
||||
</LayoutWrapperFooter>
|
||||
</LayoutSideNavigation>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
showOfCustomerReviews() {
|
||||
this.setState({
|
||||
reviews: propTypes.REVIEW_TYPE_OF_CUSTOMER,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
scrollingDisabled,
|
||||
currentUser,
|
||||
user,
|
||||
userShowError,
|
||||
queryListingsError,
|
||||
listings,
|
||||
reviews,
|
||||
queryReviewsError,
|
||||
intl,
|
||||
} = this.props;
|
||||
const ensuredCurrentUser = ensureCurrentUser(currentUser);
|
||||
const profileUser = ensureUser(user);
|
||||
const isCurrentUser =
|
||||
ensuredCurrentUser.id && profileUser.id && ensuredCurrentUser.id.uuid === profileUser.id.uuid;
|
||||
const displayName = profileUser.attributes.profile.displayName;
|
||||
const bio = profileUser.attributes.profile.bio;
|
||||
const hasBio = !!bio;
|
||||
const hasListings = listings.length > 0;
|
||||
|
||||
const editLinkMobile = isCurrentUser ? (
|
||||
<NamedLink className={css.editLinkMobile} name="ProfileSettingsPage">
|
||||
<FormattedMessage id="ProfilePage.editProfileLinkMobile" />
|
||||
</NamedLink>
|
||||
) : null;
|
||||
const editLinkDesktop = isCurrentUser ? (
|
||||
<NamedLink className={css.editLinkDesktop} name="ProfileSettingsPage">
|
||||
<FormattedMessage id="ProfilePage.editProfileLinkDesktop" />
|
||||
</NamedLink>
|
||||
) : null;
|
||||
|
||||
const asideContent = (
|
||||
<div className={css.asideContent}>
|
||||
<AvatarLarge className={css.avatar} user={user} disableProfileLink />
|
||||
<h1 className={css.mobileHeading}>
|
||||
{displayName ? (
|
||||
<FormattedMessage id="ProfilePage.mobileHeading" values={{ name: displayName }} />
|
||||
) : null}
|
||||
</h1>
|
||||
{editLinkMobile}
|
||||
{editLinkDesktop}
|
||||
</div>
|
||||
);
|
||||
|
||||
const listingsContainerClasses = classNames(css.listingsContainer, {
|
||||
[css.withBioMissingAbove]: !hasBio,
|
||||
});
|
||||
|
||||
const reviewsError = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="ProfilePage.loadingReviewsFailed" />
|
||||
</p>
|
||||
);
|
||||
|
||||
const reviewsOfProvider = reviews.filter(
|
||||
r => r.attributes.type === propTypes.REVIEW_TYPE_OF_PROVIDER
|
||||
);
|
||||
|
||||
const reviewsOfCustomer = reviews.filter(
|
||||
r => r.attributes.type === propTypes.REVIEW_TYPE_OF_CUSTOMER
|
||||
);
|
||||
|
||||
const mobileReviews = (
|
||||
<div className={css.mobileReviews}>
|
||||
<h2 className={css.mobileReviewsTitle}>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfProviderTitle"
|
||||
values={{ count: reviewsOfProvider.length }}
|
||||
/>
|
||||
</h2>
|
||||
{queryReviewsError ? reviewsError : null}
|
||||
<Reviews reviews={reviewsOfProvider} />
|
||||
<h2 className={css.mobileReviewsTitle}>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfCustomerTitle"
|
||||
values={{ count: reviewsOfCustomer.length }}
|
||||
/>
|
||||
</h2>
|
||||
{queryReviewsError ? reviewsError : null}
|
||||
<Reviews reviews={reviewsOfCustomer} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const desktopReviewTabs = [
|
||||
{
|
||||
text: (
|
||||
<h3 className={css.desktopReviewsTitle}>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfProviderTitle"
|
||||
values={{ count: reviewsOfProvider.length }}
|
||||
/>
|
||||
</h3>
|
||||
),
|
||||
selected: this.state.reviews === propTypes.REVIEW_TYPE_OF_PROVIDER,
|
||||
onClick: this.showOfProviderReviews,
|
||||
},
|
||||
{
|
||||
text: (
|
||||
<h3 className={css.desktopReviewsTitle}>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfCustomerTitle"
|
||||
values={{ count: reviewsOfCustomer.length }}
|
||||
/>
|
||||
</h3>
|
||||
),
|
||||
selected: this.state.reviews === propTypes.REVIEW_TYPE_OF_CUSTOMER,
|
||||
onClick: this.showOfCustomerReviews,
|
||||
},
|
||||
];
|
||||
|
||||
const desktopReviews = (
|
||||
<div className={css.desktopReviews}>
|
||||
<TabNavHorizontal
|
||||
className={css.desktopReviewsTabNav}
|
||||
tabs={desktopReviewTabs}
|
||||
type={TAB_TYPE_BUTTON}
|
||||
/>
|
||||
|
||||
{queryReviewsError ? reviewsError : null}
|
||||
|
||||
{this.state.reviews === propTypes.REVIEW_TYPE_OF_PROVIDER ? (
|
||||
<Reviews reviews={reviewsOfProvider} />
|
||||
) : (
|
||||
<Reviews reviews={reviewsOfCustomer} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const mainContent = (
|
||||
<div>
|
||||
<h1 className={css.desktopHeading}>
|
||||
<FormattedMessage id="ProfilePage.desktopHeading" values={{ name: displayName }} />
|
||||
</h1>
|
||||
{hasBio ? <p className={css.bio}>{bio}</p> : null}
|
||||
{hasListings ? (
|
||||
<div className={listingsContainerClasses}>
|
||||
<h2 className={css.listingsTitle}>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.listingsTitle"
|
||||
values={{ count: listings.length }}
|
||||
/>
|
||||
</h2>
|
||||
<ul className={css.listings}>
|
||||
{listings.map(l => (
|
||||
<li className={css.listing} key={l.id.uuid}>
|
||||
<ListingCard listing={l} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
{mobileReviews}
|
||||
{desktopReviews}
|
||||
</div>
|
||||
);
|
||||
|
||||
let content;
|
||||
|
||||
if (userShowError || queryListingsError) {
|
||||
content = (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="ProfilePage.loadingDataFailed" />
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
content = mainContent;
|
||||
}
|
||||
|
||||
const schemaTitle = intl.formatMessage(
|
||||
{
|
||||
id: 'ProfilePage.schemaTitle',
|
||||
},
|
||||
{
|
||||
name: displayName,
|
||||
siteTitle: config.siteTitle,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Page
|
||||
scrollingDisabled={scrollingDisabled}
|
||||
title={schemaTitle}
|
||||
schema={{
|
||||
'@context': 'http://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
name: schemaTitle,
|
||||
}}
|
||||
>
|
||||
<LayoutSideNavigation>
|
||||
<LayoutWrapperTopbar>
|
||||
<TopbarContainer currentPage="ProfilePage" />
|
||||
</LayoutWrapperTopbar>
|
||||
<LayoutWrapperSideNav className={css.aside}>{asideContent}</LayoutWrapperSideNav>
|
||||
<LayoutWrapperMain>{content}</LayoutWrapperMain>
|
||||
<LayoutWrapperFooter>
|
||||
<Footer />
|
||||
</LayoutWrapperFooter>
|
||||
</LayoutSideNavigation>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProfilePageComponent.defaultProps = {
|
||||
currentUser: null,
|
||||
user: null,
|
||||
userShowError: null,
|
||||
queryListingsError: null,
|
||||
reviews: [],
|
||||
queryReviewsError: null,
|
||||
};
|
||||
|
||||
const { bool, arrayOf } = PropTypes;
|
||||
|
|
@ -161,6 +278,8 @@ ProfilePageComponent.propTypes = {
|
|||
userShowError: propTypes.error,
|
||||
queryListingsError: propTypes.error,
|
||||
listings: arrayOf(propTypes.listing).isRequired,
|
||||
reviews: arrayOf(propTypes.review),
|
||||
queryReviewsError: propTypes.error,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
|
|
@ -168,7 +287,14 @@ ProfilePageComponent.propTypes = {
|
|||
|
||||
const mapStateToProps = state => {
|
||||
const { currentUser } = state.user;
|
||||
const { userId, userShowError, queryListingsError, userListingRefs } = state.ProfilePage;
|
||||
const {
|
||||
userId,
|
||||
userShowError,
|
||||
queryListingsError,
|
||||
userListingRefs,
|
||||
reviews,
|
||||
queryReviewsError,
|
||||
} = state.ProfilePage;
|
||||
const userMatches = getMarketplaceEntities(state, [{ type: 'user', id: userId }]);
|
||||
const user = userMatches.length === 1 ? userMatches[0] : null;
|
||||
const listings = getMarketplaceEntities(state, userListingRefs);
|
||||
|
|
@ -179,6 +305,8 @@ const mapStateToProps = state => {
|
|||
userShowError,
|
||||
queryListingsError,
|
||||
listings,
|
||||
reviews,
|
||||
queryReviewsError,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,79 @@ exports[`ProfilePage matches snapshot 1`] = `
|
|||
}
|
||||
/>
|
||||
</h1>
|
||||
<div>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfProviderTitle"
|
||||
values={
|
||||
Object {
|
||||
"count": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</h2>
|
||||
<InjectIntl(ReviewsComponent)
|
||||
reviews={Array []}
|
||||
/>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfCustomerTitle"
|
||||
values={
|
||||
Object {
|
||||
"count": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</h2>
|
||||
<InjectIntl(ReviewsComponent)
|
||||
reviews={Array []}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TabNavHorizontal
|
||||
className={null}
|
||||
rootClassName={null}
|
||||
skin="light"
|
||||
tabClassName={null}
|
||||
tabRootClassName={null}
|
||||
tabs={
|
||||
Array [
|
||||
Object {
|
||||
"onClick": [Function],
|
||||
"selected": true,
|
||||
"text": <h3>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfProviderTitle"
|
||||
values={
|
||||
Object {
|
||||
"count": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</h3>,
|
||||
},
|
||||
Object {
|
||||
"onClick": [Function],
|
||||
"selected": false,
|
||||
"text": <h3>
|
||||
<FormattedMessage
|
||||
id="ProfilePage.reviewsOfCustomerTitle"
|
||||
values={
|
||||
Object {
|
||||
"count": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</h3>,
|
||||
},
|
||||
]
|
||||
}
|
||||
type="button"
|
||||
/>
|
||||
<InjectIntl(ReviewsComponent)
|
||||
reviews={Array []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutWrapperMain>
|
||||
<LayoutWrapperFooter
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@
|
|||
"ListingPage.ownClosedListing": "Your listing has been closed and can't be booked.",
|
||||
"ListingPage.ownListing": "This is your own listing.",
|
||||
"ListingPage.perNight": "per night",
|
||||
"ListingPage.reviewsError": "Fetching reviews failed.",
|
||||
"ListingPage.reviewsError": "Loading reviews failed.",
|
||||
"ListingPage.reviewsHeading": "Reviews ({count})",
|
||||
"ListingPage.schemaTitle": "{title} - {price} | {siteTitle}",
|
||||
"ListingPage.viewImagesButton": "View photos ({count})",
|
||||
|
|
@ -417,7 +417,10 @@
|
|||
"ProfilePage.editProfileLinkMobile": "Edit",
|
||||
"ProfilePage.listingsTitle": "My saunas ({count})",
|
||||
"ProfilePage.loadingDataFailed": "Whoops, something went wrong. Please try again.",
|
||||
"ProfilePage.loadingReviewsFailed": "Loading reviews failed.",
|
||||
"ProfilePage.mobileHeading": "{name}",
|
||||
"ProfilePage.reviewsOfProviderTitle": "Reviews from Guests ({count})",
|
||||
"ProfilePage.reviewsOfCustomerTitle": "Reviews from Hosts ({count})",
|
||||
"ProfilePage.schemaTitle": "{name} | {siteTitle}",
|
||||
"ProfileSettingsForm.addYourProfilePicture": "+ Add your profile picture…",
|
||||
"ProfileSettingsForm.addYourProfilePictureMobile": "+ Add",
|
||||
|
|
|
|||
|
|
@ -261,6 +261,10 @@ export const isReviewTransition = transition => {
|
|||
// Possible amount of stars in a review
|
||||
export const REVIEW_RATINGS = [1, 2, 3, 4, 5];
|
||||
|
||||
// Review types: review of a provider and of a customer
|
||||
export const REVIEW_TYPE_OF_PROVIDER = 'ofProvider';
|
||||
export const REVIEW_TYPE_OF_CUSTOMER = 'ofCustomer';
|
||||
|
||||
// A review on a user
|
||||
export const review = shape({
|
||||
id: uuid.isRequired,
|
||||
|
|
@ -269,7 +273,7 @@ export const review = shape({
|
|||
content: string,
|
||||
rating: oneOf(REVIEW_RATINGS),
|
||||
state: string.isRequired,
|
||||
type: string.isRequired,
|
||||
type: oneOf([REVIEW_TYPE_OF_PROVIDER, REVIEW_TYPE_OF_CUSTOMER]).isRequired,
|
||||
}).isRequired,
|
||||
author: user,
|
||||
subject: user,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue