From 91959fbabb981f102b5476f789747e242e52474e Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 29 May 2024 18:29:06 +0300 Subject: [PATCH 01/17] Add payment card flow --- src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + .../PaymentCardCurrencyModal.tsx | 84 +++++ .../AddPaymentCard/PaymentCardForm.tsx | 274 +++++++++++++++++ src/components/Section/IconSection.tsx | 12 +- src/components/Section/index.tsx | 26 +- src/languages/en.ts | 10 + src/languages/es.ts | 10 + .../ModalStackNavigators/index.tsx | 1 + .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- src/libs/Navigation/linkingConfig/config.ts | 4 + src/libs/Navigation/types.ts | 1 + .../PaymentCard/AddPaymentCard.tsx | 102 +++++++ .../members/WorkspaceOwnerPaymentCardForm.tsx | 288 +++++------------- src/styles/variables.ts | 1 + 15 files changed, 594 insertions(+), 223 deletions(-) create mode 100644 src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx create mode 100644 src/components/AddPaymentCard/PaymentCardForm.tsx create mode 100644 src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 61034382fefd..bf2430418db4 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -103,6 +103,7 @@ const ROUTES = { SETTINGS_PREFERENCES: 'settings/preferences', SETTINGS_SUBSCRIPTION: 'settings/subscription', SETTINGS_SUBSCRIPTION_SIZE: 'settings/subscription/subscription-size', + SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD: 'settings/subscription/add-payment-card', SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode', SETTINGS_LANGUAGE: 'settings/preferences/language', SETTINGS_THEME: 'settings/preferences/theme', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6f32f980d6c2..c97ff5abca28 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -107,6 +107,7 @@ const SCREENS = { SUBSCRIPTION: { ROOT: 'Settings_Subscription', SIZE: 'Settings_Subscription_Size', + ADD_PAYMENT_CARD: 'Settings_Subscription_Add_Payment_Card', }, }, SAVE_THE_WORLD: { diff --git a/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx b/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx new file mode 100644 index 000000000000..42532e647621 --- /dev/null +++ b/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx @@ -0,0 +1,84 @@ +import React, {useMemo} from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Modal from '@components/Modal'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; + +type PaymentCardCurrencyModalProps = { + /** Whether the modal is visible */ + isVisible: boolean; + + /** The list of years to render */ + currencies: Array; + + /** Currently selected year */ + currentCurrency: keyof typeof CONST.CURRENCY; + + /** Function to call when the user selects a year */ + onCurrencyChange?: (currency: keyof typeof CONST.CURRENCY) => void; + + /** Function to call when the user closes the year picker */ + onClose?: () => void; +}; + +function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONST.CURRENCY.USD, onCurrencyChange, onClose}: PaymentCardCurrencyModalProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {sections} = useMemo( + () => ({ + sections: [ + { + data: currencies.map((currency) => ({ + text: currency, + value: currency, + keyForList: currency, + isSelected: currency === currentCurrency, + })), + }, + ], + }), + [currencies, currentCurrency], + ); + + return ( + onClose?.()} + onModalHide={onClose} + hideModalContentWhileAnimating + useNativeDriver + > + + + { + onCurrencyChange?.(option.value); + }} + initiallyFocusedOptionKey={currentCurrency} + showScrollIndicator + shouldStopPropagation + shouldUseDynamicMaxToRenderPerBatch + ListItem={RadioListItem} + /> + + + ); +} + +PaymentCardCurrencyModal.displayName = 'PaymentCardCurrencyModal'; + +export default PaymentCardCurrencyModal; diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx new file mode 100644 index 000000000000..47b0e37e08fe --- /dev/null +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -0,0 +1,274 @@ +import {useRoute} from '@react-navigation/native'; +import React, {useCallback, useRef, useState} from 'react'; +import type {ReactNode} from 'react'; +import {View} from 'react-native'; +import AddressSearch from '@components/AddressSearch'; +import CheckboxWithLabel from '@components/CheckboxWithLabel'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import Hoverable from '@components/Hoverable'; +import * as Expensicons from '@components/Icon/Expensicons'; +import type {AnimatedTextInputRef} from '@components/RNTextInput'; +import StateSelector from '@components/StateSelector'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/AddDebitCardForm'; +import PaymentCardCurrencyModal from './PaymentCardCurrencyModal'; + +type PaymentCardFormProps = { + shouldShowPaymentCardForm?: boolean; + showAcceptTerms?: boolean; + showAddressField?: boolean; + showCurrencyField?: boolean; + showStateSelector?: boolean; + isDebitCard?: boolean; + addPaymentCard: (values: FormOnyxValues) => void; + submitButtonText: string; + /** Custom content to display in the footer after card form */ + footerContent?: ReactNode; + /** Custom content to display in the footer before card form */ + headerContent?: ReactNode; +}; + +function IAcceptTheLabel() { + const {translate} = useLocalize(); + + return ( + + {`${translate('common.iAcceptThe')}`} + {`${translate('common.addCardTermsOfService')}`} {`${translate('common.and')}`} + {` ${translate('common.privacyPolicy')} `} + + ); +} + +function IAcceptDebitTheLabel() { + const {translate} = useLocalize(); + + return ( + + {`${translate('common.iAcceptThe')}`} + {`${translate('common.expensifyTermsOfService')}`} + + ); +} + +const REQUIRED_FIELDS = [ + INPUT_IDS.NAME_ON_CARD, + INPUT_IDS.CARD_NUMBER, + INPUT_IDS.EXPIRATION_DATE, + INPUT_IDS.ADDRESS_STREET, + INPUT_IDS.SECURITY_CODE, + INPUT_IDS.ADDRESS_ZIP_CODE, + INPUT_IDS.ADDRESS_STATE, +]; + +function PaymentCardForm({ + shouldShowPaymentCardForm, + addPaymentCard, + showAcceptTerms, + showAddressField, + showCurrencyField, + isDebitCard, + submitButtonText, + showStateSelector, + footerContent, + headerContent, +}: PaymentCardFormProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const route = useRoute(); + + const cardNumberRef = useRef(null); + + const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); + const [currency, setCurrency] = useState(CONST.CURRENCY.USD); + + const validate = (formValues: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(formValues, REQUIRED_FIELDS); + + if (formValues.nameOnCard && !ValidationUtils.isValidLegalName(formValues.nameOnCard)) { + errors.nameOnCard = 'addDebitCardPage.error.invalidName'; + } + + if (formValues.cardNumber && !ValidationUtils.isValidDebitCard(formValues.cardNumber.replace(/ /g, ''))) { + errors.cardNumber = 'addDebitCardPage.error.debitCardNumber'; + } + + if (formValues.expirationDate && !ValidationUtils.isValidExpirationDate(formValues.expirationDate)) { + errors.expirationDate = 'addDebitCardPage.error.expirationDate'; + } + + if (formValues.securityCode && !ValidationUtils.isValidSecurityCode(formValues.securityCode)) { + errors.securityCode = 'addDebitCardPage.error.securityCode'; + } + + if (formValues.addressStreet && !ValidationUtils.isValidAddress(formValues.addressStreet)) { + errors.addressStreet = 'addDebitCardPage.error.addressStreet'; + } + + if (formValues.addressZipCode && !ValidationUtils.isValidZipCode(formValues.addressZipCode)) { + errors.addressZipCode = 'addDebitCardPage.error.addressZipCode'; + } + + if (!formValues.acceptTerms) { + errors.acceptTerms = 'common.error.acceptTerms'; + } + + return errors; + }; + + const showCurrenciesModal = useCallback(() => { + setIsCurrencyModalVisible(true); + }, []); + + const changeCurrency = useCallback((newCurrency: keyof typeof CONST.CURRENCY) => { + setCurrency(newCurrency); + setIsCurrencyModalVisible(false); + }, []); + + if (!shouldShowPaymentCardForm) { + return null; + } + + return ( + <> + {headerContent} + + + + + + + + + + + + {!!showAddressField && ( + + )} + + {!!showStateSelector && ( + + + + )} + {!!showCurrencyField && ( + + {(isHovered) => ( + + )} + + )} + {!!showAcceptTerms && ( + + + + )} + + } + currentCurrency={currency} + onCurrencyChange={changeCurrency} + onClose={() => setIsCurrencyModalVisible(false)} + /> + {footerContent} + + + ); +} + +PaymentCardForm.displayName = 'PaymentCardForm'; + +export default PaymentCardForm; diff --git a/src/components/Section/IconSection.tsx b/src/components/Section/IconSection.tsx index cc42c6b7ace5..ea98794017b4 100644 --- a/src/components/Section/IconSection.tsx +++ b/src/components/Section/IconSection.tsx @@ -3,14 +3,20 @@ import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import Icon from '@components/Icon'; import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; import type IconAsset from '@src/types/utils/IconAsset'; type IconSectionProps = { icon?: IconAsset; iconContainerStyles?: StyleProp; + /** The width of the icon. */ + width?: number; + + /** The height of the icon. */ + height?: number; }; -function IconSection({icon, iconContainerStyles}: IconSectionProps) { +function IconSection({icon, iconContainerStyles, width = variables.iconSection, height = variables.iconSection}: IconSectionProps) { const styles = useThemeStyles(); return ( @@ -18,8 +24,8 @@ function IconSection({icon, iconContainerStyles}: IconSectionProps) { {!!icon && ( )} diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 9d6e6cbbd41f..8d65bf2c8a1b 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -22,12 +22,12 @@ const CARD_LAYOUT = { ICON_ON_RIGHT: 'iconOnRight', } as const; -type SectionProps = ChildrenProps & { +type SectionProps = Partial & { /** An array of props that are passed to individual MenuItem components */ menuItems?: MenuItemWithLink[]; /** The text to display in the title of the section */ - title: string; + title?: string; /** The text to display in the subtitle of the section */ subtitle?: string; @@ -76,6 +76,15 @@ type SectionProps = ChildrenProps & { /** The component to display in the title of the section */ renderSubtitle?: () => ReactNode; + + /** The component to display custom title */ + renderTitle?: () => ReactNode; + + /** The width of the icon. */ + iconWidth?: number; + + /** The height of the icon. */ + iconHeight?: number; }; function Section({ @@ -90,6 +99,7 @@ function Section({ subtitleStyles, subtitleMuted = false, title, + renderTitle, titleStyles, isCentralPane = false, illustration, @@ -97,6 +107,8 @@ function Section({ illustrationStyle, contentPaddingOnLargeScreens, overlayContent, + iconWidth, + iconHeight, renderSubtitle, }: SectionProps) { const styles = useThemeStyles(); @@ -110,6 +122,8 @@ function Section({ {cardLayout === CARD_LAYOUT.ICON_ON_TOP && ( @@ -132,15 +146,17 @@ function Section({ {cardLayout === CARD_LAYOUT.ICON_ON_LEFT && ( )} - - {title} - + {renderTitle ? renderTitle() : {title}} {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && ( diff --git a/src/languages/en.ts b/src/languages/en.ts index c69531a7ab13..c7eab353532a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -165,6 +165,7 @@ export default { continue: 'Continue', firstName: 'First name', lastName: 'Last name', + addCardTermsOfService: 'Expensify Terms of Service', phone: 'Phone', phoneNumber: 'Phone number', phoneNumberPlaceholder: '(xxx) xxx-xxxx', @@ -172,6 +173,7 @@ export default { and: 'and', details: 'Details', privacy: 'Privacy', + privacyPolicy: 'Privacy Policy', hidden: 'Hidden', visible: 'Visible', delete: 'Delete', @@ -1889,6 +1891,14 @@ export default { error: 'You must accept the Terms & Conditions for travel to continue', }, }, + subscription: { + paymentCard: { + addPaymentCard: 'Add payment card', + enterPaymentCardDetails: 'Enter your payment card details.', + security: 'Expensify is PCI-DSS compliant, uses bank-level encryption, and utilizes redundant infrastructure to protect your data.', + learnMoreAboutSecurity: 'Learn more about our security.', + }, + }, workspace: { common: { card: 'Cards', diff --git a/src/languages/es.ts b/src/languages/es.ts index 3e8a4a00d4f2..4484fe503260 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -148,6 +148,8 @@ export default { preferences: 'Preferencias', view: 'Ver', not: 'No', + privacyPolicy: 'la Política de Privacidad de Expensify', + addCardTermsOfService: 'Términos de Servicio', signIn: 'Conectarse', signInWithGoogle: 'Iniciar sesión con Google', signInWithApple: 'Iniciar sesión con Apple', @@ -1913,6 +1915,14 @@ export default { error: 'Debes aceptar los Términos y condiciones para que el viaje continúe', }, }, + subscription: { + paymentCard: { + addPaymentCard: 'Añade tarjeta de pago', + enterPaymentCardDetails: 'Introduce los datos de tu tarjeta de pago.', + security: 'Expensify es PCI-DSS obediente, utiliza cifrado a nivel bancario, y emplea infraestructura redundante para proteger tus datos.', + learnMoreAboutSecurity: 'Conozca más sobre nuestra seguridad.', + }, + }, workspace: { common: { card: 'Tarjetas', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 67890a132d2d..0377423f6efa 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -331,6 +331,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/taxes/ValuePage').default as React.ComponentType, [SCREENS.WORKSPACE.TAX_CREATE]: () => require('../../../../pages/workspace/taxes/WorkspaceCreateTaxPage').default as React.ComponentType, [SCREENS.SETTINGS.SAVE_THE_WORLD]: () => require('../../../../pages/TeachersUnite/SaveTheWorldPage').default as React.ComponentType, + [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD]: () => require('../../../../pages/settings/Subscription/PaymentCard/AddPaymentCard').default as React.ComponentType, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 4e77edeaa633..af21a02f7da4 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -39,7 +39,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.SETTINGS.SAVE_THE_WORLD]: [SCREENS.I_KNOW_A_TEACHER, SCREENS.INTRO_SCHOOL_PRINCIPAL, SCREENS.I_AM_A_TEACHER], [SCREENS.SETTINGS.TROUBLESHOOT]: [SCREENS.SETTINGS.CONSOLE], [SCREENS.SEARCH.CENTRAL_PANE]: [SCREENS.SEARCH.REPORT_RHP], - [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [SCREENS.SETTINGS.SUBSCRIPTION.SIZE], + [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD, SCREENS.SETTINGS.SUBSCRIPTION.SIZE], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index b19fbc4c38e0..b05678423c77 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -126,6 +126,10 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_LANGUAGE, exact: true, }, + [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD]: { + path: ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD, + exact: true, + }, [SCREENS.SETTINGS.PREFERENCES.THEME]: { path: ROUTES.SETTINGS_THEME, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c9c83bb2cc11..bc039230bd6a 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -241,6 +241,7 @@ type SettingsNavigatorParamList = { orderWeight: number; tagName: string; }; + [SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD]: undefined; [SCREENS.WORKSPACE.TAXES_SETTINGS]: { policyID: string; }; diff --git a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx new file mode 100644 index 000000000000..c0933e34e096 --- /dev/null +++ b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx @@ -0,0 +1,102 @@ +import React, {useCallback, useEffect} from 'react'; +import {View} from 'react-native'; +import PaymentCardForm from '@components/AddPaymentCard/PaymentCardForm'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Illustrations from '@components/Icon/Illustrations'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Section, {CARD_LAYOUT} from '@components/Section'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as CardUtils from '@libs/CardUtils'; +import Navigation from '@navigation/Navigation'; +import variables from '@styles/variables'; +import * as PaymentMethods from '@userActions/PaymentMethods'; +import * as PolicyActions from '@userActions/Policy'; +import CONST from '@src/CONST'; +import type ONYXKEYS from '@src/ONYXKEYS'; + +function AddPaymentCard() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const policyID = '1'; + + useEffect(() => { + PaymentMethods.clearDebitCardFormErrorAndSubmit(); + + return () => { + PaymentMethods.clearDebitCardFormErrorAndSubmit(); + }; + }, []); + + const addPaymentCard = useCallback( + (values: FormOnyxValues) => { + const cardData = { + cardNumber: values.cardNumber, + cardMonth: CardUtils.getMonthFromExpirationDateString(values.expirationDate), + cardYear: CardUtils.getYearFromExpirationDateString(values.expirationDate), + cardCVV: values.securityCode, + addressName: values.nameOnCard, + addressZip: values.addressZipCode, + currency: CONST.CURRENCY.USD, + }; + + PolicyActions.addBillingCardAndRequestPolicyOwnerChange(policyID, cardData); + }, + [policyID], + ); + + return ( + + { + Navigation.goBack(); + }} + /> + + {translate('subscription.paymentCard.enterPaymentCardDetails')}} + footerContent={ + <> +
( + + {translate('subscription.paymentCard.security')}{' '} + + {translate('subscription.paymentCard.learnMoreAboutSecurity')} + + + )} + /> + {/** TODO will be replaced after PR merged */} + + From $5/active member with the Expensify Card, $10/active member without the Expensify Card. + + + } + /> + + + ); +} + +AddPaymentCard.displayName = 'AddPaymentCard'; + +export default AddPaymentCard; diff --git a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx index 1a2f32449c41..31e40473d33f 100644 --- a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx +++ b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx @@ -1,47 +1,33 @@ -import React, {useCallback, useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import Hoverable from '@components/Hoverable'; +import PaymentCardForm from '@components/AddPaymentCard/PaymentCardForm'; +import type {FormOnyxValues} from '@components/Form/types'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; -import type {AnimatedTextInputRef} from '@components/RNTextInput'; import Section, {CARD_LAYOUT} from '@components/Section'; import Text from '@components/Text'; -import TextInput from '@components/TextInput'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CardUtils from '@libs/CardUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/AddDebitCardForm'; +import type ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import WorkspaceOwnerPaymentCardCurrencyModal from './WorkspaceOwnerPaymentCardCurrencyModal'; type WorkspaceOwnerPaymentCardFormProps = { /** The policy */ policy: OnyxEntry; }; -const REQUIRED_FIELDS = [INPUT_IDS.NAME_ON_CARD, INPUT_IDS.CARD_NUMBER, INPUT_IDS.EXPIRATION_DATE, INPUT_IDS.ADDRESS_STREET, INPUT_IDS.SECURITY_CODE, INPUT_IDS.ADDRESS_ZIP_CODE]; - function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormProps) { - const styles = useThemeStyles(); - const theme = useTheme(); const {translate} = useLocalize(); - - const cardNumberRef = useRef(null); - - const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false); - const [currency, setCurrency] = useState(CONST.CURRENCY.USD); + const theme = useTheme(); + const styles = useThemeStyles(); const [shouldShowPaymentCardForm, setShouldShowPaymentCardForm] = useState(false); const policyID = policy?.id ?? ''; @@ -72,36 +58,6 @@ function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormPr checkIfCanBeRendered(); }, [checkIfCanBeRendered]); - const validate = (formValues: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(formValues, REQUIRED_FIELDS); - - if (formValues.nameOnCard && !ValidationUtils.isValidLegalName(formValues.nameOnCard)) { - errors.nameOnCard = 'addDebitCardPage.error.invalidName'; - } - - if (formValues.cardNumber && !ValidationUtils.isValidDebitCard(formValues.cardNumber.replace(/ /g, ''))) { - errors.cardNumber = 'addDebitCardPage.error.debitCardNumber'; - } - - if (formValues.expirationDate && !ValidationUtils.isValidExpirationDate(formValues.expirationDate)) { - errors.expirationDate = 'addDebitCardPage.error.expirationDate'; - } - - if (formValues.securityCode && !ValidationUtils.isValidSecurityCode(formValues.securityCode)) { - errors.securityCode = 'addDebitCardPage.error.securityCode'; - } - - if (formValues.addressStreet && !ValidationUtils.isValidAddress(formValues.addressStreet)) { - errors.addressStreet = 'addDebitCardPage.error.addressStreet'; - } - - if (formValues.addressZipCode && !ValidationUtils.isValidZipCode(formValues.addressZipCode)) { - errors.addressZipCode = 'addDebitCardPage.error.addressZipCode'; - } - - return errors; - }; - const addPaymentCard = useCallback( (values: FormOnyxValues) => { const cardData = { @@ -119,174 +75,78 @@ function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormPr [policyID], ); - const showCurrenciesModal = useCallback(() => { - setIsCurrencyModalVisible(true); - }, []); - - const changeCurrency = useCallback((newCurrency: keyof typeof CONST.CURRENCY) => { - setCurrency(newCurrency); - setIsCurrencyModalVisible(false); - }, []); - - if (!shouldShowPaymentCardForm) { - return null; - } - return ( - <> - {translate('workspace.changeOwner.addPaymentCardTitle')} - - - - - - - - - - - - - - - - - - {(isHovered) => ( - - )} - - - - - } - currentCurrency={currency} - onCurrencyChange={changeCurrency} - onClose={() => setIsCurrencyModalVisible(false)} - /> - - - {translate('workspace.changeOwner.addPaymentCardReadAndAcceptTextPart1')}{' '} - - {translate('workspace.changeOwner.addPaymentCardTerms')} - {' '} - {translate('workspace.changeOwner.addPaymentCardAnd')}{' '} - - {translate('workspace.changeOwner.addPaymentCardPrivacy')} - {' '} - {translate('workspace.changeOwner.addPaymentCardReadAndAcceptTextPart2')} - -
- - - - {translate('workspace.changeOwner.addPaymentCardPciCompliant')} - - - - {translate('workspace.changeOwner.addPaymentCardBankLevelEncrypt')} - - - - {translate('workspace.changeOwner.addPaymentCardRedundant')} - - - - {translate('workspace.changeOwner.addPaymentCardLearnMore')}{' '} + {translate('workspace.changeOwner.addPaymentCardTitle')}} + footerContent={ + <> + + {translate('workspace.changeOwner.addPaymentCardReadAndAcceptTextPart1')}{' '} - {translate('workspace.changeOwner.addPaymentCardSecurity')} - - . + {translate('workspace.changeOwner.addPaymentCardTerms')} + {' '} + {translate('workspace.changeOwner.addPaymentCardAnd')}{' '} + + {translate('workspace.changeOwner.addPaymentCardPrivacy')} + {' '} + {translate('workspace.changeOwner.addPaymentCardReadAndAcceptTextPart2')} -
-
- +
+ + + + {translate('workspace.changeOwner.addPaymentCardPciCompliant')} + + + + {translate('workspace.changeOwner.addPaymentCardBankLevelEncrypt')} + + + + {translate('workspace.changeOwner.addPaymentCardRedundant')} + + + + {translate('workspace.changeOwner.addPaymentCardLearnMore')}{' '} + + {translate('workspace.changeOwner.addPaymentCardSecurity')} + + . + +
+ + } + /> ); } diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 6f1cac46d729..0acab4e86ffc 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -86,6 +86,7 @@ export default { iconBottomBar: 24, sidebarAvatarSize: 28, iconHeader: 48, + iconSection: 68, emojiSize: 20, emojiLineHeight: 28, iouAmountTextSize: 40, From fd98b53f6d27f84e9378cf5c81e4d6b50b289885 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 29 May 2024 18:56:10 +0300 Subject: [PATCH 02/17] fix ts --- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx index c0933e34e096..d568061b4074 100644 --- a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx +++ b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx @@ -14,7 +14,7 @@ import * as CardUtils from '@libs/CardUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as PolicyActions from '@userActions/Policy'; +import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; From 75964e2960656d4cb10a48278a474ad7f58e9a29 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 31 May 2024 11:47:00 +0300 Subject: [PATCH 03/17] Migrate debit card page for wallet to use one standard add card component --- .../AddPaymentCard/PaymentCardForm.tsx | 110 +++++++--- src/languages/en.ts | 23 +++ src/languages/es.ts | 23 +++ .../AddSubscriptionPaymentCardParams.ts | 11 + src/libs/API/parameters/index.ts | 1 + src/libs/actions/PaymentMethods.ts | 60 ++++++ .../PaymentCard/AddPaymentCard.tsx | 48 ++--- .../settings/Wallet/AddDebitCardPage.tsx | 194 ++---------------- ...WorkspaceOwnerPaymentCardCurrencyModal.tsx | 84 -------- 9 files changed, 236 insertions(+), 318 deletions(-) create mode 100644 src/libs/API/parameters/AddSubscriptionPaymentCardParams.ts delete mode 100644 src/pages/workspace/members/WorkspaceOwnerPaymentCardCurrencyModal.tsx diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 47b0e37e08fe..bb96ac48b406 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -2,6 +2,7 @@ import {useRoute} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; import type {ReactNode} from 'react'; import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; import AddressSearch from '@components/AddressSearch'; import CheckboxWithLabel from '@components/CheckboxWithLabel'; import FormProvider from '@components/Form/FormProvider'; @@ -18,6 +19,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -31,7 +33,7 @@ type PaymentCardFormProps = { showCurrencyField?: boolean; showStateSelector?: boolean; isDebitCard?: boolean; - addPaymentCard: (values: FormOnyxValues) => void; + addPaymentCard: (values: FormOnyxValues, currency?: ValueOf) => void; submitButtonText: string; /** Custom content to display in the footer after card form */ footerContent?: ReactNode; @@ -72,6 +74,59 @@ const REQUIRED_FIELDS = [ INPUT_IDS.ADDRESS_STATE, ]; +const CARD_TYPES = { + DEBIT_CARD: 'debit', + PAYMENT_CARD: 'payment', +}; + +const CARD_TYPE_SECTIONS = { + DEFAULTS: 'defaults', + ERROR: 'error', +}; +type CartTypesMap = (typeof CARD_TYPES)[keyof typeof CARD_TYPES]; +type CartTypeSectionsMap = (typeof CARD_TYPE_SECTIONS)[keyof typeof CARD_TYPE_SECTIONS]; + +type CardLabels = Record>>; + +const CARD_LABELS: CardLabels = { + [CARD_TYPES.DEBIT_CARD]: { + [CARD_TYPE_SECTIONS.DEFAULTS]: { + cardNumber: 'addDebitCardPage.debitCardNumber', + nameOnCard: 'addDebitCardPage.nameOnCard', + expirationDate: 'addDebitCardPage.expirationDate', + expiration: 'addDebitCardPage.expiration', + securityCode: 'addDebitCardPage.cvv', + billingAddress: 'addDebitCardPage.billingAddress', + }, + [CARD_TYPE_SECTIONS.ERROR]: { + nameOnCard: 'addDebitCardPage.error.invalidName', + cardNumber: 'addDebitCardPage.error.debitCardNumber', + expirationDate: 'addDebitCardPage.error.expirationDate', + securityCode: 'addDebitCardPage.error.securityCode', + addressStreet: 'addDebitCardPage.error.addressStreet', + addressZipCode: 'addDebitCardPage.error.addressZipCode', + }, + }, + [CARD_TYPES.PAYMENT_CARD]: { + defaults: { + cardNumber: 'addPaymentCardPage.paymentCardNumber', + nameOnCard: 'addPaymentCardPage.nameOnCard', + expirationDate: 'addPaymentCardPage.expirationDate', + expiration: 'addPaymentCardPage.expiration', + securityCode: 'addPaymentCardPage.cvv', + billingAddress: 'addPaymentCardPage.billingAddress', + }, + error: { + nameOnCard: 'addPaymentCardPage.error.invalidName', + cardNumber: 'addPaymentCardPage.error.paymentCardNumber', + expirationDate: 'addPaymentCardPage.error.expirationDate', + securityCode: 'addPaymentCardPage.error.securityCode', + addressStreet: 'addPaymentCardPage.error.addressStreet', + addressZipCode: 'addPaymentCardPage.error.addressZipCode', + }, + }, +}; + function PaymentCardForm({ shouldShowPaymentCardForm, addPaymentCard, @@ -87,6 +142,7 @@ function PaymentCardForm({ const styles = useThemeStyles(); const {translate} = useLocalize(); const route = useRoute(); + const label = CARD_LABELS[isDebitCard ? CARD_TYPES.DEBIT_CARD : CARD_TYPES.PAYMENT_CARD]; const cardNumberRef = useRef(null); @@ -97,27 +153,27 @@ function PaymentCardForm({ const errors = ValidationUtils.getFieldRequiredErrors(formValues, REQUIRED_FIELDS); if (formValues.nameOnCard && !ValidationUtils.isValidLegalName(formValues.nameOnCard)) { - errors.nameOnCard = 'addDebitCardPage.error.invalidName'; + errors.nameOnCard = label.error.nameOnCard; } if (formValues.cardNumber && !ValidationUtils.isValidDebitCard(formValues.cardNumber.replace(/ /g, ''))) { - errors.cardNumber = 'addDebitCardPage.error.debitCardNumber'; + errors.cardNumber = label.error.cardNumber; } if (formValues.expirationDate && !ValidationUtils.isValidExpirationDate(formValues.expirationDate)) { - errors.expirationDate = 'addDebitCardPage.error.expirationDate'; + errors.expirationDate = label.error.expirationDate; } if (formValues.securityCode && !ValidationUtils.isValidSecurityCode(formValues.securityCode)) { - errors.securityCode = 'addDebitCardPage.error.securityCode'; + errors.securityCode = label.error.securityCode; } if (formValues.addressStreet && !ValidationUtils.isValidAddress(formValues.addressStreet)) { - errors.addressStreet = 'addDebitCardPage.error.addressStreet'; + errors.addressStreet = label.error.addressStreet; } if (formValues.addressZipCode && !ValidationUtils.isValidZipCode(formValues.addressZipCode)) { - errors.addressZipCode = 'addDebitCardPage.error.addressZipCode'; + errors.addressZipCode = label.error.addressZipCode; } if (!formValues.acceptTerms) { @@ -146,7 +202,7 @@ function PaymentCardForm({ addPaymentCard(formData, currency)} submitButtonText={submitButtonText} scrollContextEnabled style={[styles.mh5, styles.flexGrow1]} @@ -154,8 +210,8 @@ function PaymentCardForm({ @@ -186,8 +242,8 @@ function PaymentCardForm({ {!!showAddressField && ( - + + + )} ; +}) { + const {cardNumber, cardYear, cardMonth, cardCVV, addressName, addressZip, currency} = cardData; + + const parameters: AddPaymentCardParams = { + cardNumber, + cardYear, + cardMonth, + cardCVV, + addressName, + addressZip, + currency, + isP2PDebitCard: false, + }; + + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, + value: {isLoading: true}, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, + value: {isLoading: false}, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, + value: {isLoading: false}, + }, + ]; + + // TODO integrate API for subscription card as a follow up + API.write(WRITE_COMMANDS.ADD_PAYMENT_CARD, parameters, { + optimisticData, + successData, + failureData, + }); +} + /** * Resets the values for the add debit card form back to their initial states */ @@ -373,6 +432,7 @@ export { makeDefaultPaymentMethod, kycWallRef, continueSetup, + addSubscriptionPaymentCard, clearDebitCardFormErrorAndSubmit, dismissSuccessfulTransferBalancePage, transferWalletBalance, diff --git a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx index d568061b4074..88aaf12966c0 100644 --- a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx +++ b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx @@ -1,5 +1,6 @@ import React, {useCallback, useEffect} from 'react'; import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; import PaymentCardForm from '@components/AddPaymentCard/PaymentCardForm'; import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -11,17 +12,14 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CardUtils from '@libs/CardUtils'; -import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; function AddPaymentCard() { const styles = useThemeStyles(); const {translate} = useLocalize(); - const policyID = '1'; useEffect(() => { PaymentMethods.clearDebitCardFormErrorAndSubmit(); @@ -31,31 +29,27 @@ function AddPaymentCard() { }; }, []); - const addPaymentCard = useCallback( - (values: FormOnyxValues) => { - const cardData = { - cardNumber: values.cardNumber, - cardMonth: CardUtils.getMonthFromExpirationDateString(values.expirationDate), - cardYear: CardUtils.getYearFromExpirationDateString(values.expirationDate), - cardCVV: values.securityCode, - addressName: values.nameOnCard, - addressZip: values.addressZipCode, - currency: CONST.CURRENCY.USD, - }; - - PolicyActions.addBillingCardAndRequestPolicyOwnerChange(policyID, cardData); - }, - [policyID], - ); + // TODO refactor ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM to ONYXKEYS.FORMS.ADD_CARD_FORM as a follow up + const addPaymentCard = useCallback((values: FormOnyxValues, currency?: ValueOf) => { + const cardData = { + cardNumber: values.cardNumber, + cardMonth: CardUtils.getMonthFromExpirationDateString(values.expirationDate), + cardYear: CardUtils.getYearFromExpirationDateString(values.expirationDate), + cardCVV: values.securityCode, + addressName: values.nameOnCard, + addressZip: values.addressZipCode, + currency: currency ?? CONST.CURRENCY.USD, + }; + if (currency === CONST.CURRENCY.GBP) { + // TODO add AddPaymentCardGBP flow as a follow up + return; + } + PaymentMethods.addSubscriptionPaymentCard(cardData); + }, []); return ( - { - Navigation.goBack(); - }} - /> + )} /> - {/** TODO will be replaced after PR merged */} - + {/** TODO reusable component will be taken from https://github.com/Expensify/App/pull/42690 */} + From $5/active member with the Expensify Card, $10/active member without the Expensify Card. diff --git a/src/pages/settings/Wallet/AddDebitCardPage.tsx b/src/pages/settings/Wallet/AddDebitCardPage.tsx index 0beb3c16018d..0befaa55da52 100644 --- a/src/pages/settings/Wallet/AddDebitCardPage.tsx +++ b/src/pages/settings/Wallet/AddDebitCardPage.tsx @@ -1,67 +1,20 @@ -import {useRoute} from '@react-navigation/native'; import React, {useEffect, useRef} from 'react'; -import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import AddressSearch from '@components/AddressSearch'; -import CheckboxWithLabel from '@components/CheckboxWithLabel'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import {useOnyx} from 'react-native-onyx'; +import PaymentCardForm from '@components/AddPaymentCard/PaymentCardForm'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import ScreenWrapper from '@components/ScreenWrapper'; -import StateSelector from '@components/StateSelector'; -import Text from '@components/Text'; -import TextInput from '@components/TextInput'; -import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as ValidationUtils from '@libs/ValidationUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; -import type {AddDebitCardForm} from '@src/types/form'; -import INPUT_IDS from '@src/types/form/AddDebitCardForm'; -type DebitCardPageOnyxProps = { - /** Form data propTypes */ - formData: OnyxEntry; -}; - -type DebitCardPageProps = DebitCardPageOnyxProps; - -function IAcceptTheLabel() { - const {translate} = useLocalize(); - - return ( - - {`${translate('common.iAcceptThe')}`} - {`${translate('common.expensifyTermsOfService')}`} - - ); -} - -const REQUIRED_FIELDS = [ - INPUT_IDS.NAME_ON_CARD, - INPUT_IDS.CARD_NUMBER, - INPUT_IDS.EXPIRATION_DATE, - INPUT_IDS.SECURITY_CODE, - INPUT_IDS.ADDRESS_STREET, - INPUT_IDS.ADDRESS_ZIP_CODE, - INPUT_IDS.ADDRESS_STATE, -]; - -function DebitCardPage({formData}: DebitCardPageProps) { - const styles = useThemeStyles(); +function DebitCardPage() { const {translate} = useLocalize(); + const [formData] = useOnyx(ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM); const prevFormDataSetupComplete = usePrevious(!!formData?.setupComplete); const nameOnCardRef = useRef(null); - const route = useRoute(); /** * Reset the form values on the mount and unmount so that old errors don't show when this form is displayed again. @@ -82,43 +35,6 @@ function DebitCardPage({formData}: DebitCardPageProps) { PaymentMethods.continueSetup(); }, [prevFormDataSetupComplete, formData?.setupComplete]); - /** - * @param values - form input values passed by the Form component - */ - const validate = (values: FormOnyxValues): FormInputErrors => { - const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS); - - if (values.nameOnCard && !ValidationUtils.isValidLegalName(values.nameOnCard)) { - errors.nameOnCard = 'addDebitCardPage.error.invalidName'; - } - - if (values.cardNumber && !ValidationUtils.isValidDebitCard(values.cardNumber.replace(/ /g, ''))) { - errors.cardNumber = 'addDebitCardPage.error.debitCardNumber'; - } - - if (values.expirationDate && !ValidationUtils.isValidExpirationDate(values.expirationDate)) { - errors.expirationDate = 'addDebitCardPage.error.expirationDate'; - } - - if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) { - errors.securityCode = 'addDebitCardPage.error.securityCode'; - } - - if (values.addressStreet && !ValidationUtils.isValidAddress(values.addressStreet)) { - errors.addressStreet = 'addDebitCardPage.error.addressStreet'; - } - - if (values.addressZipCode && !ValidationUtils.isValidZipCode(values.addressZipCode)) { - errors.addressZipCode = 'addDebitCardPage.error.addressZipCode'; - } - - if (!values.acceptTerms) { - errors.acceptTerms = 'common.error.acceptTerms'; - } - - return errors; - }; - return ( nameOnCardRef.current?.focus()} @@ -129,103 +45,19 @@ function DebitCardPage({formData}: DebitCardPageProps) { title={translate('addDebitCardPage.addADebitCard')} onBackButtonPress={() => Navigation.goBack()} /> - - - - - - - - - - - - - - - - - - - - + /> ); } DebitCardPage.displayName = 'DebitCardPage'; -export default withOnyx({ - formData: { - key: ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM, - }, -})(DebitCardPage); +export default DebitCardPage; diff --git a/src/pages/workspace/members/WorkspaceOwnerPaymentCardCurrencyModal.tsx b/src/pages/workspace/members/WorkspaceOwnerPaymentCardCurrencyModal.tsx deleted file mode 100644 index fcbbbbd4af3f..000000000000 --- a/src/pages/workspace/members/WorkspaceOwnerPaymentCardCurrencyModal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, {useMemo} from 'react'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import Modal from '@components/Modal'; -import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; -import RadioListItem from '@components/SelectionList/RadioListItem'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; - -type WorkspaceOwnerPaymentCardCurrencyModalProps = { - /** Whether the modal is visible */ - isVisible: boolean; - - /** The list of years to render */ - currencies: Array; - - /** Currently selected year */ - currentCurrency: keyof typeof CONST.CURRENCY; - - /** Function to call when the user selects a year */ - onCurrencyChange?: (currency: keyof typeof CONST.CURRENCY) => void; - - /** Function to call when the user closes the year picker */ - onClose?: () => void; -}; - -function WorkspaceOwnerPaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONST.CURRENCY.USD, onCurrencyChange, onClose}: WorkspaceOwnerPaymentCardCurrencyModalProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const {sections} = useMemo( - () => ({ - sections: [ - { - data: currencies.map((currency) => ({ - text: currency, - value: currency, - keyForList: currency, - isSelected: currency === currentCurrency, - })), - }, - ], - }), - [currencies, currentCurrency], - ); - - return ( - onClose?.()} - onModalHide={onClose} - hideModalContentWhileAnimating - useNativeDriver - > - - - { - onCurrencyChange?.(option.value); - }} - initiallyFocusedOptionKey={currentCurrency} - showScrollIndicator - shouldStopPropagation - shouldUseDynamicMaxToRenderPerBatch - ListItem={RadioListItem} - /> - - - ); -} - -WorkspaceOwnerPaymentCardCurrencyModal.displayName = 'WorkspaceOwnerPaymentCardCurrencyModal'; - -export default WorkspaceOwnerPaymentCardCurrencyModal; From 8bde82a6720f7812c49f3298c0a6dfebffe7e802 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 31 May 2024 11:50:02 +0300 Subject: [PATCH 04/17] nit --- src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx index 31e40473d33f..413717c3610b 100644 --- a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx +++ b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx @@ -80,6 +80,7 @@ function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormPr shouldShowPaymentCardForm={shouldShowPaymentCardForm} addPaymentCard={addPaymentCard} showCurrencyField + isDebitCard submitButtonText={translate('workspace.changeOwner.addPaymentCardButtonText')} headerContent={{translate('workspace.changeOwner.addPaymentCardTitle')}} footerContent={ From f7271b521f32976ebbb2c8f82ec35831853fc693 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 31 May 2024 12:05:55 +0300 Subject: [PATCH 05/17] add navigation --- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx index 88aaf12966c0..f0648579cfd1 100644 --- a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx +++ b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx @@ -12,6 +12,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CardUtils from '@libs/CardUtils'; +import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; @@ -45,6 +46,7 @@ function AddPaymentCard() { return; } PaymentMethods.addSubscriptionPaymentCard(cardData); + Navigation.goBack(); }, []); return ( From 32b73b589418bd55c2e04d894f61635151f9afd6 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 31 May 2024 12:20:19 +0300 Subject: [PATCH 06/17] changes after design review --- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx index f0648579cfd1..1dc7a3d50024 100644 --- a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx +++ b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx @@ -52,7 +52,7 @@ function AddPaymentCard() { return ( - + Date: Fri, 31 May 2024 12:28:26 +0300 Subject: [PATCH 07/17] update owner wrapper --- src/pages/workspace/members/WorkspaceOwnerChangeWrapperPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceOwnerChangeWrapperPage.tsx b/src/pages/workspace/members/WorkspaceOwnerChangeWrapperPage.tsx index b32c04a5c4aa..bf5e8cb869e2 100644 --- a/src/pages/workspace/members/WorkspaceOwnerChangeWrapperPage.tsx +++ b/src/pages/workspace/members/WorkspaceOwnerChangeWrapperPage.tsx @@ -67,7 +67,7 @@ function WorkspaceOwnerChangeWrapperPage({route, policy}: WorkspaceOwnerChangeWr Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policyID, accountID)); }} /> - + {policy?.isLoading && } {!policy?.isLoading && (error === CONST.POLICY.OWNERSHIP_ERRORS.NO_BILLING_CARD ? ( From 32bf15feed7afc6d9f1aa5af02dcdba678998246 Mon Sep 17 00:00:00 2001 From: "mikalai.arefyev" Date: Mon, 3 Jun 2024 15:00:18 +0300 Subject: [PATCH 08/17] updates after design and c+ review --- src/components/AddPaymentCard/PaymentCardForm.tsx | 2 +- src/components/Section/IconSection.tsx | 2 +- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index bb96ac48b406..816326bf4f58 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -226,7 +226,7 @@ function PaymentCardForm({ spellCheck={false} /> - + ( From 76dbab1015b34c9bba3b191d6a34e885991d5db4 Mon Sep 17 00:00:00 2001 From: "mikalai.arefyev" Date: Mon, 3 Jun 2024 15:07:46 +0300 Subject: [PATCH 09/17] Revert "updates after design and c+ review" This reverts commit 09724ef2fd252eb2b3a6891b12f7c45b01c184e5. --- src/components/AddPaymentCard/PaymentCardForm.tsx | 2 +- src/components/Section/IconSection.tsx | 2 +- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 816326bf4f58..bb96ac48b406 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -226,7 +226,7 @@ function PaymentCardForm({ spellCheck={false} /> - + ( From 486f058ca85292e907ceeb3b15c527d9f8204f49 Mon Sep 17 00:00:00 2001 From: "mikalai.arefyev" Date: Mon, 3 Jun 2024 15:00:18 +0300 Subject: [PATCH 10/17] updates after design and c+ review --- src/components/AddPaymentCard/PaymentCardForm.tsx | 2 +- src/components/Section/IconSection.tsx | 2 +- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index bb96ac48b406..816326bf4f58 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -226,7 +226,7 @@ function PaymentCardForm({ spellCheck={false} /> - + ( From 8ddbed9f7c20b1602e11c0f570f22d69d8246218 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 3 Jun 2024 16:04:13 +0300 Subject: [PATCH 11/17] lint --- src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx index 24b22c83901c..a36072c51942 100644 --- a/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx +++ b/src/pages/settings/Subscription/PaymentCard/AddPaymentCard.tsx @@ -13,7 +13,6 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CardUtils from '@libs/CardUtils'; import Navigation from '@navigation/Navigation'; -import variables from '@styles/variables'; import * as PaymentMethods from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; From 788db6213384086eef0061f39b27f7ffb998c57a Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 3 Jun 2024 20:31:50 +0300 Subject: [PATCH 12/17] merge conflicts From 8088eb3581241d1bdd8db31e7e4fea7478ee4f5f Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 4 Jun 2024 09:51:12 +0300 Subject: [PATCH 13/17] update border radius --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index a9c9c12335a1..eec579733c34 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3649,7 +3649,7 @@ const styles = (theme: ThemeColors) => cardSectionContainer: { backgroundColor: theme.cardBG, - borderRadius: variables.componentBorderRadiusCard, + borderRadius: variables.componentBorderRadiusLarge, width: 'auto', textAlign: 'left', overflow: 'hidden', From 6f33179153e268b7573dffc4a141408897024377 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 4 Jun 2024 15:56:56 +0300 Subject: [PATCH 14/17] fix currency modal --- src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx b/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx index 42532e647621..60fa838b0577 100644 --- a/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx +++ b/src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx @@ -6,6 +6,7 @@ import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; type PaymentCardCurrencyModalProps = { @@ -26,6 +27,7 @@ type PaymentCardCurrencyModalProps = { }; function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONST.CURRENCY.USD, onCurrencyChange, onClose}: PaymentCardCurrencyModalProps) { + const {isSmallScreenWidth} = useWindowDimensions(); const styles = useThemeStyles(); const {translate} = useLocalize(); const {sections} = useMemo( @@ -51,6 +53,7 @@ function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONS onClose={() => onClose?.()} onModalHide={onClose} hideModalContentWhileAnimating + innerContainerStyle={styles.RHPNavigatorContainer(isSmallScreenWidth)} useNativeDriver > Date: Tue, 4 Jun 2024 16:18:34 +0300 Subject: [PATCH 15/17] fix ts --- src/languages/en.ts | 14 ++++++-------- src/languages/es.ts | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 577993a44aba..995f50f05475 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1914,14 +1914,6 @@ export default { error: 'You must accept the Terms & Conditions for travel to continue', }, }, - subscription: { - paymentCard: { - addPaymentCard: 'Add payment card', - enterPaymentCardDetails: 'Enter your payment card details.', - security: 'Expensify is PCI-DSS compliant, uses bank-level encryption, and utilizes redundant infrastructure to protect your data.', - learnMoreAboutSecurity: 'Learn more about our security.', - }, - }, workspace: { common: { card: 'Cards', @@ -3241,5 +3233,11 @@ export default { size: 'Please enter a valid subscription size.', }, }, + paymentCard: { + addPaymentCard: 'Add payment card', + enterPaymentCardDetails: 'Enter your payment card details.', + security: 'Expensify is PCI-DSS compliant, uses bank-level encryption, and utilizes redundant infrastructure to protect your data.', + learnMoreAboutSecurity: 'Learn more about our security.', + }, }, } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index fc889fe6de51..f35b89a5087a 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1938,14 +1938,6 @@ export default { error: 'Debes aceptar los Términos y condiciones para que el viaje continúe', }, }, - subscription: { - paymentCard: { - addPaymentCard: 'Añade tarjeta de pago', - enterPaymentCardDetails: 'Introduce los datos de tu tarjeta de pago.', - security: 'Expensify es PCI-DSS obediente, utiliza cifrado a nivel bancario, y emplea infraestructura redundante para proteger tus datos.', - learnMoreAboutSecurity: 'Conozca más sobre nuestra seguridad.', - }, - }, workspace: { common: { card: 'Tarjetas', @@ -3747,5 +3739,11 @@ export default { size: 'Por favor ingrese un tamaño de suscripción valido.', }, }, + paymentCard: { + addPaymentCard: 'Añade tarjeta de pago', + enterPaymentCardDetails: 'Introduce los datos de tu tarjeta de pago.', + security: 'Expensify es PCI-DSS obediente, utiliza cifrado a nivel bancario, y emplea infraestructura redundante para proteger tus datos.', + learnMoreAboutSecurity: 'Conozca más sobre nuestra seguridad.', + }, }, } satisfies EnglishTranslation; From 0652324011a230c1581c1a04ddd1863494fab9cb Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 5 Jun 2024 10:09:23 +0300 Subject: [PATCH 16/17] add suggested changes --- .../AddPaymentCard/PaymentCardForm.tsx | 21 +++++-------------- src/languages/en.ts | 1 - src/languages/es.ts | 1 - 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/components/AddPaymentCard/PaymentCardForm.tsx b/src/components/AddPaymentCard/PaymentCardForm.tsx index 816326bf4f58..61e9a2d1860a 100644 --- a/src/components/AddPaymentCard/PaymentCardForm.tsx +++ b/src/components/AddPaymentCard/PaymentCardForm.tsx @@ -37,7 +37,7 @@ type PaymentCardFormProps = { submitButtonText: string; /** Custom content to display in the footer after card form */ footerContent?: ReactNode; - /** Custom content to display in the footer before card form */ + /** Custom content to display in the header before card form */ headerContent?: ReactNode; }; @@ -53,17 +53,6 @@ function IAcceptTheLabel() { ); } -function IAcceptDebitTheLabel() { - const {translate} = useLocalize(); - - return ( - - {`${translate('common.iAcceptThe')}`} - {`${translate('common.expensifyTermsOfService')}`} - - ); -} - const REQUIRED_FIELDS = [ INPUT_IDS.NAME_ON_CARD, INPUT_IDS.CARD_NUMBER, @@ -226,7 +215,7 @@ function PaymentCardForm({ spellCheck={false} /> - + )} diff --git a/src/languages/en.ts b/src/languages/en.ts index 995f50f05475..451851c85f76 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -189,7 +189,6 @@ export default { saveAndContinue: 'Save & continue', settings: 'Settings', termsOfService: 'Terms of Service', - expensifyTermsOfService: 'Expensify Terms of Service', members: 'Members', invite: 'Invite', here: 'here', diff --git a/src/languages/es.ts b/src/languages/es.ts index f35b89a5087a..a4fb98a21217 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -179,7 +179,6 @@ export default { saveAndContinue: 'Guardar y continuar', settings: 'Configuración', termsOfService: 'Términos de Servicio', - expensifyTermsOfService: 'Términos de Servicio de Expensify', members: 'Miembros', invite: 'Invitar', here: 'aquí', From dcd4dfd777cc786cd3b4242b38df69abc1d7ce2c Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 5 Jun 2024 20:52:49 +0300 Subject: [PATCH 17/17] remove isdebit --- src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx index 413717c3610b..31e40473d33f 100644 --- a/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx +++ b/src/pages/workspace/members/WorkspaceOwnerPaymentCardForm.tsx @@ -80,7 +80,6 @@ function WorkspaceOwnerPaymentCardForm({policy}: WorkspaceOwnerPaymentCardFormPr shouldShowPaymentCardForm={shouldShowPaymentCardForm} addPaymentCard={addPaymentCard} showCurrencyField - isDebitCard submitButtonText={translate('workspace.changeOwner.addPaymentCardButtonText')} headerContent={{translate('workspace.changeOwner.addPaymentCardTitle')}} footerContent={