Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add billing currency #43572

Merged
merged 36 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
61859a6
Add billing currency
narefyev91 Jun 12, 2024
bc42f72
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 12, 2024
370f305
fix lint
narefyev91 Jun 12, 2024
3736030
change hint text position
narefyev91 Jun 12, 2024
52cb5a5
update cvv
narefyev91 Jun 12, 2024
b8705b0
fix ts and broken complex expression checks
narefyev91 Jun 13, 2024
acce063
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 13, 2024
78c84ef
fix ts
narefyev91 Jun 13, 2024
85bfa1b
updates after design review
narefyev91 Jun 14, 2024
dc44700
updates after design review
narefyev91 Jun 14, 2024
68a71f7
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 17, 2024
b4f6b1f
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 17, 2024
89f91d4
refactoring for naming, add preparation for 3ds flow
narefyev91 Jun 18, 2024
5e0691c
clean up
narefyev91 Jun 18, 2024
d02ffaf
clean up
narefyev91 Jun 18, 2024
0de6d73
earlier return
narefyev91 Jun 18, 2024
625b3f9
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 19, 2024
e7bcd41
clean up
narefyev91 Jun 19, 2024
c0ff628
updates after c+ review
narefyev91 Jun 20, 2024
da5f4af
fix ts
narefyev91 Jun 20, 2024
6d2432e
add not found page for native, add edit payment card logic
narefyev91 Jun 21, 2024
5380b0f
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 22, 2024
bc01fbe
add lost page
narefyev91 Jun 24, 2024
e38e702
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 25, 2024
0c639f8
prettier
narefyev91 Jun 25, 2024
b6a8ee3
fix ts
narefyev91 Jun 25, 2024
e21dfaa
fix url
narefyev91 Jun 25, 2024
a6ba5c0
fix url
narefyev91 Jun 25, 2024
51856c6
fix ts
narefyev91 Jun 25, 2024
9baf820
fix virtualization error
narefyev91 Jun 25, 2024
24af72f
fix after expensify review
narefyev91 Jun 26, 2024
7e3c249
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 26, 2024
fe290d6
re-run lint checks
narefyev91 Jun 26, 2024
5650e5b
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 26, 2024
71519b5
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 26, 2024
3714818
Merge branch 'refs/heads/main' into add-billing-currency
narefyev91 Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -336,6 +336,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 @@ -402,8 +405,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 @@ -472,6 +475,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 @@ -514,7 +519,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 @@ -546,6 +551,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 @@ -701,6 +707,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
160 changes: 160 additions & 0 deletions src/components/AddPaymentCard/PaymentCardChangeCurrencyForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
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 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 INPUT_IDS from '@src/types/form/ChangeBillingCurrencyForm';
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 HeaderCurrencyInfo({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.notDetails')}`}
</Text>
</View>
);
}
narefyev91 marked this conversation as resolved.
Show resolved Hide resolved

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) => ({
blimpich marked this conversation as resolved.
Show resolved Hide resolved
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]}
>
<HeaderCurrencyInfo />
<>
<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={<HeaderCurrencyInfo 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;
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
Loading