mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 12:43:11 +10:00
416 lines
12 KiB
JavaScript
416 lines
12 KiB
JavaScript
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { compose } from 'redux';
|
|
import { connect } from 'react-redux';
|
|
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
import moment from 'moment';
|
|
import classNames from 'classnames';
|
|
import {
|
|
LINE_ITEM_DAY,
|
|
LINE_ITEM_UNITS,
|
|
txHasFirstReview,
|
|
txIsAccepted,
|
|
txIsCanceled,
|
|
txIsCompleted,
|
|
txIsDeclinedOrExpired,
|
|
txIsEnquired,
|
|
txIsRequested,
|
|
txIsReviewed,
|
|
propTypes,
|
|
} from '../../util/types';
|
|
import { formatMoney } from '../../util/currency';
|
|
import { userDisplayName } from '../../util/data';
|
|
import { daysBetween } from '../../util/dates';
|
|
import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck';
|
|
import { isScrollingDisabled } from '../../ducks/UI.duck';
|
|
import {
|
|
Avatar,
|
|
NamedLink,
|
|
NotificationBadge,
|
|
Page,
|
|
PaginationLinks,
|
|
TabNav,
|
|
LayoutSideNavigation,
|
|
LayoutWrapperMain,
|
|
LayoutWrapperSideNav,
|
|
LayoutWrapperTopbar,
|
|
LayoutWrapperFooter,
|
|
Footer,
|
|
} from '../../components';
|
|
import { TopbarContainer, NotFoundPage } from '../../containers';
|
|
import config from '../../config';
|
|
|
|
import { loadData } from './InboxPage.duck';
|
|
import css from './InboxPage.css';
|
|
|
|
const { arrayOf, bool, number, oneOf, shape, string } = PropTypes;
|
|
|
|
const formatDate = (intl, date) => {
|
|
return {
|
|
short: intl.formatDate(date, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
}),
|
|
long: `${intl.formatDate(date)} ${intl.formatTime(date)}`,
|
|
};
|
|
};
|
|
|
|
// Translated name of the state of the given transaction
|
|
const txState = (intl, tx, isOrder) => {
|
|
if (txIsAccepted(tx)) {
|
|
return {
|
|
nameClassName: css.nameAccepted,
|
|
bookingClassName: css.bookingAccepted,
|
|
lastTransitionedAtClassName: css.lastTransitionedAtAccepted,
|
|
stateClassName: css.stateAccepted,
|
|
state: intl.formatMessage({
|
|
id: 'InboxPage.stateAccepted',
|
|
}),
|
|
};
|
|
} else if (txIsDeclinedOrExpired(tx)) {
|
|
return {
|
|
nameClassName: css.nameDeclined,
|
|
bookingClassName: css.bookingDeclined,
|
|
lastTransitionedAtClassName: css.lastTransitionedAtDeclined,
|
|
stateClassName: css.stateDeclined,
|
|
state: intl.formatMessage({
|
|
id: 'InboxPage.stateDeclined',
|
|
}),
|
|
};
|
|
} else if (txIsCanceled(tx)) {
|
|
return {
|
|
nameClassName: css.nameCanceled,
|
|
bookingClassName: css.bookingCanceled,
|
|
lastTransitionedAtClassName: css.lastTransitionedAtCanceled,
|
|
stateClassName: css.stateCanceled,
|
|
state: intl.formatMessage({
|
|
id: 'InboxPage.stateCanceled',
|
|
}),
|
|
};
|
|
} else if (txIsCompleted(tx) || txHasFirstReview(tx) || txIsReviewed(tx)) {
|
|
return {
|
|
nameClassName: css.nameDelivered,
|
|
bookingClassName: css.bookingDelivered,
|
|
lastTransitionedAtClassName: css.lastTransitionedAtDelivered,
|
|
stateClassName: css.stateDelivered,
|
|
state: intl.formatMessage({
|
|
id: 'InboxPage.stateDelivered',
|
|
}),
|
|
};
|
|
} else if (txIsEnquired(tx)) {
|
|
return {
|
|
nameClassName: isOrder ? css.nameEnquiredOrder : css.nameEnquired,
|
|
bookingClassName: css.bookingEnquired,
|
|
lastTransitionedAtClassName: css.lastTransitionedAtEnquired,
|
|
stateClassName: css.stateEnquired,
|
|
state: intl.formatMessage({
|
|
id: 'InboxPage.stateEnquiry',
|
|
}),
|
|
};
|
|
}
|
|
return {
|
|
nameClassName: isOrder ? css.nameRequested : css.namePending,
|
|
bookingClassName: isOrder ? css.bookingRequested : css.bookingPending,
|
|
lastTransitionedAtClassName: isOrder
|
|
? css.lastTransitionedAtRequested
|
|
: css.lastTransitionedAtPending,
|
|
stateClassName: isOrder ? css.stateRequested : css.statePending,
|
|
state: isOrder
|
|
? intl.formatMessage({
|
|
id: 'InboxPage.stateRequested',
|
|
})
|
|
: intl.formatMessage({
|
|
id: 'InboxPage.statePending',
|
|
}),
|
|
};
|
|
};
|
|
|
|
const bookingData = (unitType, tx, isOrder, intl) => {
|
|
const { start, end } = tx.booking.attributes;
|
|
const isDaily = unitType === LINE_ITEM_DAY;
|
|
const isUnits = unitType === LINE_ITEM_UNITS;
|
|
const isSingleDay = isDaily && daysBetween(start, end) === 1;
|
|
const bookingStart = formatDate(intl, start);
|
|
|
|
// Shift the exclusive API end date with daily bookings
|
|
const endDate =
|
|
isDaily || isUnits
|
|
? moment(end)
|
|
.subtract(1, 'days')
|
|
.toDate()
|
|
: end;
|
|
const bookingEnd = formatDate(intl, endDate);
|
|
const bookingPrice = isOrder ? tx.attributes.payinTotal : tx.attributes.payoutTotal;
|
|
const price = formatMoney(intl, bookingPrice);
|
|
return { bookingStart, bookingEnd, price, isSingleDay };
|
|
};
|
|
|
|
// Functional component as internal helper to print BookingInfo if that is needed
|
|
const BookingInfoMaybe = props => {
|
|
const { bookingClassName, isOrder, intl, tx, unitType } = props;
|
|
const isEnquiry = txIsEnquired(tx);
|
|
|
|
if (isEnquiry) {
|
|
return null;
|
|
}
|
|
|
|
const { bookingStart, bookingEnd, price, isSingleDay } = bookingData(unitType, tx, isOrder, intl);
|
|
const dateInfo = isSingleDay ? bookingStart.short : `${bookingStart.short} - ${bookingEnd.short}`;
|
|
|
|
return (
|
|
<div className={classNames(css.bookingInfo, bookingClassName)}>
|
|
{dateInfo}
|
|
<span className={css.itemPrice}>{price}</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
BookingInfoMaybe.propTypes = {
|
|
intl: intlShape.isRequired,
|
|
isOrder: bool.isRequired,
|
|
tx: propTypes.transaction.isRequired,
|
|
unitType: propTypes.bookingUnitType.isRequired,
|
|
};
|
|
|
|
export const InboxItem = props => {
|
|
const { unitType, type, tx, intl } = props;
|
|
const { customer, provider } = tx;
|
|
const isOrder = type === 'order';
|
|
|
|
const otherUser = isOrder ? provider : customer;
|
|
const bannedUserDisplayName = intl.formatMessage({
|
|
id: 'InboxPage.bannedUserDisplayName',
|
|
});
|
|
const isOtherUserBanned = otherUser.attributes.banned;
|
|
const otherUserDisplayName = userDisplayName(otherUser, bannedUserDisplayName);
|
|
|
|
const stateData = txState(intl, tx, isOrder);
|
|
const isSaleNotification = !isOrder && txIsRequested(tx);
|
|
const rowNotificationDot = isSaleNotification ? <div className={css.notificationDot} /> : null;
|
|
const lastTransitionedAt = formatDate(intl, tx.attributes.lastTransitionedAt);
|
|
|
|
const linkClasses = classNames(css.itemLink, {
|
|
[css.bannedUserLink]: isOtherUserBanned,
|
|
});
|
|
|
|
return (
|
|
<div className={css.item}>
|
|
<div className={css.itemAvatar}>
|
|
<Avatar user={otherUser} />
|
|
</div>
|
|
<NamedLink
|
|
className={linkClasses}
|
|
name={isOrder ? 'OrderDetailsPage' : 'SaleDetailsPage'}
|
|
params={{ id: tx.id.uuid }}
|
|
>
|
|
<div className={css.rowNotificationDot}>{rowNotificationDot}</div>
|
|
<div className={css.itemInfo}>
|
|
<div className={classNames(css.itemUsername, stateData.nameClassName)}>
|
|
{otherUserDisplayName}
|
|
</div>
|
|
<BookingInfoMaybe
|
|
bookingClassName={stateData.bookingClassName}
|
|
intl={intl}
|
|
isOrder={isOrder}
|
|
tx={tx}
|
|
unitType={unitType}
|
|
/>
|
|
</div>
|
|
<div className={css.itemState}>
|
|
<div className={classNames(css.stateName, stateData.stateClassName)}>
|
|
{stateData.state}
|
|
</div>
|
|
<div
|
|
className={classNames(css.lastTransitionedAt, stateData.lastTransitionedAtClassName)}
|
|
title={lastTransitionedAt.long}
|
|
>
|
|
{lastTransitionedAt.short}
|
|
</div>
|
|
</div>
|
|
</NamedLink>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
InboxItem.propTypes = {
|
|
unitType: propTypes.bookingUnitType.isRequired,
|
|
type: oneOf(['order', 'sale']).isRequired,
|
|
tx: propTypes.transaction.isRequired,
|
|
intl: intlShape.isRequired,
|
|
};
|
|
|
|
export const InboxPageComponent = props => {
|
|
const {
|
|
unitType,
|
|
currentUser,
|
|
fetchInProgress,
|
|
fetchOrdersOrSalesError,
|
|
intl,
|
|
pagination,
|
|
params,
|
|
providerNotificationCount,
|
|
scrollingDisabled,
|
|
transactions,
|
|
} = props;
|
|
const { tab } = params;
|
|
|
|
const validTab = tab === 'orders' || tab === 'sales';
|
|
if (!validTab) {
|
|
return <NotFoundPage />;
|
|
}
|
|
|
|
const isOrders = tab === 'orders';
|
|
|
|
const ordersTitle = intl.formatMessage({ id: 'InboxPage.ordersTitle' });
|
|
const salesTitle = intl.formatMessage({ id: 'InboxPage.salesTitle' });
|
|
const title = isOrders ? ordersTitle : salesTitle;
|
|
|
|
const toTxItem = tx => {
|
|
return (
|
|
<li key={tx.id.uuid} className={css.listItem}>
|
|
<InboxItem unitType={unitType} type={isOrders ? 'order' : 'sale'} tx={tx} intl={intl} />
|
|
</li>
|
|
);
|
|
};
|
|
|
|
const error = fetchOrdersOrSalesError ? (
|
|
<p className={css.error}>
|
|
<FormattedMessage id="InboxPage.fetchFailed" />
|
|
</p>
|
|
) : null;
|
|
|
|
const noResults =
|
|
!fetchInProgress && transactions.length === 0 && !fetchOrdersOrSalesError ? (
|
|
<li key="noResults" className={css.noResults}>
|
|
<FormattedMessage id={isOrders ? 'InboxPage.noOrdersFound' : 'InboxPage.noSalesFound'} />
|
|
</li>
|
|
) : null;
|
|
|
|
const hasOrderOrSaleTransactions = (tx, isOrdersTab, user) => {
|
|
return isOrdersTab
|
|
? user.id && tx && tx.length > 0 && tx[0].customer.id.uuid === user.id.uuid
|
|
: user.id && tx && tx.length > 0 && tx[0].provider.id.uuid === user.id.uuid;
|
|
};
|
|
const hasTransactions =
|
|
!fetchInProgress && hasOrderOrSaleTransactions(transactions, isOrders, currentUser);
|
|
const pagingLinks =
|
|
hasTransactions && pagination && pagination.totalPages > 1 ? (
|
|
<PaginationLinks
|
|
className={css.pagination}
|
|
pageName="InboxPage"
|
|
pagePathParams={params}
|
|
pagination={pagination}
|
|
/>
|
|
) : null;
|
|
|
|
const providerNotificationBadge =
|
|
providerNotificationCount > 0 ? <NotificationBadge count={providerNotificationCount} /> : null;
|
|
const tabs = [
|
|
{
|
|
text: (
|
|
<span>
|
|
<FormattedMessage id="InboxPage.ordersTabTitle" />
|
|
</span>
|
|
),
|
|
selected: isOrders,
|
|
linkProps: {
|
|
name: 'InboxPage',
|
|
params: { tab: 'orders' },
|
|
},
|
|
},
|
|
{
|
|
text: (
|
|
<span>
|
|
<FormattedMessage id="InboxPage.salesTabTitle" />
|
|
{providerNotificationBadge}
|
|
</span>
|
|
),
|
|
selected: !isOrders,
|
|
linkProps: {
|
|
name: 'InboxPage',
|
|
params: { tab: 'sales' },
|
|
},
|
|
},
|
|
];
|
|
const nav = <TabNav rootClassName={css.tabs} tabRootClassName={css.tab} tabs={tabs} />;
|
|
|
|
return (
|
|
<Page title={title} scrollingDisabled={scrollingDisabled}>
|
|
<LayoutSideNavigation>
|
|
<LayoutWrapperTopbar>
|
|
<TopbarContainer
|
|
className={css.topbar}
|
|
mobileRootClassName={css.mobileTopbar}
|
|
desktopClassName={css.desktopTopbar}
|
|
currentPage="InboxPage"
|
|
/>
|
|
</LayoutWrapperTopbar>
|
|
<LayoutWrapperSideNav className={css.navigation}>
|
|
<h1 className={css.title}>
|
|
<FormattedMessage id="InboxPage.title" />
|
|
</h1>
|
|
{nav}
|
|
</LayoutWrapperSideNav>
|
|
<LayoutWrapperMain>
|
|
{error}
|
|
<ul className={css.itemList}>
|
|
{!fetchInProgress ? transactions.map(toTxItem) : null}
|
|
{noResults}
|
|
</ul>
|
|
{pagingLinks}
|
|
</LayoutWrapperMain>
|
|
<LayoutWrapperFooter>
|
|
<Footer />
|
|
</LayoutWrapperFooter>
|
|
</LayoutSideNavigation>
|
|
</Page>
|
|
);
|
|
};
|
|
|
|
InboxPageComponent.defaultProps = {
|
|
unitType: config.bookingUnitType,
|
|
currentUser: null,
|
|
currentUserHasOrders: null,
|
|
fetchOrdersOrSalesError: null,
|
|
pagination: null,
|
|
providerNotificationCount: 0,
|
|
sendVerificationEmailError: null,
|
|
};
|
|
|
|
InboxPageComponent.propTypes = {
|
|
params: shape({
|
|
tab: string.isRequired,
|
|
}).isRequired,
|
|
|
|
unitType: propTypes.bookingUnitType,
|
|
currentUser: propTypes.currentUser,
|
|
fetchInProgress: bool.isRequired,
|
|
fetchOrdersOrSalesError: propTypes.error,
|
|
pagination: propTypes.pagination,
|
|
providerNotificationCount: number,
|
|
scrollingDisabled: bool.isRequired,
|
|
transactions: arrayOf(propTypes.transaction).isRequired,
|
|
|
|
// from injectIntl
|
|
intl: intlShape.isRequired,
|
|
};
|
|
|
|
const mapStateToProps = state => {
|
|
const { fetchInProgress, fetchOrdersOrSalesError, pagination, transactionRefs } = state.InboxPage;
|
|
const { currentUser, currentUserNotificationCount: providerNotificationCount } = state.user;
|
|
return {
|
|
currentUser,
|
|
fetchInProgress,
|
|
fetchOrdersOrSalesError,
|
|
pagination,
|
|
providerNotificationCount,
|
|
scrollingDisabled: isScrollingDisabled(state),
|
|
transactions: getMarketplaceEntities(state, transactionRefs),
|
|
};
|
|
};
|
|
|
|
const InboxPage = compose(connect(mapStateToProps), injectIntl)(InboxPageComponent);
|
|
|
|
InboxPage.loadData = loadData;
|
|
|
|
export default InboxPage;
|