Skip to content

Commit

Permalink
Merge pull request #43572 from narefyev91/add-billing-currency
Browse files Browse the repository at this point in the history
  • Loading branch information
blimpich authored Jun 26, 2024
2 parents 472d532 + 3714818 commit 64c6624
Show file tree
Hide file tree
Showing 34 changed files with 707 additions and 112 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ const CONST = {
ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/',
LIST_OF_RESTRICTED_BUSINESSES: 'https://community.expensify.com/discussion/6191/list-of-restricted-businesses',
TRAVEL_TERMS_URL: `${USE_EXPENSIFY_URL}/travelterms`,
PRICING: `https://www.expensify.com/pricing`,

// Use Environment.getEnvironmentURL to get the complete URL with port number
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
Expand Down
13 changes: 10 additions & 3 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ const ONYXKEYS = {
// Paths of PDF file that has been cached during one session
CACHED_PDF_PATHS: 'cachedPDFPaths',

/** Stores iframe link to verify 3DS flow for subscription */
VERIFY_3DS_SUBSCRIPTION: 'verify3dsSubscription',

/** Holds the checks used while transferring the ownership of the workspace */
POLICY_OWNERSHIP_CHANGE_CHECKS: 'policyOwnershipChangeChecks',

Expand Down Expand Up @@ -405,8 +408,8 @@ const ONYXKEYS = {

/** List of Form ids */
FORMS: {
ADD_DEBIT_CARD_FORM: 'addDebitCardForm',
ADD_DEBIT_CARD_FORM_DRAFT: 'addDebitCardFormDraft',
ADD_PAYMENT_CARD_FORM: 'addPaymentCardForm',
ADD_PAYMENT_CARD_FORM_DRAFT: 'addPaymentCardFormDraft',
WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm',
WORKSPACE_CATEGORY_FORM: 'workspaceCategoryForm',
WORKSPACE_CATEGORY_FORM_DRAFT: 'workspaceCategoryFormDraft',
Expand Down Expand Up @@ -475,6 +478,8 @@ const ONYXKEYS = {
SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT: 'settingsStatusSetClearAfterFormDraft',
SETTINGS_STATUS_CLEAR_DATE_FORM: 'settingsStatusClearDateForm',
SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT: 'settingsStatusClearDateFormDraft',
CHANGE_BILLING_CURRENCY_FORM: 'changeBillingCurrencyForm',
CHANGE_BILLING_CURRENCY_FORM_DRAFT: 'changeBillingCurrencyFormDraft',
PRIVATE_NOTES_FORM: 'privateNotesForm',
PRIVATE_NOTES_FORM_DRAFT: 'privateNotesFormDraft',
I_KNOW_A_TEACHER_FORM: 'iKnowTeacherForm',
Expand Down Expand Up @@ -517,7 +522,7 @@ const ONYXKEYS = {
type AllOnyxKeys = DeepValueOf<typeof ONYXKEYS>;

type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: FormTypes.AddDebitCardForm;
[ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM]: FormTypes.AddPaymentCardForm;
[ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm;
[ONYXKEYS.FORMS.WORKSPACE_CATEGORY_FORM]: FormTypes.WorkspaceCategoryForm;
[ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm;
Expand Down Expand Up @@ -549,6 +554,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.WAYPOINT_FORM]: FormTypes.WaypointForm;
[ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: FormTypes.SettingsStatusSetForm;
[ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM]: FormTypes.SettingsStatusClearDateForm;
[ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM]: FormTypes.ChangeBillingCurrencyForm;
[ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: FormTypes.SettingsStatusSetClearAfterForm;
[ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: FormTypes.PrivateNotesForm;
[ONYXKEYS.FORMS.I_KNOW_A_TEACHER_FORM]: FormTypes.IKnowTeacherForm;
Expand Down Expand Up @@ -704,6 +710,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.IS_CHECKING_PUBLIC_ROOM]: boolean;
[ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS]: Record<string, string>;
[ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID]: string;
[ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string;
[ONYXKEYS.PREFERRED_THEME]: ValueOf<typeof CONST.THEME>;
[ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken;
[ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.OnyxUpdatesFromServer;
Expand Down
3 changes: 3 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const ROUTES = {
WORKSPACE_SWITCHER: 'workspace-switcher',
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
SETTINGS_CHANGE_CURRENCY: 'settings/add-payment-card/change-currency',
SETTINGS_SHARE_CODE: 'settings/shareCode',
SETTINGS_DISPLAY_NAME: 'settings/profile/display-name',
SETTINGS_TIMEZONE: 'settings/profile/timezone',
Expand All @@ -107,6 +108,8 @@ const ROUTES = {
getRoute: (canChangeSize: 0 | 1) => `settings/subscription/subscription-size?canChangeSize=${canChangeSize}` as const,
},
SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD: 'settings/subscription/add-payment-card',
SETTINGS_SUBSCRIPTION_CHANGE_BILLING_CURRENCY: 'settings/subscription/change-billing-currency',
SETTINGS_SUBSCRIPTION_CHANGE_PAYMENT_CURRENCY: 'settings/subscription/add-payment-card/change-payment-currency',
SETTINGS_SUBSCRIPTION_DISABLE_AUTO_RENEW_SURVEY: 'settings/subscription/disable-auto-renew-survey',
SETTINGS_PRIORITY_MODE: 'settings/preferences/priority-mode',
SETTINGS_LANGUAGE: 'settings/preferences/language',
Expand Down
3 changes: 3 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const SCREENS = {
SAVE_THE_WORLD: 'Settings_TeachersUnite',
APP_DOWNLOAD_LINKS: 'Settings_App_Download_Links',
ADD_DEBIT_CARD: 'Settings_Add_Debit_Card',
ADD_PAYMENT_CARD_CHANGE_CURRENCY: 'Settings_Add_Payment_Card_Change_Currency',
ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account',
CLOSE: 'Settings_Close',
TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth',
Expand Down Expand Up @@ -104,6 +105,8 @@ const SCREENS = {
SIZE: 'Settings_Subscription_Size',
ADD_PAYMENT_CARD: 'Settings_Subscription_Add_Payment_Card',
DISABLE_AUTO_RENEW_SURVEY: 'Settings_Subscription_DisableAutoRenewSurvey',
CHANGE_BILLING_CURRENCY: 'Settings_Subscription_Change_Billing_Currency',
CHANGE_PAYMENT_CURRENCY: 'Settings_Subscription_Change_Payment_Currency',
},
},
SAVE_THE_WORLD: {
Expand Down
142 changes: 142 additions & 0 deletions src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
import type {ValueOf} from 'type-fest';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import TextInput from '@components/TextInput';
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 INPUT_IDS from '@src/types/form/ChangeBillingCurrencyForm';
import PaymentCardCurrencyHeader from './PaymentCardCurrencyHeader';
import PaymentCardCurrencyModal from './PaymentCardCurrencyModal';

type PaymentCardFormProps = {
initialCurrency?: ValueOf<typeof CONST.CURRENCY>;
isSecurityCodeRequired?: boolean;
changeBillingCurrency: (currency?: ValueOf<typeof CONST.CURRENCY>, values?: FormOnyxValues<typeof ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM>) => void;
};

const REQUIRED_FIELDS = [INPUT_IDS.SECURITY_CODE];

function PaymentCardChangeCurrencyForm({changeBillingCurrency, isSecurityCodeRequired, initialCurrency}: PaymentCardFormProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState(false);
const [currency, setCurrency] = useState<ValueOf<typeof CONST.CURRENCY>>(initialCurrency ?? CONST.CURRENCY.USD);

const validate = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM> => {
const errors = ValidationUtils.getFieldRequiredErrors(values, REQUIRED_FIELDS);

if (values.securityCode && !ValidationUtils.isValidSecurityCode(values.securityCode)) {
errors.securityCode = translate('addPaymentCardPage.error.securityCode');
}

return errors;
};

const {sections} = useMemo(
() => ({
sections: [
{
data: (Object.keys(CONST.CURRENCY) as Array<ValueOf<typeof CONST.CURRENCY>>).map((currencyItem) => ({
text: currencyItem,
value: currencyItem,
keyForList: currencyItem,
isSelected: currencyItem === currency,
})),
},
],
}),
[currency],
);

const showCurrenciesModal = useCallback(() => {
setIsCurrencyModalVisible(true);
}, []);

const changeCurrency = useCallback((selectedCurrency: ValueOf<typeof CONST.CURRENCY>) => {
setCurrency(selectedCurrency);
setIsCurrencyModalVisible(false);
}, []);

const selectCurrency = useCallback(
(selectedCurrency: ValueOf<typeof CONST.CURRENCY>) => {
setCurrency(selectedCurrency);
changeBillingCurrency(selectedCurrency);
},
[changeBillingCurrency],
);

if (isSecurityCodeRequired) {
return (
<FormProvider
formID={ONYXKEYS.FORMS.CHANGE_BILLING_CURRENCY_FORM}
validate={validate}
onSubmit={(values) => changeBillingCurrency(currency, values)}
submitButtonText={translate('common.save')}
scrollContextEnabled
style={[styles.mh5, styles.flexGrow1]}
>
<PaymentCardCurrencyHeader />
<>
<View style={[styles.mt5, styles.mhn5]}>
<MenuItemWithTopDescription
shouldShowRightIcon
title={currency}
descriptionTextStyle={styles.textNormal}
description={translate('common.currency')}
onPress={showCurrenciesModal}
/>
</View>
<InputWrapper
InputComponent={TextInput}
inputID={INPUT_IDS.SECURITY_CODE}
label={translate('addDebitCardPage.cvv')}
aria-label={translate('addDebitCardPage.cvv')}
role={CONST.ROLE.PRESENTATION}
maxLength={4}
containerStyles={[styles.mt5]}
inputMode={CONST.INPUT_MODE.NUMERIC}
/>
</>
<PaymentCardCurrencyModal
isVisible={isCurrencyModalVisible}
currencies={Object.keys(CONST.CURRENCY) as Array<ValueOf<typeof CONST.CURRENCY>>}
currentCurrency={currency}
onCurrencyChange={changeCurrency}
onClose={() => setIsCurrencyModalVisible(false)}
/>
</FormProvider>
);
}

return (
<View style={[styles.mh5, styles.flexGrow1]}>
<SelectionList
headerContent={<PaymentCardCurrencyHeader isSectionList />}
initiallyFocusedOptionKey={currency}
containerStyle={[styles.mhn5]}
sections={sections}
onSelectRow={(option) => {
selectCurrency(option.value);
}}
showScrollIndicator
shouldStopPropagation
shouldUseDynamicMaxToRenderPerBatch
ListItem={RadioListItem}
/>
</View>
);
}

PaymentCardChangeCurrencyForm.displayName = 'PaymentCardChangeCurrencyForm';

export default PaymentCardChangeCurrencyForm;
28 changes: 28 additions & 0 deletions src/components/AddPaymentCard/PaymentCardCurrencyHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import {View} from 'react-native';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

function PaymentCardCurrencyHeader({isSectionList}: {isSectionList?: boolean}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
return (
<View style={[isSectionList && styles.mh5]}>
<Text style={[styles.mt3, isSectionList && styles.mb5]}>
{`${translate('billingCurrency.note')}`}{' '}
<TextLink
style={styles.link}
href={CONST.PRICING}
>{`${translate('billingCurrency.noteLink')}`}</TextLink>{' '}
{`${translate('billingCurrency.noteDetails')}`}
</Text>
</View>
);
}

PaymentCardCurrencyHeader.displayName = 'PaymentCardCurrencyHeader';

export default PaymentCardCurrencyHeader;
17 changes: 9 additions & 8 deletions src/components/AddPaymentCard/PaymentCardCurrencyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {useMemo} from 'react';
import type {ValueOf} from 'type-fest';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Modal from '@components/Modal';
import ScreenWrapper from '@components/ScreenWrapper';
Expand All @@ -13,16 +14,16 @@ type PaymentCardCurrencyModalProps = {
/** Whether the modal is visible */
isVisible: boolean;

/** The list of years to render */
currencies: Array<keyof typeof CONST.CURRENCY>;
/** The list of currencies to render */
currencies: Array<ValueOf<typeof CONST.CURRENCY>>;

/** Currently selected year */
currentCurrency: keyof typeof CONST.CURRENCY;
/** Currently selected currency */
currentCurrency: ValueOf<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 selects a currency */
onCurrencyChange?: (currency: ValueOf<typeof CONST.CURRENCY>) => void;

/** Function to call when the user closes the year picker */
/** Function to call when the user closes the currency picker */
onClose?: () => void;
};

Expand Down Expand Up @@ -57,7 +58,7 @@ function PaymentCardCurrencyModal({isVisible, currencies, currentCurrency = CONS
useNativeDriver
>
<ScreenWrapper
style={[styles.pb0]}
style={styles.pb0}
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID={PaymentCardCurrencyModal.displayName}
Expand Down
Loading

0 comments on commit 64c6624

Please sign in to comment.