Skip to content

Commit

Permalink
enhancement(Receipt): fine-tune for event tickets (#920)
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree authored May 22, 2023
1 parent 4136d38 commit 834baa2
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 93 deletions.
30 changes: 30 additions & 0 deletions components/EventDescription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TimeRange } from './TimeRange';
import { FormattedMessage } from 'react-intl';

export const EventDescription = ({ event }) => {
return (
<React.Fragment>
<FormattedMessage defaultMessage='Registration for "{eventName}"' values={{ eventName: event.name }} />
{'. '}

{event.startsAt && (
<React.Fragment>
<FormattedMessage defaultMessage="Date:" />
&nbsp;
<TimeRange startsAt={event.startsAt} endsAt={event.endsAt} timezone={event.timezone} />
</React.Fragment>
)}
</React.Fragment>
);
};

EventDescription.propTypes = {
event: PropTypes.shape({
startsAt: PropTypes.string,
endsAt: PropTypes.string,
timezone: PropTypes.string,
name: PropTypes.string,
}),
};
72 changes: 51 additions & 21 deletions components/Receipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { FormattedMessage, injectIntl } from 'react-intl';
import { get, chunk, sumBy, max, isNil, round, uniqBy } from 'lodash';
import { Box, Flex } from '@opencollective/frontend-components/components/Grid';
import moment from 'moment';
import QRCode from 'qrcode.react';

import { formatCurrency } from '../lib/utils';
import { formatCurrency, getTransactionUrl } from '../lib/utils';
import { Tr, Td } from './StyledTable';
import LinkToCollective from './LinkToCollective';

Expand All @@ -27,6 +28,7 @@ import { H1, H2, P, Span } from '@opencollective/frontend-components/components/
import Container from '@opencollective/frontend-components/components/Container';
import StyledTag from '@opencollective/frontend-components/components/StyledTag';
import StyledHr from '@opencollective/frontend-components/components/StyledHr';
import { EventDescription } from './EventDescription';

