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 (
+