Skip to content

Commit 57c0cbc

Browse files
authored
Merge pull request Expensify#76141 from software-mansion-labs/feat/migrate-CardMissingDetailsPage-to-navigation-based-validation
2 parents a8afba6 + ddca4cb commit 57c0cbc

11 files changed

Lines changed: 306 additions & 138 deletions

File tree

src/ROUTES.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ const ROUTES = {
284284
route: 'settings/wallet/card/:cardID/missing-details',
285285
getRoute: (cardID: string) => `settings/wallet/card/${cardID}/missing-details` as const,
286286
},
287+
SETTINGS_WALLET_CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE: {
288+
route: 'settings/wallet/card/:cardID/missing-details/confirm-magic-code',
289+
getRoute: (cardID: string) => `settings/wallet/card/${cardID}/missing-details/confirm-magic-code` as const,
290+
},
287291
SETTINGS_DOMAIN_CARD_DETAIL: {
288292
route: 'settings/card/:cardID?',
289293
getRoute: (cardID: string) => `settings/card/${cardID}` as const,

src/SCREENS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ const SCREENS = {
153153
DOMAIN_CARD: 'Settings_Wallet_DomainCard',
154154
DOMAIN_CARD_CONFIRM_MAGIC_CODE: 'Settings_Wallet_DomainCard_ConfirmMagicCode',
155155
CARD_MISSING_DETAILS: 'Settings_Wallet_Card_MissingDetails',
156+
CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE: 'Settings_Wallet_Card_MissingDetails_ConfirmMagicCode',
156157
TRANSFER_BALANCE: 'Settings_Wallet_Transfer_Balance',
157158
CHOOSE_TRANSFER_ACCOUNT: 'Settings_Wallet_Choose_Transfer_Account',
158159
ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments',

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
438438
[SCREENS.SETTINGS.WALLET.DOMAIN_CARD_CONFIRM_MAGIC_CODE]: () =>
439439
require<ReactComponentModule>('../../../../pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardVerifyAccountPage').default,
440440
[SCREENS.SETTINGS.WALLET.CARD_MISSING_DETAILS]: () => require<ReactComponentModule>('../../../../pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsPage').default,
441+
[SCREENS.SETTINGS.WALLET.CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE]: () =>
442+
require<ReactComponentModule>('../../../../pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage').default,
441443
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: () => require<ReactComponentModule>('../../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default,
442444
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD_CONFIRM_MAGIC_CODE]: () =>
443445
require<ReactComponentModule>('../../../../pages/settings/Wallet/ReportVirtualCardFraudVerifyAccountPage').default,

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
231231
path: ROUTES.SETTINGS_WALLET_CARD_MISSING_DETAILS.route,
232232
exact: true,
233233
},
234+
[SCREENS.SETTINGS.WALLET.CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE]: {
235+
path: ROUTES.SETTINGS_WALLET_CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE.route,
236+
exact: true,
237+
},
234238
[SCREENS.SETTINGS.WALLET.VERIFY_ACCOUNT]: {
235239
path: ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT,
236240
exact: true,

src/libs/Navigation/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ type SettingsNavigatorParamList = {
175175
/** cardID of selected card */
176176
cardID: string;
177177
};
178+
[SCREENS.SETTINGS.WALLET.CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE]: {
179+
/** cardID of selected card */
180+
cardID: string;
181+
};
178182
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: {
179183
/** cardID of selected card */
180184
cardID: string;

src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useCallback, useMemo, useRef, useState} from 'react';
1+
import React, {useCallback, useMemo, useRef} from 'react';
22
import type {ForwardedRef} from 'react';
33
import {View} from 'react-native';
44
import type {OnyxEntry} from 'react-native-onyx';
@@ -14,10 +14,8 @@ import {normalizeCountryCode} from '@libs/CountryUtils';
1414
import Navigation from '@libs/Navigation/Navigation';
1515
import CONST from '@src/CONST';
1616
import ONYXKEYS from '@src/ONYXKEYS';
17-
import ROUTES from '@src/ROUTES';
1817
import type {PersonalDetailsForm} from '@src/types/form';
1918
import type {PrivatePersonalDetails} from '@src/types/onyx';
20-
import MissingPersonalDetailsMagicCodeModal from './MissingPersonalDetailsMagicCodeModal';
2119
import Address from './substeps/Address';
2220
import Confirmation from './substeps/Confirmation';
2321
import DateOfBirth from './substeps/DateOfBirth';
@@ -33,16 +31,15 @@ type MissingPersonalDetailsContentProps = {
3331
/** Optional custom header title */
3432
headerTitle?: string;
3533

36-
/** Optional custom completion handler */
37-
onComplete?: (values: PersonalDetailsForm, validateCode: string) => void;
34+
/** Completion handler */
35+
onComplete: () => void;
3836
};
3937

4038
const formSteps = [LegalName, DateOfBirth, Address, PhoneNumber, Confirmation];
4139

4240
function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, headerTitle, onComplete}: MissingPersonalDetailsContentProps) {
4341
const styles = useThemeStyles();
4442
const {translate} = useLocalize();
45-
const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false);
4643