export class Receipt extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -59,10 +61,18 @@ export class Receipt extends React.Component {
transactions: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
toAccount: PropTypes.shape({
type: PropTypes.string,
startsAt: PropTypes.string,
endsAt: PropTypes.string,
}),
order: PropTypes.shape({
id: PropTypes.string,
legacyId: PropTypes.number,
type: PropTypes.string,
tier: PropTypes.shape({
type: PropTypes.string,
}),
}),
}),
).isRequired,
Expand Down Expand Up @@ -322,6 +332,8 @@ export class Receipt extends React.Component {
const chunkedTransactions = this.chunkTransactions(receipt, transactions);
const taxesTotal = this.getTaxTotal();
const billTo = this.getBillTo();
const isSingleTransaction = transactions.length === 1;
const isTicketOrder = isSingleTransaction && get(transactions[0], 'order.tier.type') === 'TICKET';
return (
<div className={`Receipts ${receipt.fromAccount.slug}`}>
<div className="pages">
Expand Down Expand Up @@ -371,28 +383,46 @@ export class Receipt extends React.Component {
</Box>
</Flex>

<Box>
<H2 fontSize="16px" lineHeight="18px">
{receipt.template?.title || (
<FormattedMessage id="invoice.donationReceipt" defaultMessage="Payment Receipt" />
)}
</H2>
<div>
{receipt.dateFrom && (
<div>
<CustomIntlDate date={new Date(receipt.dateFrom)} />
</div>
)}
{receipt.dateTo && (
<div>
<label>To:</label> <CustomIntlDate date={new Date(receipt.dateTo)} />
</div>
)}
</div>
<Box mt={2}>{this.getReceiptReference()}</Box>
</Box>
<Flex justifyContent="space-between">
<Box>
<H2 fontSize="16px" lineHeight="18px">
{receipt.template?.title || (
<FormattedMessage id="invoice.donationReceipt" defaultMessage="Payment Receipt" />
)}
</H2>
<div>
{receipt.dateFrom && (
<div>
<CustomIntlDate date={new Date(receipt.dateFrom)} />
</div>
)}
{receipt.dateTo && (
<div>
<label>To:</label> <CustomIntlDate date={new Date(receipt.dateTo)} />
</div>
)}
</div>
<Box mt={2}>{this.getReceiptReference()}</Box>
</Box>
{isTicketOrder && (
<Box>
<QRCode
renderAs="svg"
value={getTransactionUrl(receipt.transactions[0])}
size={256}
fgColor="#313233"
style={{ width: '72px', height: '72px' }}
/>
</Box>
)}
</Flex>
</Box>
)}
{Boolean(isTicketOrder && receipt.transactions[0].toAccount.type === 'EVENT') && (
<P fontSize="12px" mb={3}>
<EventDescription event={receipt.transactions[0].toAccount} />
</P>
)}
<Box width={1} css={{ flexGrow: 1 }}>
{this.renderTransactionsTable(transactionsChunk)}
{pageNumber === chunkedTransactions.length - 1 && (
Expand Down
55 changes: 55 additions & 0 deletions components/TimeRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedDate, FormattedTime } from 'react-intl';
import dayjs from '../lib/dayjs';

const FormattedDateProps = (value, timeZone) => ({
value,
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone,
});

const FormattedTimeProps = (value, timeZone) => ({
value,
timeZone,
});

const getIsSameDay = (startsAt, endsAt, timezone) => {
if (!endsAt) {
return true;
}
const tzStartsAt = dayjs.tz(new Date(startsAt), timezone);
const tzEndsAt = dayjs.tz(new Date(endsAt), timezone);
return tzStartsAt.isSame(tzEndsAt, 'day');
};

export const TimeRange = ({ startsAt, endsAt, timezone }) => {
const isSameDay = getIsSameDay(startsAt, endsAt, timezone);
return (
<Fragment>
<FormattedDate {...FormattedDateProps(startsAt, timezone)} />
, <FormattedTime {...FormattedTimeProps(startsAt, timezone)} />{' '}
{endsAt && (
<Fragment>
-{' '}
{!isSameDay && (
<Fragment>
<FormattedDate {...FormattedDateProps(endsAt, timezone)} />,{' '}
</Fragment>
)}
<FormattedTime {...FormattedTimeProps(endsAt, timezone)} />{' '}
</Fragment>
)}
(UTC{dayjs().tz(timezone).format('Z')})
</Fragment>
);
};

TimeRange.propTypes = {
startsAt: PropTypes.string,
endsAt: PropTypes.string,
timezone: PropTypes.string.isRequired,
};
8 changes: 8 additions & 0 deletions lib/dayjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

dayjs.extend(utc);
dayjs.extend(timezone);

export default dayjs;
1 change: 1 addition & 0 deletions lib/fixtures/contribution-receipt.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"refundTransaction": null,
"order": {
"id": "7ywz9j4a-vgod8pg5-w56mr35n-xklb0e7a",
"legacyId": "12345",
"data": {},
"taxes": [{ "type": "VAT", "percentage": 21, "__typename": "OrderTax" }],
"quantity": 1,
Expand Down
5 changes: 5 additions & 0 deletions lib/graphql/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ const receiptTransactionFragment = gql`
...ReceiptTransactionHostFieldsFragment
}
}
... on Event {
startsAt
endsAt
timezone
}
}
giftCardEmitterAccount {
id
Expand Down
10 changes: 10 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ export function imagePreview(src, defaultImage, options = { width: 640 }) {
return null;
}

export const getTransactionUrl = (transaction) => {
const domain = 'https://opencollective.com'; // We should have this one change based on env
const toAccount = transaction.toAccount;
if (transaction.order?.legacyId) {
return `${domain}/${toAccount.slug}/orders/${transaction.order.legacyId}`;
} else {
return `${domain}/${toAccount.slug}/transactions`;
}
};

/**
* Transorm an object into a query string. Strips undefined values.
*
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"babel-plugin-formatjs": "10.3.25",
"cors": "2.8.5",
"cross-fetch": "3.1.6",
"dayjs": "1.11.7",
"debug": "4.3.4",
"dotenv": "16.0.3",
"express": "4.18.2",
Expand Down
72 changes: 0 additions & 72 deletions public/static/fixtures/simple-transaction-with-vat.json

This file was deleted.

1 comment on commit 834baa2

@vercel
Copy link

@vercel vercel bot commented on 834baa2 May 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.