diff --git a/assets/images/credit-card-exclamation.svg b/assets/images/credit-card-exclamation.svg new file mode 100644 index 0000000000000..67e686516baa0 --- /dev/null +++ b/assets/images/credit-card-exclamation.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 5c181bfdb29f4..e55d5a4512bb9 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -51,6 +51,7 @@ import Concierge from '@assets/images/concierge.svg'; import Connect from '@assets/images/connect.svg'; import ConnectionComplete from '@assets/images/connection-complete.svg'; import Copy from '@assets/images/copy.svg'; +import CreditCardExclamation from '@assets/images/credit-card-exclamation.svg'; import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg'; import CreditCard from '@assets/images/creditcard.svg'; import Crosshair from '@assets/images/crosshair.svg'; @@ -224,6 +225,7 @@ export { Copy, CreditCard, CreditCardHourglass, + CreditCardExclamation, DeletedRoomAvatar, Document, DocumentSlash, diff --git a/src/languages/en.ts b/src/languages/en.ts index 8ed0ef8207f1a..d529f25fe310c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3172,6 +3172,19 @@ export default { mergedWithCashTransaction: 'matched a receipt to this transaction.', }, subscription: { + mobileReducedFunctionalityMessage: 'You can’t make changes to your subscription in the mobile app.', + cardSection: { + title: 'Payment', + subtitle: 'Add a payment card to pay for your Expensify subscription.', + addCardButton: 'Add payment card', + cardNextPayment: 'Your next payment date is', + cardEnding: ({cardNumber}) => `Card ending in ${cardNumber}`, + cardInfo: ({name, expiration, currency}) => `Name: ${name}, Expiration: ${expiration}, Currency: ${currency}`, + changeCard: 'Change payment card', + changeCurrency: 'Change payment currency', + cardNotFound: 'No payment card added', + retryPaymentButton: 'Retry payment', + }, yourPlan: { title: 'Your plan', collect: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 2316a5d09c9f2..78499bf92eae2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3678,6 +3678,19 @@ export default { mergedWithCashTransaction: 'encontró un recibo para esta transacción.', }, subscription: { + mobileReducedFunctionalityMessage: 'No puedes hacer cambios en tu suscripción en la aplicación móvil.', + cardSection: { + title: 'Pago', + subtitle: 'Añade una tarjeta de pago para abonar tu suscripción a Expensify', + addCardButton: 'Añade tarjeta de pago', + cardNextPayment: 'Your next payment date is', + cardEnding: ({cardNumber}) => `Tarjeta terminada en ${cardNumber}`, + cardInfo: ({name, expiration, currency}) => `Nombre: ${name}, Expiración: ${expiration}, Moneda: ${currency}`, + changeCard: 'Cambiar tarjeta de pago', + changeCurrency: 'Cambiar moneda de pago', + cardNotFound: 'No se ha añadido ninguna tarjeta de pago', + retryPaymentButton: 'Reintentar el pago', + }, yourPlan: { title: 'Tu plan', collect: { diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx new file mode 100644 index 0000000000000..da65c9d74f072 --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -0,0 +1,67 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import DateUtils from '@libs/DateUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import CardSectionActions from './CardSectionActions'; +import CardSectionDataEmpty from './CardSectionDataEmpty'; + +function CardSection() { + const {translate, preferredLocale} = useLocalize(); + const styles = useThemeStyles(); + const theme = useTheme(); + const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); + + const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault), [fundList]); + + const cardMonth = useMemo(() => DateUtils.getMonthNames(preferredLocale)[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth, preferredLocale]); + + return ( +
+ + {!isEmptyObject(defaultCard?.accountData) && ( + <> + + + + {translate('subscription.cardSection.cardEnding', {cardNumber: defaultCard?.accountData?.cardNumber})} + + {translate('subscription.cardSection.cardInfo', { + name: defaultCard?.accountData?.addressName, + expiration: `${cardMonth} ${defaultCard?.accountData?.cardYear}`, + currency: defaultCard?.accountData?.currency, + })} + + + + + + )} + {isEmptyObject(defaultCard?.accountData) && } + +
+ ); +} + +CardSection.displayName = 'CardSection'; + +export default CardSection; diff --git a/src/pages/settings/Subscription/CardSection/CardSectionActions/index.native.tsx b/src/pages/settings/Subscription/CardSection/CardSectionActions/index.native.tsx new file mode 100644 index 0000000000000..853180c08eb6e --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/CardSectionActions/index.native.tsx @@ -0,0 +1,7 @@ +function CardSectionActions() { + return null; // We need to disable actions on mobile +} + +CardSectionActions.displayName = 'CardSectionActions'; + +export default CardSectionActions; diff --git a/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx b/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx new file mode 100644 index 0000000000000..eb40977c25a98 --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx @@ -0,0 +1,65 @@ +import React, {useCallback, useMemo, useRef, useState} from 'react'; +import {View} from 'react-native'; +import * as Expensicons from '@components/Icon/Expensicons'; +import ThreeDotsMenu from '@components/ThreeDotsMenu'; +import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types'; +import useLocalize from '@hooks/useLocalize'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {AnchorPosition} from '@styles/index'; +import CONST from '@src/CONST'; + +const anchorAlignment = { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, +}; + +function CardSectionActions() { + const {isSmallScreenWidth} = useWindowDimensions(); + const {translate} = useLocalize(); + const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0}); + const threeDotsMenuContainerRef = useRef(null); + + const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( + () => [ + { + icon: Expensicons.CreditCard, + text: translate('subscription.cardSection.changeCard'), + onSelected: () => {}, // TODO: update with navigation to "add card" screen (https://github.com/Expensify/App/issues/38621) + }, + { + icon: Expensicons.MoneyCircle, + text: translate('subscription.cardSection.changeCurrency'), + onSelected: () => {}, // TODO: update with navigation to "change currency" screen (https://github.com/Expensify/App/issues/38621) + }, + ], + [translate], + ); + + const calculateAndSetThreeDotsMenuPosition = useCallback(() => { + if (isSmallScreenWidth) { + return; + } + threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { + setThreeDotsMenuPosition({ + horizontal: x + width, + vertical: y + height, + }); + }); + }, [isSmallScreenWidth]); + + return ( + + + + ); +} + +CardSectionActions.displayName = 'CardSectionActions'; + +export default CardSectionActions; diff --git a/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.native.tsx b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.native.tsx new file mode 100644 index 0000000000000..bbec42d940eea --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.native.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function CardSectionDataEmpty() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const theme = useTheme(); + + return ( + + + {translate('subscription.cardSection.cardNotFound')} + + ); +} + +CardSectionDataEmpty.displayName = 'CardSectionDataEmpty'; + +export default CardSectionDataEmpty; diff --git a/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx new file mode 100644 index 0000000000000..13131841c53dc --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/CardSectionDataEmpty/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Button from '@components/Button'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function CardSectionDataEmpty() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + return ( +