4744
const ref: ForwardedRef<InteractiveStepSubHeaderHandle> = useRef(null);
4845

@@ -54,11 +51,7 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
5451
if (!values) {
5552
return;
5653
}
57-
if (!onComplete) {
58-
Navigation.navigate(ROUTES.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE);
59-
return;
60-
}
61-
setIsValidateCodeActionModalVisible(true);
54+
onComplete();
6255
}, [onComplete, values]);
6356

6457
const {
@@ -90,16 +83,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
9083
prevScreen();
9184
};
9285

93-
const handleSubmitForm = useCallback(
94-
(validateCode: string) => {
95-
if (!onComplete) {
96-
return;
97-
}
98-
onComplete(values, validateCode);
99-
},
100-
[values, onComplete],
101-
);
102-
10386
const handleNextScreen = useCallback(() => {
10487
if (isEditing) {
10588
goToTheLastStep();
@@ -142,11 +125,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
142125
screenIndex={screenIndex}
143126
personalDetailsValues={values}
144127
/>
145-
<MissingPersonalDetailsMagicCodeModal
146-
onClose={() => setIsValidateCodeActionModalVisible(false)}
147-
isValidateCodeActionModalVisible={isValidateCodeActionModalVisible}
148-
handleSubmitForm={handleSubmitForm}
149-
/>
150128
</ScreenWrapper>
151129
);
152130
}

src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx

Lines changed: 0 additions & 72 deletions
This file was deleted.

src/pages/MissingPersonalDetails/index.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import React from 'react';
1+
import React, {useCallback} from 'react';
22
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
33
import useOnyx from '@hooks/useOnyx';
4+
import Navigation from '@libs/Navigation/Navigation';
45
import ONYXKEYS from '@src/ONYXKEYS';
6+
import ROUTES from '@src/ROUTES';
57
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
68
import MissingPersonalDetailsContent from './MissingPersonalDetailsContent';
79

810
function MissingPersonalDetails() {
9-
const [privatePersonalDetails, privatePersonalDetailsMetadata] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
10-
const [draftValues, draftValuesMetadata] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT);
11+
const [privatePersonalDetails, privatePersonalDetailsMetadata] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true});
12+
const [draftValues, draftValuesMetadata] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: true});
1113

1214
const isLoading = isLoadingOnyxValue(privatePersonalDetailsMetadata, draftValuesMetadata);
1315

16+
const handleComplete = useCallback(() => {
17+
Navigation.navigate(ROUTES.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE);
18+
}, []);
19+
1420
if (isLoading) {
1521
return <FullScreenLoadingIndicator />;
1622
}
@@ -19,6 +25,7 @@ function MissingPersonalDetails() {
1925
<MissingPersonalDetailsContent
2026
privatePersonalDetails={privatePersonalDetails}
2127
draftValues={draftValues}
28+
onComplete={handleComplete}
2229
/>
2330
);
2431
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import React, {useCallback, useMemo} from 'react';
2+
import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent';
3+
import useLocalize from '@hooks/useLocalize';
4+
import useOnyx from '@hooks/useOnyx';
5+
import {clearPersonalDetailsErrors, setPersonalDetailsAndRevealExpensifyCard} from '@libs/actions/PersonalDetails';
6+
import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User';
7+
import {normalizeCountryCode} from '@libs/CountryUtils';
8+
import {getLatestError} from '@libs/ErrorUtils';
9+
import Navigation from '@libs/Navigation/Navigation';
10+
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
11+
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
12+
import {getSubstepValues} from '@pages/MissingPersonalDetails/utils';
13+
import CONST from '@src/CONST';
14+
import ONYXKEYS from '@src/ONYXKEYS';
15+
import ROUTES from '@src/ROUTES';
16+
import type SCREENS from '@src/SCREENS';
17+
import type {PersonalDetailsForm} from '@src/types/form';
18+
import {isEmptyObject} from '@src/types/utils/EmptyObject';
19+
import useExpensifyCardContext from './useExpensifyCardContext';
20+
21+
type ExpensifyCardMissingDetailsMagicCodePageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.WALLET.CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE>;
22+
23+
function ExpensifyCardMissingDetailsMagicCodePage({
24+
route: {
25+
params: {cardID = ''},
26+
},
27+
}: ExpensifyCardMissingDetailsMagicCodePageProps) {
28+
const {translate} = useLocalize();
29+
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true});
30+
const [draftValues] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: true});
31+
32+
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
33+
const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true});
34+
const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined;
35+
const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false});
36+
37+
const validateLoginError = getLatestError(privateDetailsErrors);
38+
const primaryLogin = account?.primaryLogin ?? '';
39+
const {setIsCardDetailsLoading, setCardsDetails, setCardsDetailsErrors} = useExpensifyCardContext();
40+
41+
const clearError = useCallback(() => {
42+
if (isEmptyObject(validateLoginError) && isEmptyObject(validateCodeAction?.errorFields)) {
43+
return;
44+
}
45+
clearPersonalDetailsErrors();
46+
}, [validateCodeAction?.errorFields, validateLoginError]);
47+
48+
const values = useMemo(() => normalizeCountryCode(getSubstepValues(privatePersonalDetails, draftValues)) as PersonalDetailsForm, [privatePersonalDetails, draftValues]);
49+
50+
const handleSubmitForm = useCallback(
51+
(validateCode: string) => {
52+
setIsCardDetailsLoading((prevState: Record<number, boolean>) => ({
53+
...prevState,
54+
[cardID]: true,
55+
}));
56+
57+
setPersonalDetailsAndRevealExpensifyCard(values, validateCode, countryCode, Number.parseInt(cardID, 10))
58+
.then((value) => {
59+
setCardsDetails((prevState) => ({...prevState, [cardID]: value}));
60+
setCardsDetailsErrors((prevState) => ({
61+
...prevState,
62+
[cardID]: '',
63+
}));
64+
Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID));
65+
})
66+
.catch((error: string) => {
67+
setCardsDetailsErrors((prevState) => ({
68+
...prevState,
69+
[cardID]: error,
70+
}));
71+
Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID));
72+
})
73+
.finally(() => {
74+
setIsCardDetailsLoading((prevState: Record<number, boolean>) => ({...prevState, [cardID]: false}));
75+
});
76+
},
77+
[cardID, countryCode, setCardsDetails, setCardsDetailsErrors, setIsCardDetailsLoading, values],
78+
);
79+
80+
return (
81+
<ValidateCodeActionContent
82+
title={translate('cardPage.validateCardTitle')}
83+
descriptionPrimary={translate('cardPage.enterMagicCode', {contactMethod: primaryLogin})}
84+
sendValidateCode={() => requestValidateCodeAction()}
85+
validateCodeActionErrorField="personalDetails"
86+
handleSubmitForm={handleSubmitForm}
87+
validateError={validateLoginError}
88+
clearError={clearError}
89+
onClose={() => {
90+
resetValidateActionCodeSent();
91+
Navigation.goBack(ROUTES.SETTINGS_WALLET_CARD_MISSING_DETAILS.getRoute(cardID));
92+
}}
93+
isLoading={privatePersonalDetails?.isLoading}
94+
/>
95+
);
96+
}
97+
98+
ExpensifyCardMissingDetailsMagicCodePage.displayName = 'ExpensifyCardMissingDetailsMagicCodePage';
99+
100+
export default ExpensifyCardMissingDetailsMagicCodePage;

