From 8adb1291d01868c23ddccd1bfedd11eaaab1a607 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 26 Sep 2023 15:18:30 +0200 Subject: [PATCH 01/11] cherry pick PR changes --- src/components/MenuItem.js | 21 +++- src/components/menuItemPropTypes.js | 12 ++ src/languages/en.ts | 8 ++ src/languages/es.ts | 8 ++ .../settings/Wallet/ExpensifyCardPage.js | 38 ++++-- .../settings/Wallet/WalletPage/CardDetails.js | 110 ++++++++++++++++++ 6 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 src/pages/settings/Wallet/WalletPage/CardDetails.js diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 268351699567..3e825ffc66d6 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import PressableWithFeedback from './Pressable/PressableWithFeedback'; import Text from './Text'; import styles from '../styles/styles'; import themeColors from '../styles/themes/default'; @@ -49,6 +50,8 @@ const defaultProps = { iconHeight: undefined, description: undefined, iconRight: Expensicons.ArrowRight, + onIconRightPress: undefined, + iconRightAccessibilityLabel: undefined, iconStyles: [], iconFill: undefined, secondaryIconFill: undefined, @@ -77,6 +80,8 @@ const defaultProps = { numberOfLinesTitle: 1, shouldGreyOutWhenDisabled: true, shouldRenderAsHTML: false, + rightComponent: undefined, + shouldShowRightComponent: false, }; const MenuItem = React.forwardRef((props, ref) => { @@ -131,6 +136,8 @@ const MenuItem = React.forwardRef((props, ref) => { return ''; }, [props.title, props.shouldRenderAsHTML, props.shouldParseTitle, html]); + const hasPressableRightComponent = props.onIconRightPress || props.iconRight || (props.rightComponent && props.shouldShowRightComponent); + return ( {(isHovered) => ( @@ -295,7 +302,7 @@ const MenuItem = React.forwardRef((props, ref) => { - + {Boolean(props.badgeText) && ( { )} {Boolean(props.shouldShowRightIcon) && ( - + - + )} + {props.shouldShowRightComponent && props.rightComponent} {props.shouldShowSelectedState && } diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js index 6272a7a2ef7d..3ba8bed19575 100644 --- a/src/components/menuItemPropTypes.js +++ b/src/components/menuItemPropTypes.js @@ -67,6 +67,12 @@ const propTypes = { /** Overrides the icon for shouldShowRightIcon */ iconRight: PropTypes.elementType, + /** Function to fire when the right icon has been pressed */ + onIconRightPress: PropTypes.func, + + /** accessibilityLabel for the right icon when it's pressable */ + iconRightAccessibilityLabel: PropTypes.string, + /** A description text to show under the title */ description: PropTypes.string, @@ -147,6 +153,12 @@ const propTypes = { /** Should render the content in HTML format */ shouldRenderAsHTML: PropTypes.bool, + + /** Component to be displayed on the right */ + rightComponent: PropTypes.node, + + /** Should render component on the right */ + shouldShowRightComponent: PropTypes.bool, }; export default propTypes; diff --git a/src/languages/en.ts b/src/languages/en.ts index 403931d542f3..1903ec6ed428 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -808,6 +808,14 @@ export default { setDefaultFailure: 'Something went wrong. Please chat with Concierge for further assistance.', }, addBankAccountFailure: 'An unexpected error occurred while trying to add your bank account. Please try again.', + cardDetails: { + cardNumber: 'Virtual card number', + expiration: 'Expiration', + cvv: 'CVV', + address: 'Address', + revealDetails: 'Reveal details', + copyCardNumber: 'Copy card number', + }, }, cardPage: { expensifyCard: 'Expensify Card', diff --git a/src/languages/es.ts b/src/languages/es.ts index ffe334f4a807..662a1e52501c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -803,6 +803,14 @@ export default { setDefaultFailure: 'No se ha podido configurar el método de pago.', }, addBankAccountFailure: 'Ocurrió un error inesperado al intentar añadir la cuenta bancaria. Inténtalo de nuevo.', + cardDetails: { + cardNumber: 'Número de tarjeta virtual', + expiration: 'Expiración', + cvv: 'CVV', + address: 'Dirección', + revealDetails: 'Revelar detalles', + copyCardNumber: 'Copiar número de la tarjeta', + }, }, cardPage: { expensifyCard: 'Tarjeta Expensify', diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js index bc49301e8d80..1297ba66b566 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.js +++ b/src/pages/settings/Wallet/ExpensifyCardPage.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useState} from 'react'; import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -16,6 +16,8 @@ import * as CurrencyUtils from '../../../libs/CurrencyUtils'; import Navigation from '../../../libs/Navigation/Navigation'; import styles from '../../../styles/styles'; import * as CardUtils from '../../../libs/CardUtils'; +import Button from '../../../components/Button'; +import CardDetails from './WalletPage/CardDetails'; const propTypes = { /* Onyx Props */ @@ -45,12 +47,18 @@ function ExpensifyCardPage({ const virtualCard = _.find(domainCards, (card) => card.isVirtual) || {}; const physicalCard = _.find(domainCards, (card) => !card.isVirtual) || {}; + const [shouldShowCardDetails, setShouldShowCardDetails] = useState(false); + if (_.isEmpty(virtualCard) && _.isEmpty(physicalCard)) { return ; } const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(physicalCard.availableSpend || virtualCard.availableSpend || 0); + const handleRevealDetails = () => { + setShouldShowCardDetails(true); + }; + return ( - {!_.isEmpty(physicalCard) && ( - + ) : ( + !_.isEmpty(physicalCard) && ( + + } + /> + ) )} {!_.isEmpty(physicalCard) && ( { + const address = privatePersonalDetails.address || {}; + const [street1, street2] = (address.street || '').split('\n'); + const formatted = [street1, street2, address.city, address.state, address.zip, address.country].join(', '); + + // Remove the last comma of the address + return formatted.trim().replace(/,$/, ''); + }; + + const handleCopyToClipboard = () => { + Clipboard.setString(pan); + }; + + return ( + <> + + + + + + ); +} + +CardDetails.displayName = 'CardDetails'; +CardDetails.propTypes = propTypes; +CardDetails.defaultProps = defaultProps; + +export default withOnyx({ + privatePersonalDetails: { + key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, + }, +})(CardDetails); \ No newline at end of file From 8de0bfce76ee2a238cc6dc96c01c432f2cec53e2 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 26 Sep 2023 15:40:22 +0200 Subject: [PATCH 02/11] handle empty address --- src/pages/settings/Wallet/WalletPage/CardDetails.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.js b/src/pages/settings/Wallet/WalletPage/CardDetails.js index 98ac4b5d9629..d6fdfdc7c343 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.js +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.js @@ -7,6 +7,7 @@ import Clipboard from '../../../../libs/Clipboard'; import useLocalize from '../../../../hooks/useLocalize'; import usePrivatePersonalDetails from '../../../../hooks/usePrivatePersonalDetails'; import ONYXKEYS from '../../../../ONYXKEYS'; +import _ from 'underscore'; const propTypes = { /** Card number */ @@ -59,7 +60,14 @@ function CardDetails({pan, expiration, cvv, privatePersonalDetails}) { const getFormattedAddress = () => { const address = privatePersonalDetails.address || {}; const [street1, street2] = (address.street || '').split('\n'); - const formatted = [street1, street2, address.city, address.state, address.zip, address.country].join(', '); + const addressItems = [street1, street2, address.city, address.state, address.zip, address.country]; + const areAllAddressItemsEmpty = [street1, street2, address.city, address.state, address.zip, address.country].every((item) => _.isEmpty(item)); + + if (areAllAddressItemsEmpty) { + return null; + } + + const formatted = addressItems.join(', '); // Remove the last comma of the address return formatted.trim().replace(/,$/, ''); @@ -107,4 +115,4 @@ export default withOnyx({ privatePersonalDetails: { key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS, }, -})(CardDetails); \ No newline at end of file +})(CardDetails); From bf6e4bacb28740e2fb9aafdb4b4ac18731f54be7 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 26 Sep 2023 15:50:58 +0200 Subject: [PATCH 03/11] replace native .every with underscore method --- src/pages/settings/Wallet/WalletPage/CardDetails.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/Wallet/WalletPage/CardDetails.js b/src/pages/settings/Wallet/WalletPage/CardDetails.js index d6fdfdc7c343..a12c59e5b056 100644 --- a/src/pages/settings/Wallet/WalletPage/CardDetails.js +++ b/src/pages/settings/Wallet/WalletPage/CardDetails.js @@ -1,13 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import * as Expensicons from '../../../../components/Icon/Expensicons'; import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription'; import Clipboard from '../../../../libs/Clipboard'; import useLocalize from '../../../../hooks/useLocalize'; import usePrivatePersonalDetails from '../../../../hooks/usePrivatePersonalDetails'; import ONYXKEYS from '../../../../ONYXKEYS'; -import _ from 'underscore'; const propTypes = { /** Card number */ @@ -61,7 +61,7 @@ function CardDetails({pan, expiration, cvv, privatePersonalDetails}) { const address = privatePersonalDetails.address || {}; const [street1, street2] = (address.street || '').split('\n'); const addressItems = [street1, street2, address.city, address.state, address.zip, address.country]; - const areAllAddressItemsEmpty = [street1, street2, address.city, address.state, address.zip, address.country].every((item) => _.isEmpty(item)); + const areAllAddressItemsEmpty = _.every(addressItems, (item) => _.isEmpty(item)); if (areAllAddressItemsEmpty) { return null; From 9adb8f0446675bb20e6e53293de7ceec6cfcaa2a Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 27 Sep 2023 11:24:54 +0200 Subject: [PATCH 04/11] move card details transactions under cardPage --- src/languages/en.ts | 12 ++++++------ src/languages/es.ts | 12 ++++++------ src/pages/settings/Wallet/ExpensifyCardPage.js | 2 +- src/pages/settings/Wallet/WalletPage/CardDetails.js | 10 +++++----- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 1903ec6ed428..13b2fd597ff2 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -808,6 +808,12 @@ export default { setDefaultFailure: 'Something went wrong. Please chat with Concierge for further assistance.', }, addBankAccountFailure: 'An unexpected error occurred while trying to add your bank account. Please try again.', + }, + cardPage: { + expensifyCard: 'Expensify Card', + availableSpend: 'Remaining spending power', + virtualCardNumber: 'Virtual card number', + physicalCardNumber: 'Physical card number', cardDetails: { cardNumber: 'Virtual card number', expiration: 'Expiration', @@ -817,12 +823,6 @@ export default { copyCardNumber: 'Copy card number', }, }, - cardPage: { - expensifyCard: 'Expensify Card', - availableSpend: 'Remaining spending power', - virtualCardNumber: 'Virtual card number', - physicalCardNumber: 'Physical card number', - }, transferAmountPage: { transfer: ({amount}: TransferParams) => `Transfer${amount ? ` ${amount}` : ''}`, instant: 'Instant (Debit card)', diff --git a/src/languages/es.ts b/src/languages/es.ts index 662a1e52501c..2cc5d817907d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -803,6 +803,12 @@ export default { setDefaultFailure: 'No se ha podido configurar el método de pago.', }, addBankAccountFailure: 'Ocurrió un error inesperado al intentar añadir la cuenta bancaria. Inténtalo de nuevo.', + }, + cardPage: { + expensifyCard: 'Tarjeta Expensify', + availableSpend: 'Capacidad de gasto restante', + virtualCardNumber: 'Número de la tarjeta virtual', + physicalCardNumber: 'Número de la tarjeta física', cardDetails: { cardNumber: 'Número de tarjeta virtual', expiration: 'Expiración', @@ -812,12 +818,6 @@ export default { copyCardNumber: 'Copiar número de la tarjeta', }, }, - cardPage: { - expensifyCard: 'Tarjeta Expensify', - availableSpend: 'Capacidad de gasto restante', - virtualCardNumber: 'Número de la tarjeta virtual', - physicalCardNumber: 'Número de la tarjeta física', - }, transferAmountPage: { transfer: ({amount}: TransferParams) => `Transferir${amount ? ` ${amount}` : ''}`, instant: 'Instante', diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.js b/src/pages/settings/Wallet/ExpensifyCardPage.js index 1297ba66b566..ee6c40e4be3d 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage.js +++ b/src/pages/settings/Wallet/ExpensifyCardPage.js @@ -98,7 +98,7 @@ function ExpensifyCardPage({ rightComponent={