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

feat: Step 1 country (without API calls) #51328

Merged
merged 6 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
57 changes: 57 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3314,6 +3314,63 @@ const CONST = {
ZW: 'Zimbabwe',
},

ALL_EUROPEAN_COUNTRIES: {
AL: 'Albania',
AD: 'Andorra',
AT: 'Austria',
BY: 'Belarus',
BE: 'Belgium',
BA: 'Bosnia & Herzegovina',
BG: 'Bulgaria',
HR: 'Croatia',
CY: 'Cyprus',
CZ: 'Czech Republic',
DK: 'Denmark',
EE: 'Estonia',
FO: 'Faroe Islands',
FI: 'Finland',
FR: 'France',
GE: 'Georgia',
DE: 'Germany',
GI: 'Gibraltar',
GR: 'Greece',
GL: 'Greenland',
HU: 'Hungary',
IS: 'Iceland',
IE: 'Ireland',
IM: 'Isle of Man',
IT: 'Italy',
JE: 'Jersey',
XK: 'Kosovo',
LV: 'Latvia',
LI: 'Liechtenstein',
LT: 'Lithuania',
LU: 'Luxembourg',
MT: 'Malta',
MD: 'Moldova',
MC: 'Monaco',
ME: 'Montenegro',
NL: 'Netherlands',
MK: 'North Macedonia',
NO: 'Norway',
PL: 'Poland',
PT: 'Portugal',
RO: 'Romania',
RU: 'Russia',
SM: 'San Marino',
RS: 'Serbia',
SK: 'Slovakia',
SI: 'Slovenia',
ES: 'Spain',
SJ: 'Svalbard & Jan Mayen',
SE: 'Sweden',
CH: 'Switzerland',
TR: 'Turkey',
UA: 'Ukraine',
GB: 'United Kingdom',
VA: 'Vatican City',
},