src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsPage.tsx

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import React, {useCallback} from 'react';
22
import useLocalize from '@hooks/useLocalize';
33
import useOnyx from '@hooks/useOnyx';
4-
import {setPersonalDetailsAndRevealExpensifyCard} from '@libs/actions/PersonalDetails';
54
import Navigation from '@libs/Navigation/Navigation';
65
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
76
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
87
import MissingPersonalDetailsContent from '@pages/MissingPersonalDetails/MissingPersonalDetailsContent';
9-
import CONST from '@src/CONST';
108
import ONYXKEYS from '@src/ONYXKEYS';
119
import ROUTES from '@src/ROUTES';
1210
import type SCREENS from '@src/SCREENS';
13-
import type {PersonalDetailsForm} from '@src/types/form';
14-
import useExpensifyCardContext from './useExpensifyCardContext';
1511

1612
type ExpensifyCardMissingDetailsPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.WALLET.CARD_MISSING_DETAILS>;
1713

@@ -23,38 +19,10 @@ function ExpensifyCardMissingDetailsPage({
2319
const {translate} = useLocalize();
2420
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: false});
2521
const [draftValues] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: true});
26-
const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false});
27-
const {setIsCardDetailsLoading, setCardsDetails, setCardsDetailsErrors} = useExpensifyCardContext();
2822

29-
const handleComplete = useCallback(
30-
(values: PersonalDetailsForm, validateCode: string) => {
31-
setIsCardDetailsLoading((prevState: Record<number, boolean>) => ({
32-
...prevState,
33-
[cardID]: true,
34-
}));
35-
36-
setPersonalDetailsAndRevealExpensifyCard(values, validateCode, countryCode, Number.parseInt(cardID, 10))
37-
.then((value) => {
38-
setCardsDetails((prevState) => ({...prevState, [cardID]: value}));
39-
setCardsDetailsErrors((prevState) => ({
40-
...prevState,
41-
[cardID]: '',
42-
}));
43-
Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID));
44-
})
45-
.catch((error: string) => {
46-
setCardsDetailsErrors((prevState) => ({
47-
...prevState,
48-
[cardID]: error,
49-
}));
50-
Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID));
51-
})
52-
.finally(() => {
53-
setIsCardDetailsLoading((prevState: Record<number, boolean>) => ({...prevState, [cardID]: false}));
54-
});
55-
},
56-
[cardID, countryCode, setCardsDetails, setCardsDetailsErrors, setIsCardDetailsLoading],
57-
);
23+
const handleComplete = useCallback(() => {
24+
Navigation.navigate(ROUTES.SETTINGS_WALLET_CARD_MISSING_DETAILS_CONFIRM_MAGIC_CODE.getRoute(cardID));
25+
}, [cardID]);
5826

5927
return (
6028
<MissingPersonalDetailsContent

0 commit comments

Comments
 (0)