Skip to content

Commit e69c567

Browse files
authored
Merge pull request Expensify#88345 from Expensify/claude-requireMagicCodeForDefaultContactMethod
2 parents 3210e49 + 074ae1b commit e69c567

13 files changed

Lines changed: 143 additions & 110 deletions

File tree

src/ROUTES.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,15 @@ const ROUTES = {
892892
return getUrlWithBackToParam(`settings/profile/contact-methods/new/confirm-magic-code`, backTo);
893893
},
894894
},
895+
SETTINGS_CONTACT_METHOD_SET_DEFAULT_CONFIRM: {
896+
route: 'settings/profile/contact-methods/:contactMethod/set-default/confirm',
897+
getRoute: (contactMethod: string, backTo?: string) => {
898+
const encodedMethod = encodeURIComponent(contactMethod);
899+
900+
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
901+
return getUrlWithBackToParam(`settings/profile/contact-methods/${encodedMethod}/set-default/confirm`, backTo);
902+
},
903+
},
895904
SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT: {
896905
route: 'settings/profile/contact-methods/verify',
897906
getRoute: (backTo?: string, forwardTo?: string) =>

src/SCREENS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ const SCREENS = {
143143
CONTACT_METHOD_DETAILS: 'Settings_ContactMethodDetails',
144144
NEW_CONTACT_METHOD: 'Settings_NewContactMethod',
145145
NEW_CONTACT_METHOD_CONFIRM_MAGIC_CODE: 'Settings_NewContactMethod_ConfirmMagicCode',
146+
CONTACT_METHOD_SET_DEFAULT_CONFIRM: 'Settings_ContactMethod_SetDefault_Confirm',
146147
CONTACT_METHOD_VERIFY_ACCOUNT: 'Settings_ContactMethod_Verify_Account',
147148
STATUS_CLEAR_AFTER: 'Settings_Status_Clear_After',
148149
STATUS_CLEAR_AFTER_DATE: 'Settings_Status_Clear_After_Date',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
type SetContactMethodAsDefaultParams = {
22
partnerUserID: string;
3+
validateCode: string;
34
};
45

56
export default SetContactMethodAsDefaultParams;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
407407
[SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/NewContactMethodPage').default,
408408
[SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD_CONFIRM_MAGIC_CODE]: () =>
409409
require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/NewContactMethodConfirmMagicCodePage').default,
410+
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_SET_DEFAULT_CONFIRM]: () =>
411+
require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/SetDefaultContactMethodConfirmMagicCodePage').default,
410412
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VERIFY_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/settings/Profile/Contacts/VerifyAccountPage').default,
411413
[SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE]: () => require<ReactComponentModule>('../../../../pages/settings/Preferences/PriorityModePage').default,
412414
[SCREENS.WORKSPACE.ACCOUNTING.ROOT]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/PolicyAccountingPage').default,

src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const SETTINGS_TO_RHP: Partial<Record<keyof SettingsSplitNavigatorParamList, str
1010
SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS,
1111
SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD,
1212
SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD_CONFIRM_MAGIC_CODE,
13+
SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_SET_DEFAULT_CONFIRM,
1314
SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER,
1415
SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE,
1516
SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME,

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
444444
path: ROUTES.SETTINGS_NEW_CONTACT_METHOD_CONFIRM_MAGIC_CODE.route,
445445
exact: true,
446446
},
447+
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_SET_DEFAULT_CONFIRM]: {
448+
path: ROUTES.SETTINGS_CONTACT_METHOD_SET_DEFAULT_CONFIRM.route,
449+
},
447450
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VERIFY_ACCOUNT]: {
448451
path: ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.route,
449452
exact: true,

src/libs/Navigation/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ type SettingsNavigatorParamList = {
119119
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
120120
backTo?: Routes;
121121
};
122+
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_SET_DEFAULT_CONFIRM]: {
123+
contactMethod: string;
124+
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
125+
backTo?: Routes;
126+
};
122127
[SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_VERIFY_ACCOUNT]: {
123128
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
124129
backTo?: Routes;

src/libs/actions/User.ts

Lines changed: 28 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import CONST from '@src/CONST';
4949
import ONYXKEYS from '@src/ONYXKEYS';
5050
import ROUTES from '@src/ROUTES';
5151
import type {ExpenseRuleForm, MerchantRuleForm, SpendRuleForm} from '@src/types/form';
52-
import type {AppReview, BlockedFromConcierge, CustomStatusDraft, ExpenseRule, Policy, ReportAttributesDerivedValue} from '@src/types/onyx';
52+
import type {AppReview, BlockedFromConcierge, CustomStatusDraft, ExpenseRule, ReportAttributesDerivedValue} from '@src/types/onyx';
5353
import type Login from '@src/types/onyx/Login';
5454
import type {Errors} from '@src/types/onyx/OnyxCommon';
5555
import type {AnyOnyxServerUpdate, OnyxServerUpdate, OnyxUpdateEvent} from '@src/types/onyx/OnyxUpdatesFromServer';
@@ -1053,20 +1053,37 @@ function generateStatementPDF(period: string) {
10531053

10541054
/**
10551055
* Sets a contact method / secondary login as the user's "Default" contact method.
1056+
* This uses no offline support (Pattern C) — state is only updated after the server confirms success.
10561057
* @param skipNavigation - When true, do not navigate (caller handles navigation, e.g. via useEffect when primaryContactMethod updates).
10571058
*/
10581059
function setContactMethodAsDefault(
10591060
currentUserPersonalDetails: OnyxEntry<OnyxPersonalDetails>,
1060-
policies: OnyxCollection<Policy>,
10611061
newDefaultContactMethod: string,
10621062
formatPhoneNumber: LocaleContextProps['formatPhoneNumber'],
1063-
backTo?: string,
1064-
skipNavigation?: boolean,
1063+
backTo: string | undefined,
1064+
skipNavigation: boolean,
1065+
validateCode: string,
10651066
) {
1066-
const oldDefaultContactMethod = currentEmail;
1067-
const optimisticData: Array<
1068-
OnyxUpdate<typeof ONYXKEYS.ACCOUNT | typeof ONYXKEYS.SESSION | typeof ONYXKEYS.LOGIN_LIST | typeof ONYXKEYS.PERSONAL_DETAILS_LIST | typeof ONYXKEYS.COLLECTION.POLICY>
1069-
> = [
1067+
// Pattern C: only set a pending indicator optimistically, no actual data changes
1068+
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.LOGIN_LIST>> = [
1069+
{
1070+
onyxMethod: Onyx.METHOD.MERGE,
1071+
key: ONYXKEYS.LOGIN_LIST,
1072+
value: {
1073+
[newDefaultContactMethod]: {
1074+
pendingFields: {
1075+
defaultLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
1076+
},
1077+
errorFields: {
1078+
defaultLogin: null,
1079+
},
1080+
},
1081+
},
1082+
},
1083+
];
1084+
1085+
// Pattern C: apply all actual data changes only after server confirms success
1086+
const successData: Array<OnyxUpdate<typeof ONYXKEYS.ACCOUNT | typeof ONYXKEYS.SESSION | typeof ONYXKEYS.LOGIN_LIST | typeof ONYXKEYS.PERSONAL_DETAILS_LIST>> = [
10701087
{
10711088
onyxMethod: Onyx.METHOD.MERGE,
10721089
key: ONYXKEYS.ACCOUNT,
@@ -1087,9 +1104,6 @@ function setContactMethodAsDefault(
10871104
value: {
10881105
[newDefaultContactMethod]: {
10891106
pendingFields: {
1090-
defaultLogin: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
1091-
},
1092-
errorFields: {
10931107
defaultLogin: null,
10941108
},
10951109
},
@@ -1106,36 +1120,8 @@ function setContactMethodAsDefault(
11061120
},
11071121
},
11081122
];
1109-
const successData: Array<OnyxUpdate<typeof ONYXKEYS.LOGIN_LIST>> = [
1110-
{
1111-
onyxMethod: Onyx.METHOD.MERGE,
1112-
key: ONYXKEYS.LOGIN_LIST,
1113-
value: {
1114-
[newDefaultContactMethod]: {
1115-
pendingFields: {
1116-
defaultLogin: null,
1117-
},
1118-
},
1119-
},
1120-
},
1121-
];
1122-
const failureData: Array<
1123-
OnyxUpdate<typeof ONYXKEYS.ACCOUNT | typeof ONYXKEYS.SESSION | typeof ONYXKEYS.LOGIN_LIST | typeof ONYXKEYS.PERSONAL_DETAILS_LIST | typeof ONYXKEYS.COLLECTION.POLICY>
1124-
> = [
1125-
{
1126-
onyxMethod: Onyx.METHOD.MERGE,
1127-
key: ONYXKEYS.ACCOUNT,
1128-
value: {
1129-
primaryLogin: oldDefaultContactMethod,
1130-
},
1131-
},
1132-
{
1133-
onyxMethod: Onyx.METHOD.MERGE,
1134-
key: ONYXKEYS.SESSION,
1135-
value: {
1136-
email: oldDefaultContactMethod,
1137-
},
1138-
},
1123+
1124+
const failureData: Array<OnyxUpdate<typeof ONYXKEYS.LOGIN_LIST>> = [
11391125
{
11401126
onyxMethod: Onyx.METHOD.MERGE,
11411127
key: ONYXKEYS.LOGIN_LIST,
@@ -1150,65 +1136,11 @@ function setContactMethodAsDefault(
11501136
},
11511137
},
11521138
},
1153-
{
1154-
onyxMethod: Onyx.METHOD.MERGE,
1155-
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
1156-
value: {
1157-
[currentUserAccountID]: {...currentUserPersonalDetails},
1158-
},
1159-
},
11601139
];
11611140

1162-
for (const policy of Object.values(policies ?? {})) {
1163-
if (!policy) {
1164-
continue;
1165-
}
1166-
1167-
let optimisticPolicyDataValue;
1168-
let failurePolicyDataValue;
1169-
1170-
if (policy.employeeList) {
1171-
const currentEmployee = policy.employeeList[oldDefaultContactMethod];
1172-
optimisticPolicyDataValue = {
1173-
employeeList: {
1174-
[oldDefaultContactMethod]: null,
1175-
[newDefaultContactMethod]: currentEmployee,
1176-
},
1177-
};
1178-
failurePolicyDataValue = {
1179-
employeeList: {
1180-
[oldDefaultContactMethod]: currentEmployee,
1181-
[newDefaultContactMethod]: null,
1182-
},
1183-
};
1184-
}
1185-
1186-
if (policy.ownerAccountID === currentUserAccountID) {
1187-
optimisticPolicyDataValue = {
1188-
...optimisticPolicyDataValue,
1189-
owner: newDefaultContactMethod,
1190-
};
1191-
failurePolicyDataValue = {
1192-
...failurePolicyDataValue,
1193-
owner: policy.owner,
1194-
};
1195-
}
1196-
1197-
if (optimisticPolicyDataValue && failurePolicyDataValue) {
1198-
optimisticData.push({
1199-
onyxMethod: Onyx.METHOD.MERGE,
1200-
key: `${ONYXKEYS.COLLECTION.POLICY}${policy.id}`,
1201-
value: optimisticPolicyDataValue,
1202-
});
1203-
failureData.push({
1204-
onyxMethod: Onyx.METHOD.MERGE,
1205-
key: `${ONYXKEYS.COLLECTION.POLICY}${policy.id}`,
1206-
value: failurePolicyDataValue,
1207-
});
1208-
}
1209-
}
12101141
const parameters: SetContactMethodAsDefaultParams = {
12111142
partnerUserID: newDefaultContactMethod,
1143+
validateCode,
12121144
};
12131145

12141146
API.write(WRITE_COMMANDS.SET_CONTACT_METHOD_AS_DEFAULT, parameters, {

src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import Text from '@components/Text';
1818
import ValidateCodeActionForm from '@components/ValidateCodeActionForm';
1919
import type {ValidateCodeFormHandle} from '@components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm';
2020
import useConfirmModal from '@hooks/useConfirmModal';
21-
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
2221
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
2322
import useLocalize from '@hooks/useLocalize';
2423
import useOnyx from '@hooks/useOnyx';
@@ -32,7 +31,6 @@ import {
3231
deleteContactMethod,
3332
requestContactMethodValidateCode,
3433
resetContactMethodValidateCodeSentState,
35-
setContactMethodAsDefault,
3634
validateSecondaryLogin,
3735
} from '@libs/actions/User';
3836
import {isMobileSafari} from '@libs/Browser';
@@ -60,14 +58,12 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
6058
const [myDomainSecurityGroups, myDomainSecurityGroupsResult] = useOnyx(ONYXKEYS.MY_DOMAIN_SECURITY_GROUPS);
6159
const [securityGroups, securityGroupsResult] = useOnyx(ONYXKEYS.COLLECTION.SECURITY_GROUP);
6260
const [isLoadingReportData = true, isLoadingReportDataResult] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
63-
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
6461
const [isValidateCodeFormVisible, setIsValidateCodeFormVisible] = useState(true);
6562
const {isActingAsDelegate} = useDelegateNoAccessState();
6663
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
6764
const isLoadingOnyxValues = isLoadingOnyxValue(loginListResult, sessionResult, myDomainSecurityGroupsResult, securityGroupsResult, isLoadingReportDataResult);
6865
const {isAccountLocked} = useLockedAccountState();
6966
const {showLockedAccountModal} = useLockedAccountActions();
70-
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
7167

7268
const {formatPhoneNumber, translate} = useLocalize();
7369
const themeStyles = useThemeStyles();
@@ -92,11 +88,11 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
9288
const prevPendingDeletedLogin = usePrevious(loginData?.pendingFields?.deletedLogin);
9389

9490
/**
95-
* Attempt to set this contact method as user's "Default contact method"
91+
* Navigate to the magic code verification page before setting contact method as default
9692
*/
97-
const setAsDefault = useCallback(() => {
98-
setContactMethodAsDefault(currentUserPersonalDetails, allPolicies, contactMethod, formatPhoneNumber, backTo);
99-
}, [currentUserPersonalDetails, allPolicies, contactMethod, formatPhoneNumber, backTo]);
93+
const navigateToSetDefaultConfirm = useCallback(() => {
94+
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_SET_DEFAULT_CONFIRM.getRoute(contactMethod, backTo));
95+
}, [contactMethod, backTo]);
10096

10197
/**
10298
* Determines whether the user's primary login switching is restricted
@@ -276,7 +272,7 @@ function ContactMethodDetailsPage({route}: ContactMethodDetailsPageProps) {
276272
<MenuItem
277273
title={translate('contacts.setAsDefault')}
278274
icon={icons.Star}
279-
onPress={isAccountLocked ? showLockedAccountModal : setAsDefault}
275+
onPress={isAccountLocked ? showLockedAccountModal : navigateToSetDefaultConfirm}
280276
/>
281277
</OfflineWithFeedback>
282278
) : null}

src/pages/settings/Profile/Contacts/NewContactMethodConfirmMagicCodePage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, {useEffect} from 'react';
22
import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent';
33
import useLocalize from '@hooks/useLocalize';
44
import useOnyx from '@hooks/useOnyx';
5-
import {clearPendingContactActionErrors, requestValidateCodeAction, verifyAddSecondaryLoginCode} from '@libs/actions/User';
5+
import {clearPendingContactActionErrors, requestValidateCodeAction, resetValidateActionCodeSent, verifyAddSecondaryLoginCode} from '@libs/actions/User';
66
import {getLatestErrorField} from '@libs/ErrorUtils';
77
import Navigation from '@libs/Navigation/Navigation';
88
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
@@ -42,6 +42,7 @@ function NewContactMethodConfirmMagicCodePage({route}: NewContactMethodConfirmMa
4242
clearPendingContactActionErrors();
4343
}}
4444
onClose={() => {
45+
resetValidateActionCodeSent();
4546
Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS.getRoute(navigateBackTo));
4647
}}
4748
isLoading={pendingContactAction?.isLoading}

0 commit comments

Comments
 (0)