Use displayName/abbreviatedName of the user

The API changed from having firstName/lastName in the user object
profile. The displayName/abbreviatedName should be used instead.
This commit is contained in:
Kimmo Puputti 2017-08-21 10:18:49 +03:00
parent 2f6bb884af
commit 852d8250f2
14 changed files with 78 additions and 83 deletions

View file

@ -1,18 +1,16 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import * as propTypes from '../../util/propTypes';
import css from './Avatar.css';
const Avatar = props => {
const { className, firstName, lastName, rootClassName } = props;
const { rootClassName, className, user } = props;
const classes = classNames(rootClassName || css.root, className);
const authorName = lastName ? `${firstName} ${lastName}` : firstName;
const initials = lastName ? firstName.charAt(0) + lastName.charAt(0) : firstName.charAt(0);
const { displayName, abbreviatedName } = user.profile;
const placeHolderAvatar = (
<div className={classes} title={authorName}>
<span className={css.initials}>{initials}</span>
<div className={classes} title={displayName}>
<span className={css.initials}>{abbreviatedName}</span>
</div>
);
@ -23,14 +21,12 @@ const { string } = PropTypes;
Avatar.defaultProps = {
className: null,
lastName: null,
rootClassName: null,
};
Avatar.propTypes = {
className: string,
firstName: string.isRequired,
lastName: string,
user: propTypes.user.isRequired,
rootClassName: string,
};

View file

@ -37,7 +37,7 @@ export const ListingCardComponent = props => {
const { title = '', price } = currentListing.attributes;
const slug = createSlug(title);
const author = ensureUser(listing.author);
const authorName = `${author.attributes.profile.firstName} ${author.attributes.profile.lastName}`;
const authorName = author.attributes.profile.displayName;
const firstImage = currentListing.images && currentListing.images.length > 0
? currentListing.images[0]
: null;

View file

@ -132,9 +132,8 @@ const OrderDetailsPanel = props => {
const currentCustomer = ensureUser(currentTransaction.customer);
const providerProfile = currentProvider.attributes.profile;
const authorFirstName = providerProfile.firstName;
const authorLastName = providerProfile.lastName;
const customerName = currentCustomer.attributes.profile.firstName;
const authorDisplayName = providerProfile.displayName;
const customerDisplayName = currentCustomer.attributes.profile.displayName;
const transactionState = currentTransaction.attributes.state;
const lastTransitionedAt = currentTransaction.attributes.lastTransitionedAt;
const lastTransition = currentTransaction.attributes.lastTransitione;
@ -154,11 +153,11 @@ const OrderDetailsPanel = props => {
const listingTitle = currentListing.attributes.title;
const bookingInfo = breakdown(currentTransaction);
const title = orderTitle(transactionState, listingLink, customerName, lastTransition);
const title = orderTitle(transactionState, listingLink, customerDisplayName, lastTransition);
const message = orderMessage(
transactionState,
listingLink,
authorFirstName,
authorDisplayName,
lastTransitionedAt,
lastTransition
);
@ -185,7 +184,7 @@ const OrderDetailsPanel = props => {
/>
</div>
<div className={classNames(css.avatarWrapper, css.avatarMobile)}>
<AvatarMedium firstName={authorFirstName} lastName={authorLastName} />
<AvatarMedium user={currentProvider} />
</div>
<div className={css.orderInfo}>
<h1 className={css.title}>{title}</h1>
@ -214,14 +213,14 @@ const OrderDetailsPanel = props => {
/>
</div>
<div className={css.avatarWrapper}>
<AvatarMedium firstName={authorFirstName} lastName={authorLastName} />
<AvatarMedium user={currentProvider} />
</div>
<div className={css.breakdownHeadings}>
<h2 className={css.breakdownTitle}>{listingTitle}</h2>
<p className={css.breakdownSubtitle}>
<FormattedMessage
id="OrderDetailsPanel.hostedBy"
values={{ name: authorFirstName }}
values={{ name: authorDisplayName }}
/>
</p>
</div>

View file

@ -125,8 +125,7 @@ const SaleDetailsPanel = props => {
const currentListing = ensureListing(currentTransaction.listing);
const currentCustomer = ensureUser(currentTransaction.customer);
const customerFirstName = currentCustomer.attributes.profile.firstName;
const customerLastName = currentCustomer.attributes.profile.lastName;
const customerDisplayName = currentCustomer.attributes.profile.displayName;
const transactionState = currentTransaction.attributes.state;
const lastTransitionedAt = currentTransaction.attributes.lastTransitionedAt;
const lastTransition = currentTransaction.attributes.lastTransition;
@ -150,10 +149,10 @@ const SaleDetailsPanel = props => {
const bookingInfo = breakdown(currentTransaction, totalMessage);
const title = saleTitle(transactionState, listingLink, customerFirstName, lastTransition);
const title = saleTitle(transactionState, listingLink, customerDisplayName, lastTransition);
const message = saleMessage(
transactionState,
customerFirstName,
customerDisplayName,
lastTransitionedAt,
lastTransition
);
@ -202,11 +201,11 @@ const SaleDetailsPanel = props => {
/>
</div>
<div className={css.avatarWrapperMobile}>
<AvatarMedium firstName={customerFirstName} lastName={customerLastName} />
<AvatarMedium user={currentCustomer} />
</div>
<div className={css.info}>
<div className={css.avatarWrapperDesktop}>
<AvatarMedium firstName={customerFirstName} lastName={customerLastName} />
<AvatarMedium user={currentCustomer} />
</div>
<h1 className={css.title}>{title}</h1>
<p className={css.message}>{message}</p>

View file

@ -7,7 +7,7 @@ import { Button, Modal, NamedLink, TopbarDesktop, TopbarMobileMenu } from '../..
import { TopbarSearchForm } from '../../containers';
import { withFlattenedRoutes, withViewport } from '../../util/contextHelpers';
import { parse, stringify } from '../../util/urlHelpers';
import { ensureUser } from '../../util/data';
import { ensureCurrentUser } from '../../util/data';
import { createResourceLocatorString, pathByRouteName } from '../../util/routes';
import * as propTypes from '../../util/propTypes';
@ -96,8 +96,7 @@ class TopbarComponent extends Component {
location,
onManageDisableScrolling,
} = this.props;
const me = ensureUser(currentUser);
const profile = me.attributes.profile;
const me = ensureCurrentUser(currentUser);
const { mobilemenu, mobilesearch, address, origin, bounds, country } = parse(location.search, {
latlng: ['origin'],
@ -114,8 +113,7 @@ class TopbarComponent extends Component {
<TopbarMobileMenu
isAuthenticated={isAuthenticated}
currentUserHasListings={currentUserHasListings}
firstName={profile.firstName}
lastName={profile.lastName}
user={me}
onLogout={this.handleLogout}
notificationCount={notificationCount}
/>
@ -157,8 +155,7 @@ class TopbarComponent extends Component {
<div className={css.desktop}>
<TopbarDesktop
currentUserHasListings={currentUserHasListings}
firstName={profile.firstName}
lastName={profile.lastName}
user={me}
initialSearchFormValues={initialSearchFormValues}
intl={intl}
isAuthenticated={isAuthenticated}

View file

@ -1,6 +1,7 @@
import React, { PropTypes } from 'react';
import { FormattedMessage, intlShape } from 'react-intl';
import classNames from 'classnames';
import * as propTypes from '../../util/propTypes';
import {
Avatar,
InlineTextButton,
@ -18,8 +19,7 @@ import css from './TopbarDesktop.css';
const TopbarDesktop = props => {
const {
className,
firstName,
lastName,
user,
rootClassName,
currentUserHasListings,
notificationCount,
@ -61,7 +61,7 @@ const TopbarDesktop = props => {
const profileMenu = isAuthenticated
? <Menu>
<MenuLabel className={css.profileMenuLabel} isOpenClassName={css.profileMenuIsOpen}>
<Avatar className={css.avatar} firstName={firstName} lastName={lastName} />
<Avatar className={css.avatar} user={user} />
</MenuLabel>
<MenuContent className={css.profileMenuContent}>
<MenuItem key="logout">
@ -111,8 +111,6 @@ const TopbarDesktop = props => {
const { bool, func, object, number, string } = PropTypes;
TopbarDesktop.defaultProps = {
firstName: '',
lastName: '',
notificationCount: 0,
className: null,
rootClassName: null,
@ -124,8 +122,7 @@ TopbarDesktop.propTypes = {
currentUserHasListings: bool.isRequired,
isAuthenticated: bool.isRequired,
onLogout: func.isRequired,
firstName: string,
lastName: string,
user: propTypes.currentUser.isRequired,
notificationCount: number,
rootClassName: string,
onSearchSubmit: func.isRequired,

View file

@ -4,6 +4,7 @@
*/
import React, { PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import * as propTypes from '../../util/propTypes';
import { AvatarLarge, InlineTextButton, NamedLink, NotificationBadge } from '../../components';
import css from './TopbarMobileMenu.css';
@ -12,8 +13,7 @@ const TopbarMobileMenu = props => {
const {
isAuthenticated,
currentUserHasListings,
firstName,
lastName,
user,
notificationCount,
onLogout,
} = props;
@ -69,12 +69,14 @@ const TopbarMobileMenu = props => {
</NamedLink>
);
const displayName = user.attributes.profile.displayName;
return (
<div className={css.root}>
<AvatarLarge className={css.avatar} firstName={firstName} lastName={lastName} />
<AvatarLarge className={css.avatar} user={user} />
<div className={css.content}>
<span className={css.greeting}>
<FormattedMessage id="TopbarMobileMenu.greeting" values={{ firstName }} />
<FormattedMessage id="TopbarMobileMenu.greeting" values={{ displayName }} />
</span>
<InlineTextButton className={css.logoutButton} onClick={onLogout}>
<FormattedMessage id="TopbarMobileMenu.logoutLink" />
@ -90,15 +92,14 @@ const TopbarMobileMenu = props => {
);
};
TopbarMobileMenu.defaultProps = { firstName: '', lastName: '', notificationCount: 0 };
TopbarMobileMenu.defaultProps = { notificationCount: 0 };
const { bool, func, number, string } = PropTypes;
const { bool, func, number } = PropTypes;
TopbarMobileMenu.propTypes = {
isAuthenticated: bool.isRequired,
currentUserHasListings: bool.isRequired,
firstName: string,
lastName: string,
user: propTypes.currentUser.isRequired,
notificationCount: number,
onLogout: func.isRequired,
};

View file

@ -124,9 +124,7 @@ export class CheckoutPageComponent extends Component {
const { bookingStart, bookingEnd } = pageData.bookingDates || {};
const currentListing = ensureListing(pageData.listing);
const currentAuthor = ensureUser(currentListing.author);
const authorProfile = currentAuthor.attributes.profile;
const authorFirstName = authorProfile.firstName;
const authorLastName = authorProfile.lastName;
const authorDisplayName = currentAuthor.attributes.profile.displayName;
const isOwnListing = currentListing.id &&
currentUser &&
@ -207,14 +205,17 @@ export class CheckoutPageComponent extends Component {
/>
</div>
<div className={classNames(css.avatarWrapper, css.avatarMobile)}>
<AvatarMedium firstName={authorFirstName} lastName={authorLastName} />
<AvatarMedium user={currentAuthor} />
</div>
<div className={css.bookListingContainer}>
<div className={css.heading}>
<h1 className={css.title}>{title}</h1>
<div className={css.author}>
<span className={css.authorName}>
<FormattedMessage id="ListingPage.hostedBy" values={{ name: authorFirstName }} />
<FormattedMessage
id="ListingPage.hostedBy"
values={{ name: authorDisplayName }}
/>
</span>
</div>
</div>
@ -257,12 +258,12 @@ export class CheckoutPageComponent extends Component {
/>
</div>
<div className={css.avatarWrapper}>
<AvatarMedium firstName={authorFirstName} lastName={authorLastName} />
<AvatarMedium user={currentAuthor} />
</div>
<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: authorDisplayName }} />
</p>
</div>
<h3 className={css.bookingBreakdownTitle}>

View file

@ -26,17 +26,6 @@ import css from './InboxPage.css';
const { arrayOf, bool, func, instanceOf, number, object, oneOf, shape, string } = PropTypes;
const formatUser = user => {
const profile = user && user.attributes && user.attributes.profile
? user.attributes.profile
: null;
return {
name: `${profile.firstName} ${profile.lastName}`,
firstName: profile.firstName,
lastName: profile.lastName,
};
};
const formatDate = (intl, date) => {
return {
short: intl.formatDate(date, {
@ -103,7 +92,8 @@ export const InboxItem = props => {
const { customer, provider, booking } = tx;
const isOrder = type === 'order';
const otherUser = formatUser(isOrder ? provider : customer);
const otherUser = isOrder ? provider : customer;
const otherUserDisplayName = otherUser.attributes.profile.displayName;
const stateData = txState(intl, tx, isOrder);
const isSaleNotification = !isOrder && tx.attributes.state === propTypes.TX_STATE_PREAUTHORIZED;
@ -121,14 +111,14 @@ export const InboxItem = props => {
params={{ id: tx.id.uuid }}
>
<div className={css.itemAvatar}>
<Avatar firstName={otherUser.firstName} lastName={otherUser.lastName} />
<Avatar user={otherUser} />
</div>
<div className={css.rowNotificationDot}>
{rowNotificationDot}
</div>
<div className={css.itemInfo}>
<div className={classNames(css.itemUsername, stateData.nameClassName)}>
{otherUser.name}
{otherUserDisplayName}
</div>
<div className={classNames(css.bookingInfo, stateData.bookingClassName)}>
{bookingStart.short} - {bookingEnd.short}

View file

@ -132,9 +132,8 @@ export class ListingPageComponent extends Component {
currentListing.author.id.uuid === currentUser.id.uuid;
const showBookButton = !isOwnListing;
const { firstName: authorFirstName, lastName: authorLastName } = authorAvailable
? ensureUser(currentListing.author).attributes.profile
: { firstName: '', lastName: '' };
const currentAuthor = ensureUser(authorAvailable ? currentListing.author : {});
const currentAuthorDisplayName = currentAuthor.attributes.profile.displayName;
// TODO location address is currently serialized inside address field (API will change later)
// Content is something like { locationAddress: 'Street, Province, Country', building: 'A 42' };
@ -219,11 +218,7 @@ export class ListingPageComponent extends Component {
<div className={css.contentContainer}>
<div className={css.avatarWrapper}>
<Avatar
rootClassName={css.avatar}
firstName={authorFirstName}
lastName={authorLastName}
/>
<Avatar rootClassName={css.avatar} user={currentAuthor} />
</div>
<div className={css.mainContent}>
@ -242,7 +237,7 @@ export class ListingPageComponent extends Component {
<span className={css.authorName}>
<FormattedMessage
id="ListingPage.hostedBy"
values={{ name: authorFirstName }}
values={{ name: currentAuthorDisplayName }}
/>
</span>
</div>
@ -276,7 +271,7 @@ export class ListingPageComponent extends Component {
<span className={css.authorName}>
<FormattedMessage
id="ListingPage.hostedBy"
values={{ name: authorFirstName }}
values={{ name: currentAuthorDisplayName }}
/>
</span>
</div>

View file

@ -317,7 +317,7 @@
"TopbarDesktop.logo": "Logo",
"TopbarDesktop.logout": "Log out",
"TopbarDesktop.signup": "Sign up",
"TopbarMobileMenu.greeting": "Hello {firstName}",
"TopbarMobileMenu.greeting": "Hello {displayName}",
"TopbarMobileMenu.inboxLink": "Inbox",
"TopbarMobileMenu.loginLink": "Log in",
"TopbarMobileMenu.logoutLink": "Log out",

View file

@ -153,3 +153,13 @@ export const ensureUser = user => {
const empty = { id: null, type: 'user', attributes: { profile: {} } };
return { ...empty, ...user };
};
/**
* Create shell objects to ensure that attributes etc. exists.
*
* @param {Object} current user entity object, which is to be ensured agains null values
*/
export const ensureCurrentUser = user => {
const empty = { id: null, type: 'current-user', attributes: { profile: {} } };
return { ...empty, ...user };
};

View file

@ -70,6 +70,8 @@ export const currentUser = shape({
profile: shape({
firstName: string.isRequired,
lastName: string.isRequired,
displayName: string.isRequired,
abbreviatedName: string.isRequired,
}).isRequired,
stripeConnected: bool.isRequired,
}),
@ -81,8 +83,8 @@ export const user = shape({
type: value('user').isRequired,
attributes: shape({
profile: shape({
firstName: string.isRequired,
lastName: string.isRequired,
displayName: string.isRequired,
abbreviatedName: string.isRequired,
}).isRequired,
}),
});

View file

@ -19,7 +19,10 @@ export const createUser = id => ({
id: new UUID(id),
type: 'user',
attributes: {
profile: { firstName: `${id} first name`, lastName: `${id} last name` },
profile: {
displayName: `${id} display name`,
abbreviatedName: `${id} abbreviated name`,
},
},
});
@ -29,7 +32,12 @@ export const createCurrentUser = id => ({
type: 'current-user',
attributes: {
email: `${id}@example.com`,
profile: { firstName: `${id} first name`, lastName: `${id} last name` },
profile: {
firstName: `${id} first name`,
lastName: `${id} last name`,
displayName: `${id} display name`,
abbreviatedName: `${id} abbreviated name`,
},
stripeConnected: true,
},
});