// Sources: https://github.com/Expensify/App/issues/14958#issuecomment-1442138427
// https://github.com/Expensify/App/issues/14958#issuecomment-1456026810
COUNTRY_ZIP_REGEX_DATA: {
Expand Down
4 changes: 3 additions & 1 deletion src/components/Form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type DatePicker from '@components/DatePicker';
import type EmojiPickerButtonDropdown from '@components/EmojiPicker/EmojiPickerButtonDropdown';
import type PercentageForm from '@components/PercentageForm';
import type Picker from '@components/Picker';
import type PushRowWithModal from '@components/PushRowWithModal';
import type RadioButtons from '@components/RadioButtons';
import type RoomNameInput from '@components/RoomNameInput';
import type SingleChoiceQuestion from '@components/SingleChoiceQuestion';
Expand Down Expand Up @@ -63,7 +64,8 @@ type ValidInputs =
| typeof NetSuiteMenuWithTopDescriptionForm
| typeof CountryPicker
| typeof StatePicker
| typeof ConstantSelector;
| typeof ConstantSelector
| typeof PushRowWithModal;

type ValueTypeKey = 'string' | 'boolean' | 'date' | 'country' | 'reportFields' | 'disabledListValues';
type ValueTypeMap = {
Expand Down
7 changes: 6 additions & 1 deletion src/components/PushRowWithModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type PushRowWithModalProps = {
selectedOption: string;

/** Function to call when the user selects an option */
onOptionChange: (value: string) => void;
onOptionChange: (option: string) => void;

/** Additional styles to apply to container */
wrapperStyles?: StyleProp<ViewStyle>;
Expand All @@ -31,6 +31,9 @@ type PushRowWithModalProps = {

/** Text to display on error message */
errorText?: string;

/** Function called whenever option changes */
onInputChange?: (value: string) => void;
};

function PushRowWithModal({
Expand All @@ -43,6 +46,7 @@ function PushRowWithModal({
searchInputTitle,
shouldAllowChange = true,
errorText,
onInputChange = () => {},
}: PushRowWithModalProps) {
const [isModalVisible, setIsModalVisible] = useState(false);

Expand All @@ -56,6 +60,7 @@ function PushRowWithModal({

const handleOptionChange = (value: string) => {
onOptionChange(value);
onInputChange(value);
};

return (
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,10 @@ const translations = {
countryStep: {
confirmBusinessBank: 'Confirm business bank account currency and country',
confirmCurrency: 'Confirm currency and country',
yourBusiness: 'Your business bank account currency must match your workspace currency.',
youCanChange: 'You can change your workspace currency in your',
findCountry: 'Find country',
selectCountry: 'Select country',
},
signerInfoStep: {
signerInfo: 'Signer info',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,10 @@ const translations = {
countryStep: {
confirmBusinessBank: 'Confirmar moneda y país de la cuenta bancaria comercial',
confirmCurrency: 'Confirmar moneda y país',
yourBusiness: 'La moneda de su cuenta bancaria comercial debe coincidir con la moneda de su espacio de trabajo.',
youCanChange: 'Puede cambiar la moneda de su espacio de trabajo en su',
findCountry: 'Encontrar país',
selectCountry: 'Seleccione su país',
},
signerInfoStep: {
signerInfo: 'Información del firmante',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,114 @@
import React, {useState} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {useOnyx} 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 MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import PushRowWithModal from '@components/PushRowWithModal';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as ValidationUtils from '@libs/ValidationUtils';
import mapCurrencyToCountry from '@pages/ReimbursementAccount/utils/mapCurrencyToCountry';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/ReimbursementAccountForm';

const {COUNTRY} = INPUT_IDS.ADDITIONAL_DATA;

function Confirmation({onNext}: SubStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT);

const policyID = reimbursementAccount?.achData?.policyID ?? '-1';
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const currency = policy?.outputCurrency ?? '';

const shouldAllowChange = currency === CONST.CURRENCY.EUR;
const currencyMappedToCountry = mapCurrencyToCountry(currency);

const [selectedCountry, setSelectedCountry] = useState<string>('');
const countryDefaultValue = reimbursementAccount?.achData?.additionalData?.[COUNTRY] ?? reimbursementAccountDraft?.[COUNTRY] ?? '';
const [selectedCountry, setSelectedCountry] = useState<string>(countryDefaultValue);

const disableSubmit = !(currency in CONST.CURRENCY);

const handleSettingsPress = () => {
Navigation.navigate(ROUTES.WORKSPACE_PROFILE.getRoute(policyID));
};

const handleSelectingCountry = (country: string) => {
FormActions.setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[COUNTRY]: country});
setSelectedCountry(country);
};

const validate = useCallback((values: FormOnyxValues<typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM>): FormInputErrors<typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM> => {
return ValidationUtils.getFieldRequiredErrors(values, [COUNTRY]);
}, []);

useEffect(() => {
if (currency === CONST.CURRENCY.EUR) {
return;
}

FormActions.setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[COUNTRY]: currencyMappedToCountry});
setSelectedCountry(currencyMappedToCountry);
}, [currency, currencyMappedToCountry]);

return (
<SafeAreaConsumer>
{({safeAreaPaddingBottomStyle}) => (
<ScrollView
style={styles.pt0}
contentContainerStyle={[styles.flexGrow1, safeAreaPaddingBottomStyle]}
>
<Text style={[styles.textHeadlineLineHeightXXL, styles.ph5, styles.mb3]}>{translate('countryStep.confirmBusinessBank')}</Text>
<MenuItemWithTopDescription
description={translate('common.currency')}
title={currency}
interactive={false}
/>
<Text style={[styles.ph5, styles.mb3, styles.mutedTextLabel]}>
{`${translate('countryStep.yourBusiness')} ${translate('countryStep.youCanChange')}`}
{` `}
<PressableWithoutFeedback
MrMuzyk marked this conversation as resolved.
Show resolved Hide resolved
accessibilityRole="button"
accessibilityLabel={translate('common.settings')}
accessible
onPress={handleSettingsPress}
>
<Text style={[styles.label, styles.textBlue]}>{translate('common.settings').toLowerCase()}</Text>
</PressableWithoutFeedback>
.
</Text>
<FormProvider
formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM}
submitButtonText={translate('common.confirm')}
validate={validate}
onSubmit={onNext}
style={[styles.flexGrow1]}
submitButtonStyles={[styles.mh5, styles.pb0, styles.mbn1]}
isSubmitDisabled={disableSubmit}
>
<Text style={[styles.textHeadlineLineHeightXXL, styles.ph5, styles.mb3]}>{translate('countryStep.confirmBusinessBank')}</Text>
{/* This is only to showcase usage of PushRowWithModal component. The actual implementation will come in next issue - https://github.com/Expensify/App/issues/50897 */}
<PushRowWithModal
optionsList={CONST.ALL_COUNTRIES}
<InputWrapper
InputComponent={PushRowWithModal}
optionsList={shouldAllowChange ? CONST.ALL_EUROPEAN_COUNTRIES : CONST.ALL_COUNTRIES}
selectedOption={selectedCountry}
MrMuzyk marked this conversation as resolved.
Show resolved Hide resolved
onOptionChange={handleSelectingCountry}
description={translate('common.country')}
modalHeaderTitle="Select country"
searchInputTitle="Find country"
modalHeaderTitle={translate('countryStep.selectCountry')}
searchInputTitle={translate('countryStep.findCountry')}
shouldAllowChange={shouldAllowChange}
value={selectedCountry}
inputID={COUNTRY}
/>
</FormProvider>
</ScrollView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ function ReimbursementAccountPage({route, policy}: ReimbursementAccountPageProps
const [hasACHDataBeenLoaded, setHasACHDataBeenLoaded] = useState(reimbursementAccount !== CONST.REIMBURSEMENT_ACCOUNT.DEFAULT_DATA && isPreviousPolicy);
const [shouldShowContinueSetupButton, setShouldShowContinueSetupButton] = useState(getShouldShowContinueSetupButtonInitialValue());

function getBankAccountFields<T extends InputID>(fieldNames: T[]): Pick<ACHDataReimbursementAccount, T> {
function getBankAccountFields(fieldNames: InputID[]): Partial<ACHDataReimbursementAccount> {
return {
...lodashPick(reimbursementAccount?.achData, ...fieldNames),
};
Expand Down
18 changes: 18 additions & 0 deletions src/pages/ReimbursementAccount/utils/mapCurrencyToCountry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import CONST from '@src/CONST';

function mapCurrencyToCountry(currency: string): string {
switch (currency) {
case CONST.CURRENCY.USD:
return 'US';
MrMuzyk marked this conversation as resolved.
Show resolved Hide resolved
case CONST.CURRENCY.AUD:
return 'AU';
case CONST.CURRENCY.CAD:
return 'CA';
case CONST.CURRENCY.GBP:
return 'GB';
default:
return '';
}
}

export default mapCurrencyToCountry;
7 changes: 7 additions & 0 deletions src/pages/workspace/WorkspaceProfileCurrencyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@ import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import mapCurrencyToCountry from '@pages/ReimbursementAccount/utils/mapCurrencyToCountry';
import * as FormActions from '@userActions/FormActions';
import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import INPUT_IDS from '@src/types/form/ReimbursementAccountForm';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper';
import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';

type WorkspaceProfileCurrencyPageProps = WithPolicyAndFullscreenLoadingProps;

const {COUNTRY} = INPUT_IDS.ADDITIONAL_DATA;

function WorkspaceProfileCurrencyPage({policy}: WorkspaceProfileCurrencyPageProps) {
const {translate} = useLocalize();

const onSelectCurrency = (item: CurrencyListItem) => {
FormActions.setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[COUNTRY]: mapCurrencyToCountry(item.currencyCode)});
Policy.updateGeneralSettings(policy?.id ?? '-1', policy?.name ?? '', item.currencyCode);
Navigation.setNavigationActionToMicrotaskQueue(Navigation.goBack);
};
Expand Down
20 changes: 19 additions & 1 deletion src/types/form/ReimbursementAccountForm.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type {Country} from '@src/CONST';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import type Form from './Form';

Expand Down Expand Up @@ -50,6 +51,9 @@ const INPUT_IDS = {
AMOUNT1: 'amount1',
AMOUNT2: 'amount2',
AMOUNT3: 'amount3',
ADDITIONAL_DATA: {
COUNTRY: 'country',
},
} as const;

type InputID = DeepValueOf<typeof INPUT_IDS>;
Expand Down Expand Up @@ -121,8 +125,21 @@ type ReimbursementAccountProps = {
[INPUT_IDS.AMOUNT3]: string;
};

type NonUSDReimbursementAccountAdditionalProps = {
[INPUT_IDS.ADDITIONAL_DATA.COUNTRY]: Country | '';
MrMuzyk marked this conversation as resolved.
Show resolved Hide resolved
};

type ReimbursementAccountForm = ReimbursementAccountFormExtraProps &
Form<InputID, BeneficialOwnersStepBaseProps & BankAccountStepProps & CompanyStepProps & RequestorStepProps & ACHContractStepProps & ReimbursementAccountProps>;
Form<
InputID,
BeneficialOwnersStepBaseProps &
BankAccountStepProps &
CompanyStepProps &
RequestorStepProps &
ACHContractStepProps &
ReimbursementAccountProps &
NonUSDReimbursementAccountAdditionalProps
>;

export type {
ReimbursementAccountForm,
Expand All @@ -133,6 +150,7 @@ export type {
BeneficialOwnersStepProps,
ACHContractStepProps,
ReimbursementAccountProps,
NonUSDReimbursementAccountAdditionalProps,
InputID,
};
export default INPUT_IDS;
15 changes: 15 additions & 0 deletions src/types/onyx/ReimbursementAccount.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import type {ACHContractStepProps, BeneficialOwnersStepProps, CompanyStepProps, ReimbursementAccountProps, RequestorStepProps} from '@src/types/form/ReimbursementAccountForm';
import type INPUT_IDS from '@src/types/form/ReimbursementAccountForm';
import type {BankName} from './Bank';
import type * as OnyxCommon from './OnyxCommon';

Expand All @@ -10,6 +12,16 @@ type BankAccountStep = ValueOf<typeof CONST.BANK_ACCOUNT.STEP>;
/** Substeps to setup a reimbursement bank account */
type BankAccountSubStep = ValueOf<typeof CONST.BANK_ACCOUNT.SUBSTEP>;

/**
*
*/
type AdditionalData = {
/**
*
*/
[INPUT_IDS.ADDITIONAL_DATA.COUNTRY]: Country | '';
MrMuzyk marked this conversation as resolved.
Show resolved Hide resolved
};

/** Model of ACH data */
type ACHData = Partial<BeneficialOwnersStepProps & CompanyStepProps & RequestorStepProps & ACHContractStepProps & ReimbursementAccountProps> & {
/** Step of the setup flow that we are on. Determines which view is presented. */
Expand Down Expand Up @@ -50,6 +62,9 @@ type ACHData = Partial<BeneficialOwnersStepProps & CompanyStepProps & RequestorS

/** Bank Account setup type (plaid or manual) */
setupType?: string;

/** Additional data for the non USD account in setup */
additionalData?: AdditionalData;
};

/** The step in an reimbursement account's ach data */
Expand Down
Loading