diff --git a/.storybook/fonts.css b/.storybook/fonts.css index bbbcf3839000..906490c3a9d9 100644 --- a/.storybook/fonts.css +++ b/.storybook/fonts.css @@ -40,6 +40,13 @@ src: url('../assets/fonts/web/ExpensifyMono-Bold.woff2') format('woff2'), url('../assets/fonts/web/ExpensifyMono-Bold.woff') format('woff'); } +@font-face { + font-family: ExpensifyNewKansas-Medium; + font-weight: 400; + font-style: normal; + src: url('../assets/fonts/web/ExpensifyNewKansas-Medium.woff2') format('woff2'), url('../assets/fonts/web/ExpensifyNewKansas-Medium.woff') format('woff'); +} + * { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/assets/images/MCCGroupIcons/MCC-Airlines.svg b/assets/images/MCCGroupIcons/MCC-Airlines.svg index 9d7924cff407..b707faf9857e 100644 --- a/assets/images/MCCGroupIcons/MCC-Airlines.svg +++ b/assets/images/MCCGroupIcons/MCC-Airlines.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Commuter.svg b/assets/images/MCCGroupIcons/MCC-Commuter.svg index 2996c9f5f793..d8f808cf463b 100644 --- a/assets/images/MCCGroupIcons/MCC-Commuter.svg +++ b/assets/images/MCCGroupIcons/MCC-Commuter.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Gas.svg b/assets/images/MCCGroupIcons/MCC-Gas.svg index 519882921fb6..b13e657a1af4 100644 --- a/assets/images/MCCGroupIcons/MCC-Gas.svg +++ b/assets/images/MCCGroupIcons/MCC-Gas.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Goods.svg b/assets/images/MCCGroupIcons/MCC-Goods.svg index 2aa86250e9d8..e3ea39f77344 100644 --- a/assets/images/MCCGroupIcons/MCC-Goods.svg +++ b/assets/images/MCCGroupIcons/MCC-Goods.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Groceries.svg b/assets/images/MCCGroupIcons/MCC-Groceries.svg index e957d6ee0238..349154ca5496 100644 --- a/assets/images/MCCGroupIcons/MCC-Groceries.svg +++ b/assets/images/MCCGroupIcons/MCC-Groceries.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Hotel.svg b/assets/images/MCCGroupIcons/MCC-Hotel.svg index 8de897bfafff..04be004b24bb 100644 --- a/assets/images/MCCGroupIcons/MCC-Hotel.svg +++ b/assets/images/MCCGroupIcons/MCC-Hotel.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Mail.svg b/assets/images/MCCGroupIcons/MCC-Mail.svg index 56b4d7bd1005..e554fa44f37f 100644 --- a/assets/images/MCCGroupIcons/MCC-Mail.svg +++ b/assets/images/MCCGroupIcons/MCC-Mail.svg @@ -1,4 +1,7 @@ - - - + + + + diff --git a/assets/images/MCCGroupIcons/MCC-Meals.svg b/assets/images/MCCGroupIcons/MCC-Meals.svg index e8b9eab9d803..df3672cf52a6 100644 --- a/assets/images/MCCGroupIcons/MCC-Meals.svg +++ b/assets/images/MCCGroupIcons/MCC-Meals.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Misc.svg b/assets/images/MCCGroupIcons/MCC-Misc.svg index 8bd292d0568f..a4ef1615d146 100644 --- a/assets/images/MCCGroupIcons/MCC-Misc.svg +++ b/assets/images/MCCGroupIcons/MCC-Misc.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-RentalCar.svg b/assets/images/MCCGroupIcons/MCC-RentalCar.svg index f88d28723569..789cb5bc3fe3 100644 --- a/assets/images/MCCGroupIcons/MCC-RentalCar.svg +++ b/assets/images/MCCGroupIcons/MCC-RentalCar.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Services.svg b/assets/images/MCCGroupIcons/MCC-Services.svg index f4d632e86581..25c67065c105 100644 --- a/assets/images/MCCGroupIcons/MCC-Services.svg +++ b/assets/images/MCCGroupIcons/MCC-Services.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Taxi.svg b/assets/images/MCCGroupIcons/MCC-Taxi.svg index 89d3eb239371..2cc31e4db079 100644 --- a/assets/images/MCCGroupIcons/MCC-Taxi.svg +++ b/assets/images/MCCGroupIcons/MCC-Taxi.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/assets/images/MCCGroupIcons/MCC-Utilities.svg b/assets/images/MCCGroupIcons/MCC-Utilities.svg index 464344b41b4e..27e7290bf4e5 100644 --- a/assets/images/MCCGroupIcons/MCC-Utilities.svg +++ b/assets/images/MCCGroupIcons/MCC-Utilities.svg @@ -1,4 +1,7 @@ - - - + + + + diff --git a/assets/images/eReceiptBGs/eReceiptBG_blue.png b/assets/images/eReceiptBGs/eReceiptBG_blue.png new file mode 100644 index 000000000000..f317b72dc4fc Binary files /dev/null and b/assets/images/eReceiptBGs/eReceiptBG_blue.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_green.png b/assets/images/eReceiptBGs/eReceiptBG_green.png new file mode 100644 index 000000000000..55fe8886bca9 Binary files /dev/null and b/assets/images/eReceiptBGs/eReceiptBG_green.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_navy.png b/assets/images/eReceiptBGs/eReceiptBG_navy.png new file mode 100644 index 000000000000..2b9616d42c11 Binary files /dev/null and b/assets/images/eReceiptBGs/eReceiptBG_navy.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_pink.png b/assets/images/eReceiptBGs/eReceiptBG_pink.png new file mode 100644 index 000000000000..41b6492c3a35 Binary files /dev/null and b/assets/images/eReceiptBGs/eReceiptBG_pink.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_tangerine.png b/assets/images/eReceiptBGs/eReceiptBG_tangerine.png new file mode 100644 index 000000000000..00a8cd6dd612 Binary files /dev/null and b/assets/images/eReceiptBGs/eReceiptBG_tangerine.png differ diff --git a/assets/images/eReceiptBGs/eReceiptBG_yellow.png b/assets/images/eReceiptBGs/eReceiptBG_yellow.png new file mode 100644 index 000000000000..7eb9d1f87fa6 Binary files /dev/null and b/assets/images/eReceiptBGs/eReceiptBG_yellow.png differ diff --git a/assets/images/eReceiptIcon.svg b/assets/images/eReceiptIcon.svg index f4fc8c9fcc34..e54c3a106a48 100644 --- a/assets/images/eReceiptIcon.svg +++ b/assets/images/eReceiptIcon.svg @@ -1,3 +1,6 @@ - - + + + diff --git a/src/CONST.ts b/src/CONST.ts index cbfe07ae5aab..a9bc15ac0fe7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -141,6 +141,7 @@ const CONST = { MONTH_DAY_ABBR_FORMAT: 'MMM d', SHORT_DATE_FORMAT: 'MM-dd', MONTH_DAY_YEAR_ABBR_FORMAT: 'MMM d, yyyy', + MONTH_DAY_YEAR_FORMAT: 'MMMM d, yyyy', FNS_TIMEZONE_FORMAT_STRING: "yyyy-MM-dd'T'HH:mm:ssXXX", FNS_DB_FORMAT_STRING: 'yyyy-MM-dd HH:mm:ss.SSS', LONG_DATE_FORMAT_WITH_WEEKDAY: 'eeee, MMMM d, yyyy', @@ -1475,6 +1476,15 @@ const CONST = { MAKE_REQUEST_WITH_SIDE_EFFECTS: 'makeRequestWithSideEffects', }, + ERECEIPT_COLORS: { + YELLOW: 'Yellow', + ICE: 'Ice', + BLUE: 'Blue', + GREEN: 'Green', + TANGERINE: 'Tangerine', + PINK: 'Pink', + }, + MAP_PADDING: 50, MAP_MARKER_SIZE: 20, diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js new file mode 100644 index 000000000000..e6b3a9809c7e --- /dev/null +++ b/src/components/EReceipt.js @@ -0,0 +1,107 @@ +import React from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import ONYXKEYS from '../ONYXKEYS'; +import * as StyleUtils from '../styles/StyleUtils'; +import transactionPropTypes from './transactionPropTypes'; +import styles from '../styles/styles'; +import * as Expensicons from './Icon/Expensicons'; +import Icon from './Icon'; +import Text from './Text'; +import * as ReportUtils from '../libs/ReportUtils'; +import * as CurrencyUtils from '../libs/CurrencyUtils'; +import * as CardUtils from '../libs/CardUtils'; +import variables from '../styles/variables'; +import useLocalize from '../hooks/useLocalize'; +import EReceiptThumbnail from './EReceiptThumbnail'; +import CONST from '../CONST'; + +const propTypes = { + /* TransactionID of the transaction this EReceipt corresponds to */ + transactionID: PropTypes.string.isRequired, + + /* Onyx Props */ + transaction: transactionPropTypes, +}; + +const defaultProps = { + transaction: {}, +}; + +function EReceipt({transaction, transactionID}) { + const {translate} = useLocalize(); + + // Get receipt colorway, or default to Yellow. + const {backgroundColor: primaryColor, color: secondaryColor} = StyleUtils.getEReceiptColorStyles(StyleUtils.getEReceiptColorCode(transaction)); + + const { + amount: transactionAmount, + currency: transactionCurrency, + merchant: transactionMerchant, + created: transactionDate, + cardID: transactionCardID, + } = ReportUtils.getTransactionDetails(transaction, CONST.DATE.MONTH_DAY_YEAR_FORMAT); + const formattedAmount = CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency); + const currency = CurrencyUtils.getCurrencySymbol(transactionCurrency); + const amount = formattedAmount.replace(currency, ''); + const cardDescription = CardUtils.getCardDescription(transactionCardID); + + const secondaryTextColorStyle = StyleUtils.getColorStyle(secondaryColor); + + return ( + + + + + + + + + + + + {currency} + + + {amount} + + + {transactionMerchant} + + + + {translate('eReceipt.transactionDate')} + {transactionDate} + + + {translate('common.card')} + {cardDescription} + + + + + {translate('eReceipt.guaranteed')} + + + + ); +} + +EReceipt.displayName = 'EReceipt'; +EReceipt.propTypes = propTypes; +EReceipt.defaultProps = defaultProps; + +export default withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, +})(EReceipt); diff --git a/src/components/EReceiptThumbnail.js b/src/components/EReceiptThumbnail.js new file mode 100644 index 000000000000..f1bb5b025e2f --- /dev/null +++ b/src/components/EReceiptThumbnail.js @@ -0,0 +1,124 @@ +import React, {useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import ONYXKEYS from '../ONYXKEYS'; +import * as StyleUtils from '../styles/StyleUtils'; +import transactionPropTypes from './transactionPropTypes'; +import styles from '../styles/styles'; +import * as Expensicons from './Icon/Expensicons'; +import * as MCCIcons from './Icon/MCCIcons'; +import Icon from './Icon'; +import * as ReportUtils from '../libs/ReportUtils'; +import variables from '../styles/variables'; +import * as eReceiptBGs from './Icon/EReceiptBGs'; +import Image from './Image'; +import CONST from '../CONST'; + +const propTypes = { + /* TransactionID of the transaction this EReceipt corresponds to */ + // eslint-disable-next-line react/no-unused-prop-types + transactionID: PropTypes.string.isRequired, + + /* Onyx Props */ + transaction: transactionPropTypes, +}; + +const defaultProps = { + transaction: {}, +}; + +const backgroundImages = { + [CONST.ERECEIPT_COLORS.YELLOW]: eReceiptBGs.EReceiptBG_Yellow, + [CONST.ERECEIPT_COLORS.ICE]: eReceiptBGs.EReceiptBG_Ice, + [CONST.ERECEIPT_COLORS.BLUE]: eReceiptBGs.EReceiptBG_Blue, + [CONST.ERECEIPT_COLORS.GREEN]: eReceiptBGs.EReceiptBG_Green, + [CONST.ERECEIPT_COLORS.TANGERINE]: eReceiptBGs.EReceiptBG_Tangerine, + [CONST.ERECEIPT_COLORS.PINK]: eReceiptBGs.EReceiptBG_Pink, +}; + +function getBackgroundImage(transaction) { + return backgroundImages[StyleUtils.getEReceiptColorCode(transaction)]; +} + +function EReceiptThumbnail({transaction}) { + // Get receipt colorway, or default to Yellow. + const {backgroundColor: primaryColor, color: secondaryColor} = StyleUtils.getEReceiptColorStyles(StyleUtils.getEReceiptColorCode(transaction)); + + const [containerWidth, setContainerWidth] = useState(0); + const [containerHeight, setContainerHeight] = useState(0); + + const onContainerLayout = (event) => { + const {width, height} = event.nativeEvent.layout; + setContainerWidth(width); + setContainerHeight(height); + }; + + const {mccGroup: transactionMCCGroup} = ReportUtils.getTransactionDetails(transaction); + const MCCIcon = MCCIcons[`${transactionMCCGroup}`]; + + const isSmall = containerWidth && containerWidth < variables.eReceiptThumbnailSmallBreakpoint; + const isMedium = containerWidth && containerWidth < variables.eReceiptThumbnailMediumBreakpoint; + + let receiptIconWidth = variables.eReceiptIconWidth; + let receiptIconHeight = variables.eReceiptIconHeight; + let receiptMCCSize = variables.eReceiptMCCHeightWidth; + + if (isSmall) { + receiptIconWidth = variables.eReceiptIconWidthSmall; + receiptIconHeight = variables.eReceiptIconHeightSmall; + receiptMCCSize = variables.eReceiptMCCHeightWidthSmall; + } else if (isMedium) { + receiptIconWidth = variables.eReceiptIconWidthMedium; + receiptIconHeight = variables.eReceiptIconHeightMedium; + receiptMCCSize = variables.eReceiptMCCHeightWidthMedium; + } + + return ( + + + + + + {MCCIcon ? ( + + ) : null} + + + + ); +} + +EReceiptThumbnail.displayName = 'EReceiptThumbnail'; +EReceiptThumbnail.propTypes = propTypes; +EReceiptThumbnail.defaultProps = defaultProps; + +export default withOnyx({ + transaction: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + }, +})(EReceiptThumbnail); diff --git a/src/components/Icon/EReceiptBGs.js b/src/components/Icon/EReceiptBGs.js new file mode 100644 index 000000000000..ff74c0fb83c2 --- /dev/null +++ b/src/components/Icon/EReceiptBGs.js @@ -0,0 +1,8 @@ +import EReceiptBG_Yellow from '../../../assets/images/eReceiptBGs/eReceiptBG_yellow.png'; +import EReceiptBG_Ice from '../../../assets/images/eReceiptBGs/eReceiptBG_navy.png'; +import EReceiptBG_Blue from '../../../assets/images/eReceiptBGs/eReceiptBG_blue.png'; +import EReceiptBG_Green from '../../../assets/images/eReceiptBGs/eReceiptBG_green.png'; +import EReceiptBG_Tangerine from '../../../assets/images/eReceiptBGs/eReceiptBG_tangerine.png'; +import EReceiptBG_Pink from '../../../assets/images/eReceiptBGs/eReceiptBG_pink.png'; + +export {EReceiptBG_Yellow, EReceiptBG_Ice, EReceiptBG_Blue, EReceiptBG_Green, EReceiptBG_Tangerine, EReceiptBG_Pink}; diff --git a/src/components/Icon/MCCIcons.js b/src/components/Icon/MCCIcons.js index bd30e426ab31..a704f7d46bc6 100644 --- a/src/components/Icon/MCCIcons.js +++ b/src/components/Icon/MCCIcons.js @@ -1,15 +1,15 @@ -import Airlines from '../../../assets/images/mccGroupIcons/MCC-Airlines.svg'; -import Commuter from '../../../assets/images/mccGroupIcons/MCC-Commuter.svg'; -import Gas from '../../../assets/images/mccGroupIcons/MCC-Gas.svg'; -import Goods from '../../../assets/images/mccGroupIcons/MCC-Goods.svg'; -import Groceries from '../../../assets/images/mccGroupIcons/MCC-Groceries.svg'; -import Hotel from '../../../assets/images/mccGroupIcons/MCC-Hotel.svg'; -import Mail from '../../../assets/images/mccGroupIcons/MCC-Mail.svg'; -import Meals from '../../../assets/images/mccGroupIcons/MCC-Meals.svg'; -import Rental from '../../../assets/images/mccGroupIcons/MCC-RentalCar.svg'; -import Services from '../../../assets/images/mccGroupIcons/MCC-Services.svg'; -import Taxi from '../../../assets/images/mccGroupIcons/MCC-Taxi.svg'; -import Miscellaneous from '../../../assets/images/mccGroupIcons/MCC-Misc.svg'; -import Utilities from '../../../assets/images/mccGroupIcons/MCC-Utilities.svg'; +import Airlines from '../../../assets/images/MCCGroupIcons/MCC-Airlines.svg'; +import Commuter from '../../../assets/images/MCCGroupIcons/MCC-Commuter.svg'; +import Gas from '../../../assets/images/MCCGroupIcons/MCC-Gas.svg'; +import Goods from '../../../assets/images/MCCGroupIcons/MCC-Goods.svg'; +import Groceries from '../../../assets/images/MCCGroupIcons/MCC-Groceries.svg'; +import Hotel from '../../../assets/images/MCCGroupIcons/MCC-Hotel.svg'; +import Mail from '../../../assets/images/MCCGroupIcons/MCC-Mail.svg'; +import Meals from '../../../assets/images/MCCGroupIcons/MCC-Meals.svg'; +import Rental from '../../../assets/images/MCCGroupIcons/MCC-RentalCar.svg'; +import Services from '../../../assets/images/MCCGroupIcons/MCC-Services.svg'; +import Taxi from '../../../assets/images/MCCGroupIcons/MCC-Taxi.svg'; +import Miscellaneous from '../../../assets/images/MCCGroupIcons/MCC-Misc.svg'; +import Utilities from '../../../assets/images/MCCGroupIcons/MCC-Utilities.svg'; export {Airlines, Commuter, Gas, Goods, Groceries, Hotel, Mail, Meals, Rental, Services, Taxi, Miscellaneous, Utilities}; diff --git a/src/languages/en.ts b/src/languages/en.ts index 1128e364eb94..7a43131fe4f9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -263,6 +263,7 @@ export default { recent: 'Recent', all: 'All', tbd: 'TBD', + card: 'Card', }, location: { useCurrent: 'Use current location', @@ -1840,4 +1841,8 @@ export default { globalNavigationOptions: { chats: 'Chats', }, + eReceipt: { + guaranteed: 'Guaranteed eReceipt', + transactionDate: 'Transaction date', + }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index cc7e94d6e520..dbcf01f959ff 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -253,6 +253,7 @@ export default { recent: 'Reciente', all: 'Todo', tbd: 'Por determinar', + card: 'Tarjeta', }, location: { useCurrent: 'Usar ubicación actual', @@ -2325,4 +2326,8 @@ export default { globalNavigationOptions: { chats: 'Chats', }, + eReceipt: { + guaranteed: 'eRecibo garantizado', + transactionDate: 'Fecha de transacción', + }, } satisfies EnglishTranslation; diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index e138034ed327..e6c7480974ca 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -29,7 +29,7 @@ function getMonthFromExpirationDateString(expirationDateString: string) { * @param cardID * @returns boolean */ -function isExpensifyCard(cardID: string) { +function isExpensifyCard(cardID: number) { const card = allCards[cardID]; if (!card) { return false; @@ -41,7 +41,7 @@ function isExpensifyCard(cardID: string) { * @param cardID * @returns string in format % - %. */ -function getCardDescription(cardID: string) { +function getCardDescription(cardID: number) { const card = allCards[cardID]; if (!card) { return ''; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d688bc544fec..c0b8b5620bfa 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1356,12 +1356,13 @@ function getMoneyRequestReportName(report, policy = undefined) { * into a flat object. Used for displaying transactions and sending them in API commands * * @param {Object} transaction + * @param {Object} createdDateFormat * @returns {Object} */ -function getTransactionDetails(transaction) { +function getTransactionDetails(transaction, createdDateFormat = CONST.DATE.FNS_FORMAT_STRING) { const report = getReport(transaction.reportID); return { - created: TransactionUtils.getCreated(transaction), + created: TransactionUtils.getCreated(transaction, createdDateFormat), amount: TransactionUtils.getAmount(transaction, isExpenseReport(report)), currency: TransactionUtils.getCurrency(transaction), comment: TransactionUtils.getDescription(transaction), @@ -1370,6 +1371,8 @@ function getTransactionDetails(transaction) { category: TransactionUtils.getCategory(transaction), billable: TransactionUtils.getBillable(transaction), tag: TransactionUtils.getTag(transaction), + mccGroup: TransactionUtils.getMCCGroup(transaction), + cardID: TransactionUtils.getCardID(transaction), }; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 716247d571b8..2b7729abc442 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -245,6 +245,13 @@ function getCategory(transaction: Transaction): string { return transaction?.category ?? ''; } +/** + * Return the cardID from the transaction. + */ +function getCardID(transaction: Transaction): number { + return transaction?.cardID ?? 0; +} + /** * Return the billable field from the transaction. This "billable" field has no "modified" complement. */ @@ -262,11 +269,11 @@ function getTag(transaction: Transaction): string { /** * Return the created field from the transaction, return the modifiedCreated if present. */ -function getCreated(transaction: Transaction): string { +function getCreated(transaction: Transaction, dateFormat: string = CONST.DATE.FNS_FORMAT_STRING): string { const created = transaction?.modifiedCreated ? transaction.modifiedCreated : transaction?.created || ''; const createdDate = parseISO(created); if (isValid(createdDate)) { - return format(createdDate, CONST.DATE.FNS_FORMAT_STRING); + return format(createdDate, dateFormat); } return ''; @@ -412,6 +419,7 @@ export { getDescription, getAmount, getCurrency, + getCardID, getMerchant, getMCCGroup, getCreated, diff --git a/src/stories/EReceipt.stories.js b/src/stories/EReceipt.stories.js new file mode 100644 index 000000000000..3099e0f4a128 --- /dev/null +++ b/src/stories/EReceipt.stories.js @@ -0,0 +1,240 @@ +/* eslint-disable rulesdir/prefer-actions-set-data */ +import React from 'react'; +import Onyx from 'react-native-onyx'; +import EReceipt from '../components/EReceipt'; +import ONYXKEYS from '../ONYXKEYS'; + +const transactionData = { + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_1`]: { + transactionID: 'FAKE_1', + amount: 1000, + currency: 'USD', + cardID: 4, + merchant: 'United Airlines', + mccGroup: 'Goods', + created: '2023-07-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_2`]: { + transactionID: 'FAKE_2', + amount: 1000, + currency: 'USD', + cardID: 4, + merchant: 'United Airlines', + mccGroup: 'Airlines', + created: '2023-07-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_3`]: { + transactionID: 'FAKE_3', + amount: 1000, + currency: 'USD', + cardID: 5, + merchant: 'United Airlines', + mccGroup: 'Commuter', + created: '2023-07-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_4`]: {transactionID: 'FAKE_4', amount: 444444, currency: 'USD', cardID: 4, merchant: 'Chevron', mccGroup: 'Gas', created: '2023-07-24 13:46:20'}, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_5`]: { + transactionID: 'FAKE_5', + amount: 230440, + currency: 'USD', + cardID: 4, + merchant: 'Barnes and Noble', + mccGroup: 'Goods', + created: '2022-03-21 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_6`]: { + transactionID: 'FAKE_6', + amount: 333333, + currency: 'USD', + cardID: 4, + merchant: 'Trader Joes', + mccGroup: 'Groceries', + created: '2023-12-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_7`]: { + transactionID: 'FAKE_7', + amount: 1000, + currency: 'USD', + cardID: 4, + merchant: "Linda's Place", + mccGroup: 'Hotel', + created: '2023-03-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_8`]: { + transactionID: 'FAKE_8', + amount: 2000, + currency: 'USD', + cardID: 4, + merchant: 'United Post Office', + mccGroup: 'Mail', + created: '2023-09-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_9`]: { + transactionID: 'FAKE_9', + amount: 40884002, + currency: 'USD', + cardID: 4, + merchant: 'Dishoom', + mccGroup: 'Meals', + created: '2023-07-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_10`]: { + transactionID: 'FAKE_10', + amount: 300000, + currency: 'USD', + cardID: 4, + merchant: 'Hertz', + mccGroup: 'Rental', + created: '2023-07-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_11`]: { + transactionID: 'FAKE_11', + amount: 1000, + currency: 'USD', + cardID: 4, + merchant: 'Laundromat', + mccGroup: 'Services', + created: '2023-07-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_12`]: {transactionID: 'FAKE_12', amount: 1000, currency: 'USD', cardID: 4, merchant: 'Uber', mccGroup: 'Taxi', created: '2023-07-24 13:46:20'}, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_13`]: { + transactionID: 'FAKE_13', + amount: 11230, + currency: 'USD', + cardID: 4, + merchant: 'Pirate Party Store', + mccGroup: 'Miscellaneous', + created: '2023-10-31 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_14`]: { + transactionID: 'FAKE_14', + amount: 21500, + currency: 'GBP', + cardID: 4, + merchant: 'Light Bulbs R-US', + mccGroup: 'Utilities', + created: '2023-06-24 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_15`]: { + transactionID: 'FAKE_15', + amount: 200, + currency: 'USD', + cardID: 4, + merchant: 'Invalid MCC', + mccGroup: 'invalidMCC', + created: '2023-01-11 13:46:20', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}FAKE_16`]: { + transactionID: 'FAKE_16', + amount: 200, + currency: 'USD', + cardID: 4, + merchant: 'This is a very very very very very very very very long merchant name, why would you ever shop at a store with a sign this long?', + mccGroup: 'invalidMCC', + created: '2023-01-11 13:46:20', + }, +}; + +Onyx.mergeCollection(ONYXKEYS.COLLECTION.TRANSACTION, transactionData); +Onyx.merge('cardList', { + 4: {bank: 'Expensify Card', lastFourPAN: '1000'}, + 5: {bank: 'Expensify Card', lastFourPAN: '4444'}, +}); + +/** + * We use the Component Story Format for writing stories. Follow the docs here: + * + * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format + */ +const story = { + title: 'Components/EReceipt', + component: EReceipt, +}; + +function Template(args) { + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} + +const Default = Template.bind({}); +Default.args = { + transactionID: 'FAKE_1', +}; + +const Airlines = Template.bind({}); +Airlines.args = { + transactionID: 'FAKE_2', +}; + +const Commuter = Template.bind({}); +Commuter.args = { + transactionID: 'FAKE_3', +}; + +const Gas = Template.bind({}); +Gas.args = { + transactionID: 'FAKE_4', +}; + +const Goods = Template.bind({}); +Goods.args = { + transactionID: 'FAKE_5', +}; + +const Groceries = Template.bind({}); +Groceries.args = { + transactionID: 'FAKE_6', +}; + +const Hotel = Template.bind({}); +Hotel.args = { + transactionID: 'FAKE_7', +}; + +const Mail = Template.bind({}); +Mail.args = { + transactionID: 'FAKE_8', +}; + +const Meals = Template.bind({}); +Meals.args = { + transactionID: 'FAKE_9', +}; + +const Rental = Template.bind({}); +Rental.args = { + transactionID: 'FAKE_10', +}; + +const Services = Template.bind({}); +Services.args = { + transactionID: 'FAKE_11', +}; + +const Taxi = Template.bind({}); +Taxi.args = { + transactionID: 'FAKE_12', +}; + +const Miscellaneous = Template.bind({}); +Miscellaneous.args = { + transactionID: 'FAKE_13', +}; + +const Utilities = Template.bind({}); +Utilities.args = { + transactionID: 'FAKE_14', +}; + +const invalidMCC = Template.bind({}); +invalidMCC.args = { + transactionID: 'FAKE_15', +}; + +const veryLong = Template.bind({}); +veryLong.args = { + transactionID: 'FAKE_16', +}; + +export default story; +export {Default, Airlines, Commuter, Gas, Goods, Groceries, Hotel, Mail, Meals, Rental, Services, Taxi, Miscellaneous, Utilities, invalidMCC, veryLong}; diff --git a/src/stories/EReceiptThumbail.stories.js b/src/stories/EReceiptThumbail.stories.js new file mode 100644 index 000000000000..3d8e79957172 --- /dev/null +++ b/src/stories/EReceiptThumbail.stories.js @@ -0,0 +1,118 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import {View} from 'react-native'; +import EReceiptThumbnail from '../components/EReceiptThumbnail'; + +/** + * We use the Component Story Format for writing stories. Follow the docs here: + * + * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format + */ +const story = { + title: 'Components/EReceiptThumbnail', + component: EReceiptThumbnail, +}; + +function Template(args) { + return ( + + + + + + + + + + + + + + + + + + + + + + ); +} + +const Default = Template.bind({}); +Default.args = { + transactionID: 'FAKE_1', +}; + +const Airlines = Template.bind({}); +Airlines.args = { + transactionID: 'FAKE_2', +}; + +const Commuter = Template.bind({}); +Commuter.args = { + transactionID: 'FAKE_3', +}; + +const Gas = Template.bind({}); +Gas.args = { + transactionID: 'FAKE_4', +}; + +const Goods = Template.bind({}); +Goods.args = { + transactionID: 'FAKE_5', +}; + +const Groceries = Template.bind({}); +Groceries.args = { + transactionID: 'FAKE_6', +}; + +const Hotel = Template.bind({}); +Hotel.args = { + transactionID: 'FAKE_7', +}; + +const Mail = Template.bind({}); +Mail.args = { + transactionID: 'FAKE_8', +}; + +const Meals = Template.bind({}); +Meals.args = { + transactionID: 'FAKE_9', +}; + +const Rental = Template.bind({}); +Rental.args = { + transactionID: 'FAKE_10', +}; + +const Services = Template.bind({}); +Services.args = { + transactionID: 'FAKE_11', +}; + +const Taxi = Template.bind({}); +Taxi.args = { + transactionID: 'FAKE_12', +}; + +const Miscellaneous = Template.bind({}); +Miscellaneous.args = { + transactionID: 'FAKE_13', +}; + +const Utilities = Template.bind({}); +Utilities.args = { + transactionID: 'FAKE_14', +}; + +const invalidMCC = Template.bind({}); +invalidMCC.args = { + transactionID: 'FAKE_15', +}; + +export default story; +export {Default, Airlines, Commuter, Gas, Goods, Groceries, Hotel, Mail, Meals, Rental, Services, Taxi, Miscellaneous, Utilities, invalidMCC}; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 525598dd22db..7b6e2c120a21 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -13,9 +13,11 @@ import spacing from './utilities/spacing'; import * as UserUtils from '../libs/UserUtils'; import * as Browser from '../libs/Browser'; import cursor from './utilities/cursor'; +import {Transaction} from '../types/onyx'; type ColorValue = ValueOf; type AvatarSizeName = ValueOf; +type EReceiptColorName = ValueOf; type AvatarSizeValue = ValueOf< Pick< typeof variables, @@ -87,13 +89,22 @@ const workspaceColorOptions: WorkspaceColorStyle[] = [ {backgroundColor: colors.ice700, fill: colors.ice200}, ]; -const eReceiptColorOptions: EreceiptColorStyle[] = [ - {backgroundColor: colors.yellow600, color: colors.yellow100}, - {backgroundColor: colors.blue800, color: colors.ice400}, - {backgroundColor: colors.blue400, color: colors.blue100}, - {backgroundColor: colors.green800, color: colors.green400}, - {backgroundColor: colors.tangerine800, color: colors.tangerine400}, - {backgroundColor: colors.pink800, color: colors.pink400}, +const eReceiptColorStyles: Partial> = { + [CONST.ERECEIPT_COLORS.YELLOW]: {backgroundColor: colors.yellow600, color: colors.yellow100}, + [CONST.ERECEIPT_COLORS.ICE]: {backgroundColor: colors.blue800, color: colors.ice400}, + [CONST.ERECEIPT_COLORS.BLUE]: {backgroundColor: colors.blue400, color: colors.blue100}, + [CONST.ERECEIPT_COLORS.GREEN]: {backgroundColor: colors.green800, color: colors.green400}, + [CONST.ERECEIPT_COLORS.TANGERINE]: {backgroundColor: colors.tangerine800, color: colors.tangerine400}, + [CONST.ERECEIPT_COLORS.PINK]: {backgroundColor: colors.pink800, color: colors.pink400}, +}; + +const eReceiptColors: EReceiptColorName[] = [ + CONST.ERECEIPT_COLORS.YELLOW, + CONST.ERECEIPT_COLORS.ICE, + CONST.ERECEIPT_COLORS.BLUE, + CONST.ERECEIPT_COLORS.GREEN, + CONST.ERECEIPT_COLORS.TANGERINE, + CONST.ERECEIPT_COLORS.PINK, ]; const avatarBorderSizes: Partial> = { @@ -251,12 +262,21 @@ function getDefaultWorkspaceAvatarColor(workspaceName: string): ViewStyle | CSSP } /** - * Helper method to return eReceipt color styles + * Helper method to return eReceipt color code */ -function getEReceiptColor(transactionID: string): ViewStyle | CSSProperties { - const colorHash = UserUtils.hashText(transactionID.trim(), eReceiptColorOptions.length); +function getEReceiptColorCode(transaction: Transaction): EReceiptColorName { + const transactionID = transaction.parentTransactionID ?? transaction.transactionID ?? ''; + + const colorHash = UserUtils.hashText(transactionID.trim(), eReceiptColors.length); + + return eReceiptColors[colorHash]; +} - return eReceiptColorOptions[colorHash]; +/** + * Helper method to return eReceipt color styles + */ +function getEReceiptColorStyles(colorCode: EReceiptColorName): EreceiptColorStyle | undefined { + return eReceiptColorStyles[colorCode]; } /** @@ -770,6 +790,15 @@ function getMinimumHeight(minHeight: number): ViewStyle | CSSProperties { }; } +/** + * Get minimum width as style + */ +function getMinimumWidth(minWidth: number): ViewStyle | CSSProperties { + return { + minWidth, + }; +} + /** * Get maximum height as style */ @@ -1166,6 +1195,13 @@ function getDisabledLinkStyles(isDisabled = false): ViewStyle | CSSProperties { }; } +/** + * Returns color style + */ +function getColorStyle(color: string): ViewStyle | CSSProperties { + return {color}; +} + /** * Returns the checkbox pressable style */ @@ -1309,6 +1345,7 @@ export { hasSafeAreas, getHeight, getMinimumHeight, + getMinimumWidth, getMaximumHeight, getMaximumWidth, fade, @@ -1322,6 +1359,7 @@ export { getAutoCompleteSuggestionItemStyle, getAutoCompleteSuggestionContainerStyle, getColoredBackgroundStyle, + getColorStyle, getDefaultWorkspaceAvatarColor, getAvatarBorderRadius, getEmojiReactionBubbleStyle, @@ -1348,5 +1386,6 @@ export { getAmountFontSizeAndLineHeight, getContainerStyles, getTransparentColor, - getEReceiptColor, + getEReceiptColorStyles, + getEReceiptColorCode, }; diff --git a/src/styles/styles.js b/src/styles/styles.js index 25e92b3cd2fb..e6bc50d7a2e5 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3293,6 +3293,64 @@ const styles = (theme) => ({ lineHeight: variables.lineHeightXXLarge, }, + eReceiptAmountLarge: { + ...headlineFont, + fontSize: variables.fontSizeEReceiptLarge, + lineHeight: variables.lineHeightXXsLarge, + wordBreak: 'break-word', + textAlign: 'center', + }, + + eReceiptCurrency: { + ...headlineFont, + fontSize: variables.fontSizeXXLarge, + lineHeight: variables.lineHeightXXLarge, + wordBreak: 'break-all', + }, + + eReceiptMerchant: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeXLarge, + lineHeight: variables.lineHeightXXLarge, + color: theme.text, + }, + + eReceiptWaypointTitle: { + fontFamily: fontFamily.EXP_NEUE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + }, + + eReceiptWaypointAddress: { + fontFamily: fontFamily.MONOSPACE, + fontSize: variables.fontSizeNormal, + lineHeight: variables.lineHeightNormal, + color: theme.textColorfulBackground, + }, + + eReceiptGuaranteed: { + fontFamily: fontFamily.MONOSPACE, + fontSize: variables.fontSizeSmall, + lineHeight: variables.lineHeightSmall, + color: theme.textColorfulBackground, + }, + + eReceiptBackgroundThumbnail: { + ...sizing.w100, + position: 'absolute', + aspectRatio: 335 / 540, + top: 0, + minWidth: 217, + }, + + eReceiptContainer: { + flex: 1, + width: 335, + minHeight: 540, + borderRadius: 20, + overflow: 'hidden', + }, + loginHeroBody: { fontFamily: fontFamily.EXP_NEUE, fontSize: variables.fontSizeSignInHeroBody, diff --git a/src/styles/utilities/spacing.ts b/src/styles/utilities/spacing.ts index f0fbe4b4591a..372fa7cf636f 100644 --- a/src/styles/utilities/spacing.ts +++ b/src/styles/utilities/spacing.ts @@ -171,6 +171,10 @@ export default { marginLeft: -32, }, + mt0: { + marginTop: 0, + }, + mt1: { marginTop: 4, }, @@ -247,6 +251,10 @@ export default { marginBottom: 32, }, + mb10: { + marginBottom: 40, + }, + mb15: { marginBottom: 60, }, @@ -457,6 +465,10 @@ export default { paddingTop: 24, }, + pt8: { + paddingTop: 32, + }, + pt10: { paddingTop: 40, }, @@ -493,6 +505,10 @@ export default { paddingBottom: 32, }, + pb14: { + paddingBottom: 56, + }, + pb20: { paddingBottom: 80, }, diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 5dbe573bea3d..020d742fdeb1 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -56,6 +56,7 @@ export default { fontSizeXXLarge: 28, fontSizeXXXLarge: 32, fontSizeNormalHeight: getValueUsingPixelRatio(20, 28), + fontSizeEReceiptLarge: 44, fontSizeSignInHeroLarge: 48, fontSizeSignInHeroMedium: 38, fontSizeSignInHeroXSmall: 26, @@ -148,6 +149,22 @@ export default { addPaymentPopoverRightSpacing: 13, anonymousReportFooterBreakpoint: 650, dropDownButtonDividerHeight: 28, + eReceiptThumbnailSmallBreakpoint: 110, + eReceiptThumbnailMediumBreakpoint: 335, + eReceiptThumnailCenterReceiptBreakpoint: 200, + eReceiptIconHeight: 100, + eReceiptIconWidth: 72, + eReceiptMCCHeightWidth: 40, + eReceiptIconHeightSmall: 65, + eReceiptIconWidthSmall: 46, + eReceiptMCCHeightWidthSmall: 26, + eReceiptIconHeightMedium: 82, + eReceiptIconWidthMedium: 59, + eReceiptMCCHeightWidthMedium: 32, + eReceiptWordmarkHeight: 19.25, + eReceiptWordmarkWidth: 86, + eReceiptBGHeight: 540, + eReceiptBGHWidth: 335, // The height of the empty list is 14px (2px for borders and 12px for vertical padding) // This is calculated based on the values specified in the 'getGoogleListViewStyle' function of the 'StyleUtils' utility diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 95561d5b5311..292addbb142e 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -63,7 +63,7 @@ type Transaction = { parentTransactionID?: string; reimbursable?: boolean; /** The CC for this transaction */ - cardID?: string; + cardID?: number; /** If the transaction is pending or posted */ status?: ValueOf; /** If an EReceipt should be generated for this transaction */