mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-30 18:16:48 +10:00
SalePage: initial draft - this is pretty much the same as OrderPage
This commit is contained in:
parent
bbb6e90d25
commit
fbdb3b157f
20 changed files with 500 additions and 117 deletions
17
src/components/SaleDetailsPanel/SaleDetailsPanel.css
Normal file
17
src/components/SaleDetailsPanel/SaleDetailsPanel.css
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
.title,
|
||||
.receipt {
|
||||
margin: 1rem 1rem 2rem 1rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
margin: 1rem 1rem 2rem 1rem;
|
||||
}
|
||||
|
||||
.avatarWrapper {
|
||||
display: block;
|
||||
flex-basis: 44px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
76
src/components/SaleDetailsPanel/SaleDetailsPanel.js
Normal file
76
src/components/SaleDetailsPanel/SaleDetailsPanel.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { createSlug } from '../../util/urlHelpers';
|
||||
import { Avatar, BookingInfo, NamedLink } from '../../components';
|
||||
|
||||
import css from './SaleDetailsPanel.css';
|
||||
|
||||
const SaleDetailsPanel = props => {
|
||||
const { className, totalPrice, saleState, booking, listing, customer } = props;
|
||||
const { firstName, lastName } = customer.attributes.profile;
|
||||
const customerName = firstName ? `${firstName} ${lastName}` : '';
|
||||
|
||||
const listingLinkParams = { id: listing.id.uuid, slug: createSlug(listing.attributes.title) };
|
||||
const listingLink = (
|
||||
<NamedLink name="ListingPage" params={listingLinkParams}>
|
||||
{listing.attributes.title}
|
||||
</NamedLink>
|
||||
);
|
||||
|
||||
const title = saleState === propTypes.TX_STATE_PREAUTHORIZED
|
||||
? <FormattedMessage
|
||||
id="SaleDetailsPanel.listingTitle"
|
||||
values={{ customerName: customerName, title: listingLink }}
|
||||
/>
|
||||
: null;
|
||||
|
||||
const message = saleState === propTypes.TX_STATE_PREAUTHORIZED
|
||||
? <div className={css.message}>
|
||||
<div className={css.avatarWrapper}>
|
||||
<Avatar name={customerName} />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage id="SaleDetailsPanel.saleStatusMessage" values={{ customerName }} />
|
||||
</div>
|
||||
</div>
|
||||
: null;
|
||||
|
||||
// TODO We can't use price from listing, since that might have changed.
|
||||
// When API includes unit price and possible additional fees, we need to change this.
|
||||
const unitPrice = listing.attributes.price;
|
||||
|
||||
const bookingInfo = unitPrice
|
||||
? <BookingInfo
|
||||
className={css.receipt}
|
||||
bookingStart={booking.attributes.start}
|
||||
bookingEnd={booking.attributes.end}
|
||||
unitPrice={unitPrice}
|
||||
totalPrice={totalPrice}
|
||||
/>
|
||||
: <p className={css.error}>{'priceRequiredMessage'}</p>;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<h1 className={css.title}>{title}</h1>
|
||||
{message}
|
||||
{bookingInfo}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SaleDetailsPanel.defaultProps = { className: null };
|
||||
|
||||
const { instanceOf, string } = PropTypes;
|
||||
|
||||
SaleDetailsPanel.propTypes = {
|
||||
className: string,
|
||||
totalPrice: instanceOf(types.Money).isRequired,
|
||||
saleState: string.isRequired,
|
||||
booking: propTypes.booking.isRequired,
|
||||
listing: propTypes.listing.isRequired,
|
||||
customer: propTypes.user.isRequired,
|
||||
};
|
||||
|
||||
export default SaleDetailsPanel;
|
||||
25
src/components/SaleDetailsPanel/SaleDetailsPanel.test.js
Normal file
25
src/components/SaleDetailsPanel/SaleDetailsPanel.test.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import React from 'react';
|
||||
import { types } from '../../util/sdkLoader';
|
||||
import { createBooking, createListing, createUser } from '../../util/test-data';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import SaleDetailsPanel from './SaleDetailsPanel.js';
|
||||
|
||||
describe('SaleDetailsPanel', () => {
|
||||
it('matches snapshot', () => {
|
||||
const { Money } = types;
|
||||
const props = {
|
||||
commission: null,
|
||||
totalPrice: new Money(16500, 'USD'),
|
||||
saleState: 'state/preauthorized',
|
||||
booking: createBooking(
|
||||
'booking1',
|
||||
new Date(Date.UTC(2017, 5, 10)),
|
||||
new Date(Date.UTC(2017, 5, 13))
|
||||
),
|
||||
listing: createListing('listing1'),
|
||||
customer: createUser('customer1'),
|
||||
};
|
||||
const tree = renderShallow(<SaleDetailsPanel {...props} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
exports[`SaleDetailsPanel matches snapshot 1`] = `
|
||||
<div
|
||||
className={null}>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.listingTitle"
|
||||
values={
|
||||
Object {
|
||||
"customerName": "customer1 first name customer1 last name",
|
||||
"title": <withFlattenedRoutes(withRouter(NamedLink))
|
||||
name="ListingPage"
|
||||
params={
|
||||
Object {
|
||||
"id": "listing1",
|
||||
"slug": "listing1-title",
|
||||
}
|
||||
}>
|
||||
listing1 title
|
||||
</withFlattenedRoutes(withRouter(NamedLink))>,
|
||||
}
|
||||
} />
|
||||
</h1>
|
||||
<div>
|
||||
<div>
|
||||
<Avatar
|
||||
className={null}
|
||||
name="customer1 first name customer1 last name" />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="SaleDetailsPanel.saleStatusMessage"
|
||||
values={
|
||||
Object {
|
||||
"customerName": "customer1 first name customer1 last name",
|
||||
}
|
||||
} />
|
||||
</div>
|
||||
</div>
|
||||
<BookingInfo
|
||||
bookingEnd={2017-06-13T00:00:00.000Z}
|
||||
bookingStart={2017-06-10T00:00:00.000Z}
|
||||
totalPrice={
|
||||
Money {
|
||||
"amount": 16500,
|
||||
"currency": "USD",
|
||||
}
|
||||
}
|
||||
unitPrice={
|
||||
Money {
|
||||
"amount": 5500,
|
||||
"currency": "USD",
|
||||
}
|
||||
} />
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -26,6 +26,7 @@ import PageLayout from './PageLayout/PageLayout';
|
|||
import PaginationLinks from './PaginationLinks/PaginationLinks';
|
||||
import Promised from './Promised/Promised';
|
||||
import RoutesProvider from './RoutesProvider/RoutesProvider';
|
||||
import SaleDetailsPanel from './SaleDetailsPanel/SaleDetailsPanel';
|
||||
import SearchResultsPanel from './SearchResultsPanel/SearchResultsPanel';
|
||||
import StripeBankAccountToken from './StripeBankAccountToken/StripeBankAccountToken';
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ export {
|
|||
PaginationLinks,
|
||||
Promised,
|
||||
RoutesProvider,
|
||||
SaleDetailsPanel,
|
||||
SearchResultsPanel,
|
||||
StripeBankAccountToken,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export const fetchOrder = id =>
|
|||
dispatch(fetchOrderRequest());
|
||||
|
||||
return sdk.transactions
|
||||
.show({ id, include: ['provider', 'customer', 'listing', 'booking'] }, { expand: true })
|
||||
.show({ id, include: ['provider', 'listing', 'booking'] }, { expand: true })
|
||||
.then(response => {
|
||||
dispatch(addEntities(response));
|
||||
dispatch(fetchOrderSuccess(response));
|
||||
|
|
|
|||
|
|
@ -4,41 +4,12 @@ import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
|
|||
import { connect } from 'react-redux';
|
||||
import { OrderDetailsPanel, PageLayout } from '../../components';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { ensureBooking, ensureListing, ensureTransaction, ensureUser } from '../../util/data';
|
||||
import { getEntities } from '../../ducks/sdk.duck';
|
||||
import { loadData } from './OrderPage.duck';
|
||||
|
||||
import css from './OrderPage.css';
|
||||
|
||||
// Create shell objects to ensure that attributes etc. exists.
|
||||
// TODO: these could be moved to separate util file, if needed elsewhere
|
||||
const ensureTransaction = transaction => {
|
||||
const empty = {
|
||||
id: null,
|
||||
type: 'transaction',
|
||||
attributes: {},
|
||||
booking: {},
|
||||
listing: {},
|
||||
provider: {},
|
||||
};
|
||||
// assume own properties: id, type, attributes etc.
|
||||
return { ...empty, ...transaction };
|
||||
};
|
||||
|
||||
const ensureBooking = booking => {
|
||||
const empty = { id: null, type: 'booking', attributes: {} };
|
||||
return { ...empty, ...booking };
|
||||
};
|
||||
|
||||
const ensureListing = listing => {
|
||||
const empty = { id: null, type: 'listing', attributes: {}, images: [] };
|
||||
return { ...empty, ...listing };
|
||||
};
|
||||
|
||||
const ensureUser = user => {
|
||||
const empty = { id: null, type: 'user', attributes: { profile: {} } };
|
||||
return { ...empty, ...user };
|
||||
};
|
||||
|
||||
// OrderPage handles data loading
|
||||
// It show loading data text or OrderDetailsPanel (and later also another panel for messages).
|
||||
export const OrderPageComponent = props => {
|
||||
|
|
|
|||
15
src/containers/SalePage/SalePage.css
Normal file
15
src/containers/SalePage/SalePage.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
.title {
|
||||
margin: 1rem 1rem 2rem 1rem;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabContentVisible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.activeTab {
|
||||
font-weight: bold;
|
||||
}
|
||||
73
src/containers/SalePage/SalePage.duck.js
Normal file
73
src/containers/SalePage/SalePage.duck.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { types } from '../../util/sdkLoader';
|
||||
import { addEntities } from '../../ducks/sdk.duck';
|
||||
import { fetchCurrentUser } from '../../ducks/user.duck';
|
||||
|
||||
// ================ Action types ================ //
|
||||
|
||||
export const FETCH_SALE_REQUEST = 'app/InboxPage/FETCH_SALE_REQUEST';
|
||||
export const FETCH_SALE_SUCCESS = 'app/InboxPage/FETCH_SALE_SUCCESS';
|
||||
export const FETCH_SALE_ERROR = 'app/InboxPage/FETCH_SALE_ERROR';
|
||||
|
||||
// ================ Reducer ================ //
|
||||
|
||||
const initialState = {
|
||||
fetchInProgress: false,
|
||||
fetchSaleError: null,
|
||||
transactionRef: null,
|
||||
};
|
||||
|
||||
export default function checkoutPageReducer(state = initialState, action = {}) {
|
||||
const { type, payload } = action;
|
||||
switch (type) {
|
||||
case FETCH_SALE_REQUEST:
|
||||
return { ...state, fetchInProgress: true, fetchSaleError: null };
|
||||
case FETCH_SALE_SUCCESS: {
|
||||
const transactionRef = { id: payload.data.data.id, type: 'transaction' };
|
||||
return { ...state, fetchInProgress: false, transactionRef };
|
||||
}
|
||||
case FETCH_SALE_ERROR:
|
||||
console.error(payload); // eslint-disable-line
|
||||
return { ...state, fetchInProgress: false, fetchSaleError: payload };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
const fetchSaleRequest = () => ({ type: FETCH_SALE_REQUEST });
|
||||
const fetchSaleSuccess = response => ({ type: FETCH_SALE_SUCCESS, payload: response });
|
||||
const fetchSaleError = e => ({ type: FETCH_SALE_ERROR, error: true, payload: e });
|
||||
|
||||
// ================ Thunks ================ //
|
||||
|
||||
export const fetchSale = id =>
|
||||
(dispatch, getState, sdk) => {
|
||||
dispatch(fetchSaleRequest());
|
||||
|
||||
return sdk.transactions
|
||||
.show({ id, include: ['customer', 'listing', 'booking'] }, { expand: true })
|
||||
.then(response => {
|
||||
dispatch(addEntities(response));
|
||||
dispatch(fetchSaleSuccess(response));
|
||||
return response;
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch(fetchSaleError(e));
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
|
||||
// loadData is a collection of async calls that need to be made
|
||||
// before page has all the info it needs to render itself
|
||||
export const loadData = params =>
|
||||
dispatch => {
|
||||
const saleId = new types.UUID(params.id);
|
||||
|
||||
// Current user is needed to render Topbar
|
||||
dispatch(fetchCurrentUser());
|
||||
|
||||
// Sale (i.e. transaction entity in API, but from buyers perspective) contains sale details
|
||||
return dispatch(fetchSale(saleId));
|
||||
};
|
||||
71
src/containers/SalePage/SalePage.js
Normal file
71
src/containers/SalePage/SalePage.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { ensureBooking, ensureListing, ensureTransaction, ensureUser } from '../../util/data';
|
||||
import { SaleDetailsPanel, PageLayout } from '../../components';
|
||||
import { getEntities } from '../../ducks/sdk.duck';
|
||||
import { loadData } from './SalePage.duck';
|
||||
|
||||
import css from './SalePage.css';
|
||||
|
||||
// SalePage handles data loading
|
||||
// It show loading data text or SaleDetailsPanel (and later also another panel for messages).
|
||||
export const SalePageComponent = props => {
|
||||
const { intl, transaction } = props;
|
||||
const currentTransaction = ensureTransaction(transaction);
|
||||
const currentListing = ensureListing(currentTransaction.listing);
|
||||
const title = currentListing.attributes.title;
|
||||
|
||||
const detailsProps = {
|
||||
totalPrice: currentTransaction.attributes.total,
|
||||
saleState: currentTransaction.attributes.state,
|
||||
listing: currentListing,
|
||||
booking: ensureBooking(currentTransaction.booking),
|
||||
customer: ensureUser(currentTransaction.customer),
|
||||
};
|
||||
|
||||
const detailsClassName = classNames(css.tabContent, {
|
||||
[css.tabContentVisible]: props.tab === 'details',
|
||||
});
|
||||
|
||||
const panel = currentTransaction.id
|
||||
? <SaleDetailsPanel className={detailsClassName} {...detailsProps} />
|
||||
: <h1 className={css.title}><FormattedMessage id="SalePage.loadingData" /></h1>;
|
||||
|
||||
return (
|
||||
<PageLayout title={intl.formatMessage({ id: 'SalePage.title' }, { title })}>
|
||||
{panel}
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
SalePageComponent.defaultProps = { transaction: null };
|
||||
|
||||
const { oneOf } = PropTypes;
|
||||
|
||||
SalePageComponent.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
tab: oneOf(['details', 'discussion']).isRequired,
|
||||
transaction: propTypes.transaction,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const transactionRef = state.SalePage.transactionRef;
|
||||
const transactions = getEntities(state.data, transactionRef ? [transactionRef] : []);
|
||||
const transaction = transactions.length > 0 ? transactions[0] : null;
|
||||
return {
|
||||
transaction,
|
||||
showSaleError: state.ListingPage.showListingError,
|
||||
currentUser: state.user.currentUser,
|
||||
};
|
||||
};
|
||||
|
||||
const SalePage = connect(mapStateToProps)(injectIntl(SalePageComponent));
|
||||
|
||||
SalePage.loadData = params => {
|
||||
return loadData(params);
|
||||
};
|
||||
|
||||
export default SalePage;
|
||||
35
src/containers/SalePage/SalePage.test.js
Normal file
35
src/containers/SalePage/SalePage.test.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
createBooking,
|
||||
createListing,
|
||||
createTransaction,
|
||||
createUser,
|
||||
fakeIntl,
|
||||
} from '../../util/test-data';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import { SalePageComponent } from './SalePage';
|
||||
|
||||
describe('SalePage', () => {
|
||||
it('matches snapshot', () => {
|
||||
const transaction = createTransaction({
|
||||
id: 'tx-sale-1',
|
||||
state: 'state/preauthorized',
|
||||
booking: createBooking(
|
||||
'booking1',
|
||||
new Date(Date.UTC(2017, 5, 10)),
|
||||
new Date(Date.UTC(2017, 5, 13))
|
||||
),
|
||||
listing: createListing('listing1'),
|
||||
customer: createUser('customer1'),
|
||||
});
|
||||
|
||||
const props = {
|
||||
transaction,
|
||||
tab: 'details',
|
||||
intl: fakeIntl,
|
||||
};
|
||||
|
||||
const tree = renderShallow(<SalePageComponent {...props} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
64
src/containers/SalePage/__snapshots__/SalePage.test.js.snap
Normal file
64
src/containers/SalePage/__snapshots__/SalePage.test.js.snap
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
exports[`SalePage matches snapshot 1`] = `
|
||||
<Connect(withRouter(PageLayout))
|
||||
title="SalePage.title">
|
||||
<SaleDetailsPanel
|
||||
booking={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"end": 2017-06-13T00:00:00.000Z,
|
||||
"start": 2017-06-10T00:00:00.000Z,
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "booking1",
|
||||
},
|
||||
"type": "booking",
|
||||
}
|
||||
}
|
||||
className="undefined"
|
||||
customer={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"profile": Object {
|
||||
"firstName": "customer1 first name",
|
||||
"lastName": "customer1 last name",
|
||||
"slug": "customer1-slug",
|
||||
},
|
||||
},
|
||||
"id": UUID {
|
||||
"uuid": "customer1",
|
||||
},
|
||||
"type": "user",
|
||||
}
|
||||
}
|
||||
listing={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
"address": "listing1 address",
|
||||
"description": "listing1 description",
|
||||
"geolocation": LatLng {
|
||||
"lat": 40,
|
||||
"lng": 60,
|
||||
},
|
||||
"price": Money {
|
||||
"amount": 5500,
|
||||
"currency": "USD",
|
||||
},
|
||||
"title": "listing1 title",
|
||||
},
|
||||
"author": null,
|
||||
"id": UUID {
|
||||
"uuid": "listing1",
|
||||
},
|
||||
"images": Array [],
|
||||
"type": "listing",
|
||||
}
|
||||
}
|
||||
saleState="state/preauthorized"
|
||||
totalPrice={
|
||||
Money {
|
||||
"amount": 1000,
|
||||
"currency": "USD",
|
||||
}
|
||||
} />
|
||||
</Connect(withRouter(PageLayout))>
|
||||
`;
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/* eslint-disable react/no-unescaped-entities */
|
||||
import React, { PropTypes } from 'react';
|
||||
import { PageLayout, NamedLink } from '../../components';
|
||||
|
||||
const SalesConversationPage = props => {
|
||||
const { params } = props;
|
||||
return (
|
||||
<PageLayout title="Sales conversation page">
|
||||
<p>Sale id: {params.id}</p>
|
||||
<NamedLink name="SaleDiscussionPage" params={{ id: params.id }}>Discussion tab</NamedLink>
|
||||
<br />
|
||||
<NamedLink name="SaleDetailsPage" params={{ id: params.id }}>Details tab</NamedLink>
|
||||
<p>Mobile layout needs different views for discussion and details.</p>
|
||||
<p>
|
||||
Discussion view is the default if route doesn't specify mobile tab (e.g. <i>
|
||||
/order/1234
|
||||
</i>)
|
||||
</p>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
const { shape, number } = PropTypes;
|
||||
|
||||
SalesConversationPage.propTypes = { params: shape({ id: number.isRequired }).isRequired };
|
||||
|
||||
export default SalesConversationPage;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react';
|
||||
import { renderShallow } from '../../util/test-helpers';
|
||||
import SalesConversationPage from './SalesConversationPage';
|
||||
|
||||
describe('SalesConversationPage', () => {
|
||||
it('matches snapshot', () => {
|
||||
const tree = renderShallow(<SalesConversationPage params={{ id: 12345 }} />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
exports[`SalesConversationPage matches snapshot 1`] = `
|
||||
<Connect(withRouter(PageLayout))
|
||||
title="Sales conversation page">
|
||||
<p>
|
||||
Sale id:
|
||||
12345
|
||||
</p>
|
||||
<withFlattenedRoutes(withRouter(NamedLink))
|
||||
name="SaleDiscussionPage"
|
||||
params={
|
||||
Object {
|
||||
"id": 12345,
|
||||
}
|
||||
}>
|
||||
Discussion tab
|
||||
</withFlattenedRoutes(withRouter(NamedLink))>
|
||||
<br />
|
||||
<withFlattenedRoutes(withRouter(NamedLink))
|
||||
name="SaleDetailsPage"
|
||||
params={
|
||||
Object {
|
||||
"id": 12345,
|
||||
}
|
||||
}>
|
||||
Details tab
|
||||
</withFlattenedRoutes(withRouter(NamedLink))>
|
||||
<p>
|
||||
Mobile layout needs different views for discussion and details.
|
||||
</p>
|
||||
<p>
|
||||
Discussion view is the default if route doesn\'t specify mobile tab (e.g.
|
||||
<i>
|
||||
/order/1234
|
||||
</i>
|
||||
)
|
||||
</p>
|
||||
</Connect(withRouter(PageLayout))>
|
||||
`;
|
||||
|
|
@ -20,7 +20,7 @@ import PasswordForgottenForm from './PasswordForgottenForm/PasswordForgottenForm
|
|||
import PasswordForgottenPage from './PasswordForgottenPage/PasswordForgottenPage';
|
||||
import PayoutPreferencesPage from './PayoutPreferencesPage/PayoutPreferencesPage';
|
||||
import ProfilePage from './ProfilePage/ProfilePage';
|
||||
import SalesConversationPage from './SalesConversationPage/SalesConversationPage';
|
||||
import SalePage from './SalePage/SalePage';
|
||||
import SearchPage from './SearchPage/SearchPage';
|
||||
import SecurityPage from './SecurityPage/SecurityPage';
|
||||
import SignUpForm from './SignUpForm/SignUpForm';
|
||||
|
|
@ -51,7 +51,7 @@ export {
|
|||
PasswordForgottenPage,
|
||||
PayoutPreferencesPage,
|
||||
ProfilePage,
|
||||
SalesConversationPage,
|
||||
SalePage,
|
||||
SearchPage,
|
||||
SecurityPage,
|
||||
SignUpForm,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import EditListingPage from './EditListingPage/EditListingPage.duck';
|
|||
import InboxPage from './InboxPage/InboxPage.duck';
|
||||
import ListingPage from './ListingPage/ListingPage.duck';
|
||||
import OrderPage from './OrderPage/OrderPage.duck';
|
||||
import SalePage from './SalePage/SalePage.duck';
|
||||
import SearchPage from './SearchPage/SearchPage.duck';
|
||||
|
||||
export { CheckoutPage, EditListingPage, InboxPage, ListingPage, OrderPage, SearchPage };
|
||||
export { CheckoutPage, EditListingPage, InboxPage, ListingPage, OrderPage, SalePage, SearchPage };
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
PasswordForgottenPage,
|
||||
PayoutPreferencesPage,
|
||||
ProfilePage,
|
||||
SalesConversationPage,
|
||||
SalePage,
|
||||
SearchPage,
|
||||
SecurityPage,
|
||||
StyleguidePage,
|
||||
|
|
@ -200,21 +200,23 @@ const routesConfiguration = [
|
|||
auth: true,
|
||||
exact: true,
|
||||
name: 'SalePage',
|
||||
component: props => <SalesConversationPage {...props} tab="discussion" />,
|
||||
component: props => <SalePage {...props} tab="discussion" />,
|
||||
routes: [
|
||||
{
|
||||
path: '/sale/:id/details',
|
||||
auth: true,
|
||||
exact: true,
|
||||
name: 'SaleDetailsPage',
|
||||
component: props => <SalesConversationPage {...props} tab="discussion" />,
|
||||
component: props => <SalePage {...props} tab="details" />,
|
||||
loadData: params => SalePage.loadData(params),
|
||||
},
|
||||
{
|
||||
path: '/sale/:id/discussion',
|
||||
auth: true,
|
||||
exact: true,
|
||||
name: 'SaleDiscussionPage',
|
||||
component: props => <SalesConversationPage {...props} tab="discussion" />,
|
||||
component: props => <SalePage {...props} tab="discussion" />,
|
||||
loadData: params => SalePage.loadData(params),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -55,14 +55,18 @@
|
|||
"ListingPage.loadingListingData": "Loading listing data",
|
||||
"ListingPage.noListingData": "Could not find listing data",
|
||||
"ModalInMobile.closeModal": "Close modal",
|
||||
"OrderDetailsPanel.listingTitle": "You have requested to book {title}",
|
||||
"OrderDetailsPanel.orderStatusMessage": "{providerName} has been notified about the booking request, so sit back and relax",
|
||||
"OrderPage.loadingData": "Loading order data",
|
||||
"OrderPage.title": "Order details for ${title}",
|
||||
"OrderDetailsPanel.listingTitle": "You have requested to book {title}.",
|
||||
"OrderDetailsPanel.orderStatusMessage": "{providerName} has been notified about the booking request, so sit back and relax.",
|
||||
"OrderPage.loadingData": "Loading order data.",
|
||||
"OrderPage.title": "Order details for ${title}.",
|
||||
"PageLayout.authInfoFailed": "Could not get authentication information.",
|
||||
"PageLayout.logoutFailed": "Logout failed. Please try again.",
|
||||
"PaginationLinks.previous": "Previous page",
|
||||
"PaginationLinks.next": "Next page",
|
||||
"SaleDetailsPanel.listingTitle": "{customerName} has requested to book {title}.",
|
||||
"SaleDetailsPanel.saleStatusMessage": "{customerName} is waiting for your response.",
|
||||
"SalePage.loadingData": "Loading sale data.",
|
||||
"SalePage.title": "Sale details for ${title}.",
|
||||
"SearchPage.foundResults": "{count, number} {count, plural, one {listing} other {listings}} found.",
|
||||
"SearchPage.loadingResults": "Loading search results...",
|
||||
"SearchPage.loadingResults": "Loading search results...",
|
||||
|
|
|
|||
|
|
@ -106,3 +106,50 @@ export const denormalisedEntities = (entities, type, ids) => {
|
|||
return entityData;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create shell objects to ensure that attributes etc. exists.
|
||||
*
|
||||
* @param {Object} transaction entity object, which is to be ensured agains null values
|
||||
*/
|
||||
export const ensureTransaction = transaction => {
|
||||
const empty = {
|
||||
id: null,
|
||||
type: 'transaction',
|
||||
attributes: {},
|
||||
booking: {},
|
||||
listing: {},
|
||||
provider: {},
|
||||
};
|
||||
return { ...empty, ...transaction };
|
||||
};
|
||||
|
||||
/**
|
||||
* Create shell objects to ensure that attributes etc. exists.
|
||||
*
|
||||
* @param {Object} booking entity object, which is to be ensured agains null values
|
||||
*/
|
||||
export const ensureBooking = booking => {
|
||||
const empty = { id: null, type: 'booking', attributes: {} };
|
||||
return { ...empty, ...booking };
|
||||
};
|
||||
|
||||
/**
|
||||
* Create shell objects to ensure that attributes etc. exists.
|
||||
*
|
||||
* @param {Object} listing entity object, which is to be ensured agains null values
|
||||
*/
|
||||
export const ensureListing = listing => {
|
||||
const empty = { id: null, type: 'listing', attributes: {}, images: [] };
|
||||
return { ...empty, ...listing };
|
||||
};
|
||||
|
||||
/**
|
||||
* Create shell objects to ensure that attributes etc. exists.
|
||||
*
|
||||
* @param {Object} user entity object, which is to be ensured agains null values
|
||||
*/
|
||||
export const ensureUser = user => {
|
||||
const empty = { id: null, type: 'user', attributes: { profile: {} } };
|
||||
return { ...empty, ...user };
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue