Merge pull request #549 from sharetribe/tx-transitions

Add transaction transitions to the order/sale pages
This commit is contained in:
Hannu Lyytikäinen 2017-11-17 14:38:12 +02:00 committed by GitHub
commit b035784e8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1458 additions and 68 deletions

View file

@ -22,6 +22,13 @@ const exampleTransaction = params => {
createdAt: created,
lastTransitionedAt: created,
lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE,
transitions: [
{
at: created,
by: propTypes.TX_TRANSITION_ACTOR_CUSTOMER,
transition: propTypes.TX_TRANSITION_PREAUTHORIZE,
},
],
// payinTotal, payoutTotal, and lineItems required in params
...params,

View file

@ -17,6 +17,13 @@ const exampleTransaction = params => {
createdAt: created,
lastTransitionedAt: created,
lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE,
transitions: [
{
at: created,
by: propTypes.TX_TRANSITION_ACTOR_CUSTOMER,
transition: propTypes.TX_TRANSITION_PREAUTHORIZE,
},
],
// payinTotal, payoutTotal, and lineItems required in params
...params,

View file

@ -8,13 +8,13 @@
}
.messageItem {
margin-bottom: 35px;
margin-bottom: 38px;
/* Clearfix */
@apply --clearfix;
@media (--viewportMedium) {
margin-bottom: 40px;
margin-bottom: 41px;
}
&:last-of-type {
@ -41,7 +41,11 @@
.avatar {
flex-shrink: 0;
margin: 9px 12px 0 0;
margin: 6px 12px 0 0;
@media (--viewportMedium) {
margin: 9px 12px 0 0;
}
}
.messageContent,
@ -49,11 +53,12 @@
@apply --marketplaceMessageFontStyles;
margin: 0;
padding: 15px 14px 11px 14px;
padding: 12px 14px 11px 14px;
border-radius: 8px;
box-shadow: var(--boxShadow);
@media (--viewportMedium) {
padding: 14px 14px 11px 14px;
margin: 0;
}
}
@ -81,3 +86,47 @@
.ownMessageDate {
text-align: right;
}
.transitionItem {
margin-bottom: 36px;
/* Clearfix */
@apply --clearfix;
@media (--viewportMedium) {
margin-bottom: 40px;
}
&:last-of-type {
margin-bottom: 0;
}
}
.transition {
display: flex;
flex-direction: row;
padding: 3px 0 2px 0;
@media (--viewportMedium) {
padding: 0;
}
}
.transitionContent {
@apply --marketplaceTxTransitionFontStyles;
margin: 0;
}
.transitionDate {
@apply --marketplaceMessageDateFontStyles;
color: var(--matterColorAnti);
margin: 7px 0 0 0;
@media (--viewportMedium) {
margin: -1px 0 1px 0;
}
}
.bullet {
margin-right: 6px;
}

View file

@ -1,4 +1,12 @@
import { createUser, createCurrentUser, createMessage } from '../../util/test-data';
import {
createUser,
createCurrentUser,
createMessage,
createTransaction,
createListing,
createTxTransition,
} from '../../util/test-data';
import * as propTypes from '../../util/propTypes';
import Messages from './Messages';
export const Empty = {
@ -6,6 +14,9 @@ export const Empty = {
props: {
messages: [],
currentUser: null,
transaction: null,
customer: null,
provider: null,
},
group: 'messages',
};
@ -34,3 +45,86 @@ export const WithCurrentUser = {
},
group: 'messages',
};
export const WithTransitions = {
component: Messages,
props: {
messages: [],
transaction: createTransaction({
customer: createUser('user1'),
provider: createUser('user2'),
listing: createListing('Listing'),
transitions: [
createTxTransition({
at: new Date(Date.UTC(2017, 10, 9, 8, 10)),
by: propTypes.TX_TRANSITION_ACTOR_CUSTOMER,
transition: propTypes.TX_TRANSITION_PREAUTHORIZE,
}),
createTxTransition({
at: new Date(Date.UTC(2017, 10, 9, 8, 12)),
by: propTypes.TX_TRANSITION_ACTOR_PROVIDER,
transition: propTypes.TX_TRANSITION_ACCEPT,
}),
],
}),
currentUser: createCurrentUser('user2'),
},
group: 'messages',
};
export const WithMessagesAndTransitions = {
component: Messages,
props: {
messages: [
createMessage(
'msg1',
{ at: new Date(Date.UTC(2017, 10, 9, 8, 11)) },
{ sender: createUser('user1') }
),
createMessage(
'msg2',
{ at: new Date(Date.UTC(2017, 10, 9, 8, 14)) },
{ sender: createUser('user1') }
),
createMessage(
'msg3',
{ at: new Date(Date.UTC(2017, 10, 9, 8, 17)) },
{ sender: createUser('user2') }
),
createMessage(
'msg4',
{ at: new Date(Date.UTC(2017, 10, 12, 13, 20)) },
{ sender: createUser('user2') }
),
],
transaction: createTransaction({
customer: createUser('user1'),
provider: createUser('user2'),
listing: createListing('Listing'),
transitions: [
createTxTransition({
at: new Date(Date.UTC(2017, 10, 9, 8, 10)),
by: propTypes.TX_TRANSITION_ACTOR_CUSTOMER,
transition: propTypes.TX_TRANSITION_PREAUTHORIZE,
}),
createTxTransition({
at: new Date(Date.UTC(2017, 10, 9, 8, 12)),
by: propTypes.TX_TRANSITION_ACTOR_PROVIDER,
transition: propTypes.TX_TRANSITION_ACCEPT,
}),
createTxTransition({
at: new Date(Date.UTC(2017, 10, 9, 10, 33)),
by: propTypes.TX_TRANSITION_ACTOR_PROVIDER,
transition: propTypes.TX_TRANSITION_DECLINE,
}),
createTxTransition({
at: new Date(Date.UTC(2017, 10, 9, 10, 34)),
by: propTypes.TX_TRANSITION_ACTOR_PROVIDER,
transition: propTypes.TX_TRANSITION_MARK_DELIVERED,
}),
],
}),
currentUser: createCurrentUser('user2'),
},
group: 'messages',
};

View file

@ -4,6 +4,7 @@ import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { Avatar } from '../../components';
import { formatDate } from '../../util/dates';
import { ensureTransaction, ensureUser } from '../../util/data';
import * as propTypes from '../../util/propTypes';
import css from './Messages.css';
@ -43,11 +44,133 @@ OwnMessage.propTypes = {
intl: intlShape.isRequired,
};
const Transition = props => {
const { transition, currentUser, customer, provider, listingTitle, intl } = props;
const resolveTransitionMessage = (transition, ownRole, otherUsersName, intl) => {
const isOwnTransition = transition.by === ownRole;
const transitionType = transition.transition;
const displayName = otherUsersName;
switch (transitionType) {
case propTypes.TX_TRANSITION_PREAUTHORIZE:
return isOwnTransition
? intl.formatMessage({ id: 'Messages.ownTransitionRequest' }, { listingTitle })
: intl.formatMessage({ id: 'Messages.transitionRequest' }, { displayName, listingTitle });
case propTypes.TX_TRANSITION_ACCEPT:
return isOwnTransition
? intl.formatMessage({ id: 'Messages.ownTransitionAccept' })
: intl.formatMessage({ id: 'Messages.transitionAccept' }, { displayName });
case propTypes.TX_TRANSITION_DECLINE:
return isOwnTransition
? intl.formatMessage({ id: 'Messages.ownTransitionDecline' })
: intl.formatMessage({ id: 'Messages.transitionDecline' }, { displayName });
case propTypes.TX_TRANSITION_AUTO_DECLINE:
return ownRole === propTypes.TX_TRANSITION_ACTOR_PROVIDER
? intl.formatMessage({ id: 'Messages.ownTransitionAutoDecline' })
: intl.formatMessage({ id: 'Messages.transitionAutoDecline' }, { displayName });
case propTypes.TX_TRANSITION_MARK_DELIVERED:
return intl.formatMessage({ id: 'Messages.transitionComplete' });
case propTypes.TX_TRANSITION_CANCEL:
return intl.formatMessage({ id: 'Messages.transitionCancel' });
default:
return '';
}
};
const ownRole =
currentUser.id.uuid === customer.id.uuid
? propTypes.TX_TRANSITION_ACTOR_CUSTOMER
: propTypes.TX_TRANSITION_ACTOR_PROVIDER;
const otherUsersName =
ownRole === propTypes.TX_TRANSITION_ACTOR_CUSTOMER
? provider.attributes.profile.displayName
: customer.attributes.profile.displayName;
const transitionMessage = resolveTransitionMessage(transition, ownRole, otherUsersName, intl);
const todayString = intl.formatMessage({ id: 'Messages.today' });
return (
<div className={css.transition}>
<div className={css.bullet}>
<p className={css.transitionContent}></p>
</div>
<div>
<p className={css.transitionContent}>{transitionMessage}</p>
<p className={css.transitionDate}>{formatDate(intl, todayString, transition.at)}</p>
</div>
</div>
);
};
Transition.propTypes = {
transition: propTypes.txTransition.isRequired,
currentUser: propTypes.currentUser.isRequired,
customer: propTypes.user.isRequired,
provider: propTypes.user.isRequired,
listingTitle: string.isRequired,
intl: intlShape.isRequired,
};
const EmptyTransition = () => {
return (
<div className={css.transition}>
<div className={css.bullet}>
<p className={css.transitionContent}></p>
</div>
<div>
<p className={css.transitionContent} />
<p className={css.transitionDate} />
</div>
</div>
);
};
export const MessagesComponent = props => {
const { rootClassName, className, messages, currentUser, intl } = props;
const { rootClassName, className, messages, transaction, currentUser, intl } = props;
const classes = classNames(rootClassName || css.root, className);
const msg = message => {
const currentTransaction = ensureTransaction(transaction);
const transitions = currentTransaction.attributes.transitions
? currentTransaction.attributes.transitions
: [];
const currentCustomer = ensureUser(currentTransaction.customer);
const currentProvider = ensureUser(currentTransaction.provider);
const transitionsAvailable =
currentUser && currentUser.id && currentCustomer.id && currentProvider.id;
const isMessage = item => item && item.type === 'message';
// Compare function for sorting an array containint messages and transitions
const compareItems = (a, b) => {
const itemDate = item => (isMessage(item) ? item.attributes.at : item.at);
return itemDate(a) - itemDate(b);
};
// combine messages and transaction transitions
const items = messages.concat(transitions).sort(compareItems);
const transitionComponent = transition => {
if (transitionsAvailable) {
return (
<Transition
transition={transition}
currentUser={currentUser}
customer={currentCustomer}
provider={currentProvider}
listingTitle={currentTransaction.listing.attributes.title}
intl={intl}
/>
);
} else {
return <EmptyTransition />;
}
};
const messageComponent = message => {
const isOwnMessage =
message.sender &&
message.sender.id &&
@ -60,13 +183,31 @@ export const MessagesComponent = props => {
return <Message message={message} intl={intl} />;
};
const messageListItem = message => {
return (
<li id={`msg-${message.id.uuid}`} key={message.id.uuid} className={css.messageItem}>
{messageComponent(message)}
</li>
);
};
const transitionListItem = transition => {
return (
<li key={transition.transition} className={css.transitionItem}>
{transitionComponent(transition)}
</li>
);
};
return (
<ul className={classes}>
{messages.map(m => (
<li id={`msg-${m.id.uuid}`} key={m.id.uuid} className={css.messageItem}>
{msg(m)}
</li>
))}
{items.map(item => {
if (isMessage(item)) {
return messageListItem(item);
} else {
return transitionListItem(item);
}
})}
</ul>
);
};
@ -81,6 +222,7 @@ MessagesComponent.propTypes = {
className: string,
messages: arrayOf(propTypes.message),
transaction: propTypes.transaction,
currentUser: propTypes.currentUser,
// from injectIntl

View file

@ -155,7 +155,11 @@ export class OrderDetailsPanelComponent extends Component {
const messagesContainerClasses = classNames(css.messagesContainer, {
[css.messagesContainerWithInfoAbove]: showInfoMessage,
});
const showMessages = messages.length > 0 || initialMessageFailed || fetchMessagesError;
const txTransitions = currentTransaction.attributes.transitions
? currentTransaction.attributes.transitions
: [];
const showMessages =
messages.length > 0 || txTransitions.length > 0 || initialMessageFailed || fetchMessagesError;
const messagesContainer = showMessages ? (
<div className={messagesContainerClasses}>
<h3 className={css.messagesHeading}>
@ -171,7 +175,12 @@ export class OrderDetailsPanelComponent extends Component {
<FormattedMessage id="OrderDetailsPanel.messageLoadingFailed" />
</p>
) : null}
<Messages className={css.messages} messages={messages} currentUser={currentUser} />
<Messages
className={css.messages}
messages={messages}
transaction={currentTransaction}
currentUser={currentUser}
/>
</div>
) : null;

View file

@ -151,6 +151,18 @@ exports[`SaleDetailsPanel accepted matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -379,6 +391,18 @@ exports[`SaleDetailsPanel accepted matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -590,6 +614,18 @@ exports[`SaleDetailsPanel autodeclined matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -818,6 +854,18 @@ exports[`SaleDetailsPanel autodeclined matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -948,7 +996,7 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
</div>
<h1>
<FormattedMessage
id="SaleDetailsPanel.listingRequestedTitle"
id="SaleDetailsPanel.listingAcceptedTitle"
values={
Object {
"customerName": "customer1 display name",
@ -967,16 +1015,6 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
}
/>
</h1>
<p>
<FormattedMessage
id="SaleDetailsPanel.saleRequestedStatus"
values={
Object {
"customerName": "customer1 display name",
}
}
/>
</p>
<div />
<div>
<h3>
@ -1002,7 +1040,7 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
"lastTransition": "transition/preauthorize",
"lastTransition": "transition/accept",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@ -1039,6 +1077,18 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -1095,7 +1145,7 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
/>
</div>
<div
className="undefined"
className=""
>
<h3>
<FormattedMessage
@ -1180,29 +1230,6 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
}
/>
</div>
<div>
<div />
<SecondaryButton
disabled={false}
inProgress={false}
onClick={[Function]}
>
<FormattedMessage
id="SalePage.declineButton"
values={Object {}}
/>
</SecondaryButton>
<PrimaryButton
disabled={false}
inProgress={false}
onClick={[Function]}
>
<FormattedMessage
id="SalePage.acceptButton"
values={Object {}}
/>
</PrimaryButton>
</div>
</div>
<div>
<div>
@ -1253,7 +1280,7 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
Object {
"attributes": Object {
"createdAt": 2017-05-01T00:00:00.000Z,
"lastTransition": "transition/preauthorize",
"lastTransition": "transition/accept",
"lastTransitionedAt": 2017-06-10T00:00:00.000Z,
"lineItems": Array [
Object {
@ -1290,6 +1317,18 @@ exports[`SaleDetailsPanel canceled matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -1501,6 +1540,18 @@ exports[`SaleDetailsPanel declined matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -1729,6 +1780,18 @@ exports[`SaleDetailsPanel declined matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -1940,6 +2003,18 @@ exports[`SaleDetailsPanel delivered matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -2168,6 +2243,18 @@ exports[`SaleDetailsPanel delivered matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -2389,6 +2476,18 @@ exports[`SaleDetailsPanel preauthorized matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -2640,6 +2739,18 @@ exports[`SaleDetailsPanel preauthorized matches snapshot 1`] = `
"amount": 15500,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {

View file

@ -130,6 +130,18 @@ exports[`InboxPage matches snapshot 1`] = `
"amount": 900,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -232,6 +244,18 @@ exports[`InboxPage matches snapshot 1`] = `
"amount": 900,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -492,6 +516,18 @@ exports[`InboxPage matches snapshot 3`] = `
"amount": 900,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {
@ -594,6 +630,18 @@ exports[`InboxPage matches snapshot 3`] = `
"amount": 900,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {

View file

@ -89,6 +89,18 @@ exports[`OrderPage matches snapshot 1`] = `
"amount": 900,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {

View file

@ -95,6 +95,18 @@ exports[`SalePage matches snapshot 1`] = `
"amount": 900,
"currency": "USD",
},
"transitions": Array [
Object {
"at": 2017-05-01T00:00:00.000Z,
"by": "customer",
"transition": "transition/preauthorize",
},
Object {
"at": 2017-06-01T00:00:00.000Z,
"by": "provider",
"transition": "transition/accept",
},
],
},
"booking": Object {
"attributes": Object {

View file

@ -231,6 +231,18 @@
}
}
--marketplaceTxTransitionFontStyles {
font-family: 'sofiapro', Helvetica, Arial, sans-serif;
font-weight: var(--fontWeightMedium);
font-size: 16px;
line-height: 18px;
@media (--viewportMedium) {
font-size: 20px;
line-height: 32px;
}
}
/* ================ Tabbed navigation font styles ================ */
--marketplaceTabNavFontStyles {

View file

@ -248,7 +248,17 @@
"ManageListingsPage.youHaveListings": "You have {count} {count, plural, one {listing} other {listings}}",
"ManageListingsPage.yourListings": "Your listings",
"MapPriceMarker.unsupportedPrice": "({currency})",
"Messages.ownTransitionAccept": "You accepted the booking request.",
"Messages.ownTransitionAutoDecline": "Booking request expired. You did not reply in time.",
"Messages.ownTransitionDecline": "You declined the booking request.",
"Messages.ownTransitionRequest": "You requested to book { listingTitle }.",
"Messages.today": "Today",
"Messages.transitionAccept": "{ displayName } accepted the booking request.",
"Messages.transitionAutoDecline": "Booking request expired. { displayName } did not reply in time.",
"Messages.transitionComplete": "Booking was completed.",
"Messages.transitionCancel": "Booking was cancelled.",
"Messages.transitionDecline": "{ displayName } declined the booking request.",
"Messages.transitionRequest": "{ displayName } requested to book { listingTitle }.",
"Modal.close": "CLOSE",
"Modal.closeModal": "Close modal",
"NotFoundPage.description": "We can't find the page or the sauna you're looking for. Make sure you've typed the URL correctly, of try searching Saunatime.",

View file

@ -172,6 +172,19 @@ export const TX_TRANSITIONS = [
TX_TRANSITION_MARK_DELIVERED,
];
// Roles of actors that perform transaction transitions
export const TX_TRANSITION_ACTOR_CUSTOMER = 'customer';
export const TX_TRANSITION_ACTOR_PROVIDER = 'provider';
export const TX_TRANSITION_ACTOR_SYSTEM = 'system';
export const TX_TRANSITION_ACTOR_OPERATOR = 'operator';
export const TX_TRANSITION_ACTORS = [
TX_TRANSITION_ACTOR_CUSTOMER,
TX_TRANSITION_ACTOR_PROVIDER,
TX_TRANSITION_ACTOR_SYSTEM,
TX_TRANSITION_ACTOR_OPERATOR,
];
const txLastTransition = tx => ensureTransaction(tx).attributes.lastTransition;
export const txIsPreauthorized = tx => txLastTransition(tx) === TX_TRANSITION_PREAUTHORIZE;
@ -188,6 +201,12 @@ export const txIsCanceled = tx => txLastTransition(tx) === TX_TRANSITION_CANCEL;
export const txIsDelivered = tx => txLastTransition(tx) === TX_TRANSITION_MARK_DELIVERED;
export const txTransition = shape({
at: instanceOf(Date).isRequired,
by: oneOf(TX_TRANSITION_ACTORS).isRequired,
transition: oneOf(TX_TRANSITIONS).isRequired,
});
// Denormalised transaction object
export const transaction = shape({
id: uuid.isRequired,
@ -207,6 +226,7 @@ export const transaction = shape({
reversal: bool.isRequired,
})
).isRequired,
transitions: arrayOf(txTransition).isRequired,
}),
booking,
listing,

View file

@ -86,10 +86,19 @@ export const createListing = (id, attributes = {}, includes = {}) => ({
...includes,
});
export const createTxTransition = options => {
return {
at: new Date(Date.UTC(2017, 4, 1)),
by: propTypes.TX_TRANSITION_ACTOR_CUSTOMER,
transition: propTypes.TX_TRANSITION_PREAUTHORIZE,
...options,
};
};
export const createTransaction = options => {
const {
id,
lastTransition = propTypes.TX_TRANSITION_PREAUTHORIZE,
lastTransition = propTypes.TX_TRANSITION_ACCEPT,
total = new Money(1000, 'USD'),
commission = new Money(100, 'USD'),
booking = null,
@ -97,6 +106,18 @@ export const createTransaction = options => {
customer = null,
provider = null,
lastTransitionedAt = new Date(Date.UTC(2017, 5, 1)),
transitions = [
createTxTransition({
at: new Date(Date.UTC(2017, 4, 1)),
by: propTypes.TX_TRANSITION_ACTOR_CUSTOMER,
transition: propTypes.TX_TRANSITION_PREAUTHORIZE,
}),
createTxTransition({
at: new Date(Date.UTC(2017, 5, 1)),
by: propTypes.TX_TRANSITION_ACTOR_PROVIDER,
transition: propTypes.TX_TRANSITION_ACCEPT,
}),
],
} = options;
const nightCount = booking ? nightsBetween(booking.attributes.start, booking.attributes.end) : 1;
return {
@ -123,6 +144,7 @@ export const createTransaction = options => {
reversal: false,
},
],
transitions,
},
booking,
listing,