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,