From 64a22999fb4b05e6c50179ea92d687e93f60aef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Tue, 21 Oct 2025 16:58:22 +0200 Subject: [PATCH 1/7] Missing person magic code modal uses navigation --- src/ROUTES.ts | 1 + src/SCREENS.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/Navigation/types.ts | 1 + .../MissingPersonalDetailsContent.tsx | 23 ++------ ...> MissingPersonalDetailsMagicCodePage.tsx} | 54 ++++++++++--------- 7 files changed, 38 insertions(+), 44 deletions(-) rename src/pages/MissingPersonalDetails/{MissingPersonalDetailsMagicCodeModal.tsx => MissingPersonalDetailsMagicCodePage.tsx} (61%) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index edf204001c26d..c3e4b75a32024 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2824,6 +2824,7 @@ const ROUTES = { getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const, }, MISSING_PERSONAL_DETAILS: 'missing-personal-details', + MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE: 'missing-personal-details/confirm-magic-code', POLICY_ACCOUNTING_NETSUITE_SUBSIDIARY_SELECTOR: { route: 'workspaces/:policyID/accounting/netsuite/subsidiary-selector', getRoute: (policyID: string | undefined) => { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2609ed8ff807c..4b23732338780 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -784,6 +784,7 @@ const SCREENS = { FEATURE_TRAINING_ROOT: 'FeatureTraining_Root', RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root', MISSING_PERSONAL_DETAILS_ROOT: 'MissingPersonalDetails_Root', + MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE: 'MissingPersonalDetails_ConfirmMagicCode', ADD_UNREPORTED_EXPENSES_ROOT: 'AddUnreportedExpenses_Root', DEBUG: { REPORT: 'Debug_Report', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 501ca3646b14f..6e0d98a89ee25 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -888,6 +888,7 @@ const ShareModalStackNavigator = createModalStackNavigator({ [SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: () => require('../../../../pages/MissingPersonalDetails').default, + [SCREENS.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE]: () => require('../../../../pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage').default, }); const AddUnreportedExpenseModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index bf0708fb8ef3f..e5d128a2e7ce8 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1596,6 +1596,7 @@ const config: LinkingOptions['config'] = { [SCREENS.RIGHT_MODAL.MISSING_PERSONAL_DETAILS]: { screens: { [SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: ROUTES.MISSING_PERSONAL_DETAILS, + [SCREENS.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE]: ROUTES.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE, }, }, [SCREENS.RIGHT_MODAL.ADD_UNREPORTED_EXPENSE]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1148d6507f5e1..e64d42044abb5 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -2320,6 +2320,7 @@ type RestrictedActionParamList = { type MissingPersonalDetailsParamList = { [SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: undefined; + [SCREENS.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE]: undefined; }; type SplitExpenseParamList = { diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx index f7bd83ca9df46..5f141051a88c8 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsContent.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useMemo, useRef} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; @@ -7,18 +7,16 @@ import InteractiveStepSubHeader from '@components/InteractiveStepSubHeader'; import type {InteractiveStepSubHeaderHandle} from '@components/InteractiveStepSubHeader'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; import useSubStep from '@hooks/useSubStep'; import useThemeStyles from '@hooks/useThemeStyles'; import {clearDraftValues} from '@libs/actions/FormActions'; -import {updatePersonalDetailsAndShipExpensifyCards} from '@libs/actions/PersonalDetails'; import {normalizeCountryCode} from '@libs/CountryUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type {PersonalDetailsForm} from '@src/types/form'; import type {PrivatePersonalDetails} from '@src/types/onyx'; -import MissingPersonalDetailsMagicCodeModal from './MissingPersonalDetailsMagicCodeModal'; import Address from './substeps/Address'; import Confirmation from './substeps/Confirmation'; import DateOfBirth from './substeps/DateOfBirth'; @@ -37,8 +35,6 @@ const formSteps = [LegalName, DateOfBirth, Address, PhoneNumber, Confirmation]; function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: MissingPersonalDetailsContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false); - const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const ref: ForwardedRef = useRef(null); @@ -50,7 +46,7 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi if (!values) { return; } - setIsValidateCodeActionModalVisible(true); + Navigation.navigate(ROUTES.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE); }, [values]); const { @@ -82,13 +78,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi prevScreen(); }; - const handleSubmitForm = useCallback( - (validateCode: string) => { - updatePersonalDetailsAndShipExpensifyCards(values, validateCode, countryCode); - }, - [countryCode, values], - ); - const handleNextScreen = useCallback(() => { if (isEditing) { goToTheLastStep(); @@ -112,7 +101,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi includeSafeAreaPaddingBottom={false} shouldEnableMaxHeight testID={MissingPersonalDetailsContent.displayName} - shouldShowOfflineIndicatorInWideScreen={!!isValidateCodeActionModalVisible} > - setIsValidateCodeActionModalVisible(false)} - isValidateCodeActionModalVisible={isValidateCodeActionModalVisible} - handleSubmitForm={handleSubmitForm} - /> ); } diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx similarity index 61% rename from src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx rename to src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx index 8a32c37ce4b42..5a6041bc521ca 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx @@ -1,27 +1,29 @@ -import React, {useEffect} from 'react'; -import ValidateCodeActionModal from '@components/ValidateCodeActionModal'; +import React, {useEffect, useMemo} from 'react'; +import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import {clearDraftValues} from '@libs/actions/FormActions'; -import {clearPersonalDetailsErrors} from '@libs/actions/PersonalDetails'; -import {requestValidateCodeAction} from '@libs/actions/User'; +import {clearPersonalDetailsErrors, updatePersonalDetailsAndShipExpensifyCards} from '@libs/actions/PersonalDetails'; +import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User'; +import {normalizeCountryCode} from '@libs/CountryUtils'; import {getLatestError} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {PersonalDetailsForm} from '@src/types/form'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {getSubstepValues} from './utils'; -type MissingPersonalDetailsMagicCodeModalProps = { - handleSubmitForm: (validateCode: string) => void; - isValidateCodeActionModalVisible: boolean; - onClose?: () => void; -}; - -function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModalVisible, handleSubmitForm}: MissingPersonalDetailsMagicCodeModalProps) { +function MissingPersonalDetailsMagicCodePage() { const {translate} = useLocalize(); - const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true}); + const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: false}); + const [draftValues] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: false}); + const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const [cardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true}); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); + const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true}); const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined; const validateLoginError = getLatestError(privateDetailsErrors); @@ -45,10 +47,6 @@ function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModa Navigation.goBack(); }, [missingDetails, privateDetailsErrors, areAllCardsShipped]); - const onBackButtonPress = () => { - onClose?.(); - }; - const clearError = () => { if (isEmptyObject(validateLoginError) && isEmptyObject(validateCodeAction?.errorFields)) { return; @@ -56,22 +54,30 @@ function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModa clearPersonalDetailsErrors(); }; + const values = useMemo(() => normalizeCountryCode(getSubstepValues(privatePersonalDetails, draftValues)) as PersonalDetailsForm, [privatePersonalDetails, draftValues]); + + const handleSubmitForm = (validateCode: string) => { + updatePersonalDetailsAndShipExpensifyCards(values, validateCode, countryCode); + }; + return ( - requestValidateCodeAction()} + validateCodeActionErrorField="personalDetails" handleSubmitForm={handleSubmitForm} + validateError={validateLoginError} + clearError={clearError} + onClose={() => { + resetValidateActionCodeSent(); + Navigation.goBack(ROUTES.MISSING_PERSONAL_DETAILS); + }} isLoading={privatePersonalDetails?.isLoading} /> ); } -MissingPersonalDetailsMagicCodeModal.displayName = 'MissingPersonalDetailsMagicCodeModal'; +MissingPersonalDetailsMagicCodePage.displayName = 'MissingPersonalDetailsMagicCodePage'; -export default MissingPersonalDetailsMagicCodeModal; +export default MissingPersonalDetailsMagicCodePage; From cecb8d878c89de4ab773e4350046b9b7d172a873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Thu, 30 Oct 2025 18:52:58 +0100 Subject: [PATCH 2/7] dismoss modal on success --- .../MissingPersonalDetailsMagicCodePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx index 5a6041bc521ca..b08e81ff01998 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx @@ -44,7 +44,7 @@ function MissingPersonalDetailsMagicCodePage() { } clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); - Navigation.goBack(); + Navigation.dismissModal(); }, [missingDetails, privateDetailsErrors, areAllCardsShipped]); const clearError = () => { From 6b0a48fd03b4147f28e4d554627a4dc9026faa41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Thu, 6 Nov 2025 15:54:10 +0100 Subject: [PATCH 3/7] add use callback --- .../MissingPersonalDetailsMagicCodePage.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx index b08e81ff01998..68a18b8d283de 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -56,9 +56,12 @@ function MissingPersonalDetailsMagicCodePage() { const values = useMemo(() => normalizeCountryCode(getSubstepValues(privatePersonalDetails, draftValues)) as PersonalDetailsForm, [privatePersonalDetails, draftValues]); - const handleSubmitForm = (validateCode: string) => { - updatePersonalDetailsAndShipExpensifyCards(values, validateCode, countryCode); - }; + const handleSubmitForm = useCallback( + (validateCode: string) => { + updatePersonalDetailsAndShipExpensifyCards(values, validateCode, countryCode); + }, + [countryCode, values], + ); return ( Date: Wed, 12 Nov 2025 11:40:00 +0100 Subject: [PATCH 4/7] add arePersonalDetailsMissing util --- src/libs/ReportActionsUtils.ts | 10 +- .../MissingPersonalDetailsMagicCodeModal.tsx | 9 +- .../MissingPersonalDetailsMagicCodePage.tsx | 9 +- ...pensifyCardMissingDetailsMagicCodePage.tsx | 113 ++++++++++++++++++ .../Wallet/ExpensifyCardPage/index.tsx | 9 +- 5 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index ffb8297edc508..0d5d7abddfb8d 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -32,7 +32,7 @@ import {formatMessageElementList, translateLocal} from './Localize'; import Log from './Log'; import type {MessageElementBase, MessageTextElement} from './MessageElement'; import Parser from './Parser'; -import {getEffectiveDisplayName, getPersonalDetailByEmail, getPersonalDetailsByIDs} from './PersonalDetailsUtils'; +import {arePersonalDetailsMissing, getEffectiveDisplayName, getPersonalDetailByEmail, getPersonalDetailsByIDs} from './PersonalDetailsUtils'; import {getPolicy, isPolicyAdmin as isPolicyAdminPolicyUtils} from './PolicyUtils'; import type {getReportName, OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; import StringUtils from './StringUtils'; @@ -3115,13 +3115,7 @@ function isCardIssuedAction( } function shouldShowAddMissingDetails(actionName?: ReportActionName, card?: Card) { - const missingDetails = - !privatePersonalDetails?.legalFirstName || - !privatePersonalDetails?.legalLastName || - !privatePersonalDetails?.dob || - !privatePersonalDetails?.phoneNumber || - isEmptyObject(privatePersonalDetails?.addresses) || - privatePersonalDetails.addresses.length === 0; + const missingDetails = arePersonalDetailsMissing(privatePersonalDetails); return actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && (card?.state === CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED || missingDetails); } diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx index 8a32c37ce4b42..cb06e651b2432 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx @@ -7,6 +7,7 @@ import {clearPersonalDetailsErrors} from '@libs/actions/PersonalDetails'; import {requestValidateCodeAction} from '@libs/actions/User'; import {getLatestError} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {arePersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -28,13 +29,7 @@ function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModa const primaryLogin = account?.primaryLogin ?? ''; const areAllCardsShipped = Object.values(cardList ?? {})?.every((card) => card?.state !== CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED); - const missingDetails = - !privatePersonalDetails?.legalFirstName || - !privatePersonalDetails?.legalLastName || - !privatePersonalDetails?.dob || - !privatePersonalDetails?.phoneNumber || - isEmptyObject(privatePersonalDetails?.addresses) || - privatePersonalDetails.addresses.length === 0; + const missingDetails = arePersonalDetailsMissing(privatePersonalDetails); useEffect(() => { if (missingDetails || !!privateDetailsErrors || !areAllCardsShipped) { diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx index 68a18b8d283de..6c3663f7e874e 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx @@ -8,6 +8,7 @@ import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/acti import {normalizeCountryCode} from '@libs/CountryUtils'; import {getLatestError} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {arePersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -30,13 +31,7 @@ function MissingPersonalDetailsMagicCodePage() { const primaryLogin = account?.primaryLogin ?? ''; const areAllCardsShipped = Object.values(cardList ?? {})?.every((card) => card?.state !== CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED); - const missingDetails = - !privatePersonalDetails?.legalFirstName || - !privatePersonalDetails?.legalLastName || - !privatePersonalDetails?.dob || - !privatePersonalDetails?.phoneNumber || - isEmptyObject(privatePersonalDetails?.addresses) || - privatePersonalDetails.addresses.length === 0; + const missingDetails = arePersonalDetailsMissing(privatePersonalDetails); useEffect(() => { if (missingDetails || !!privateDetailsErrors || !areAllCardsShipped) { diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx new file mode 100644 index 0000000000000..2afae718b098b --- /dev/null +++ b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx @@ -0,0 +1,113 @@ +import React, {useCallback, useEffect, useMemo} from 'react'; +import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import {clearDraftValues} from '@libs/actions/FormActions'; +import {clearPersonalDetailsErrors, setPersonalDetailsAndRevealExpensifyCard} from '@libs/actions/PersonalDetails'; +import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User'; +import {normalizeCountryCode} from '@libs/CountryUtils'; +import {getLatestError} from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import {arePersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; +import {getSubstepValues} from '@pages/MissingPersonalDetails/utils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetailsForm} from '@src/types/form'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import useExpensifyCardContext from './useExpensifyCardContext'; + +type ExpensifyCardMissingDetailsMagicCodePageProps = PlatformStackScreenProps; + +function ExpensifyCardMissingDetailsMagicCodePage({ + route: { + params: {cardID = ''}, + }, +}: ExpensifyCardMissingDetailsMagicCodePageProps) { + const {translate} = useLocalize(); + const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: false}); + const [draftValues] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: false}); + const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + + const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); + + const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true}); + const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined; + const validateLoginError = getLatestError(privateDetailsErrors); + const primaryLogin = account?.primaryLogin ?? ''; + const {setIsCardDetailsLoading, setCardsDetails, setCardsDetailsErrors} = useExpensifyCardContext(); + + const missingDetails = arePersonalDetailsMissing(privatePersonalDetails); + + useEffect(() => { + if (missingDetails || !!privateDetailsErrors) { + return; + } + + clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); + Navigation.dismissModal(); + }, [missingDetails, privateDetailsErrors]); + + const clearError = () => { + if (isEmptyObject(validateLoginError) && isEmptyObject(validateCodeAction?.errorFields)) { + return; + } + clearPersonalDetailsErrors(); + }; + + const values = useMemo(() => normalizeCountryCode(getSubstepValues(privatePersonalDetails, draftValues)) as PersonalDetailsForm, [privatePersonalDetails, draftValues]); + + const handleSubmitForm = useCallback( + (validateCode: string) => { + setIsCardDetailsLoading((prevState: Record) => ({ + ...prevState, + [cardID]: true, + })); + + setPersonalDetailsAndRevealExpensifyCard(values, validateCode, countryCode, Number.parseInt(cardID, 10)) + .then((value) => { + setCardsDetails((prevState) => ({...prevState, [cardID]: value})); + setCardsDetailsErrors((prevState) => ({ + ...prevState, + [cardID]: '', + })); + Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID)); + }) + .catch((error: string) => { + setCardsDetailsErrors((prevState) => ({ + ...prevState, + [cardID]: error, + })); + Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID)); + }) + .finally(() => { + setIsCardDetailsLoading((prevState: Record) => ({...prevState, [cardID]: false})); + }); + }, + [cardID, countryCode, setCardsDetails, setCardsDetailsErrors, setIsCardDetailsLoading, values], + ); + + return ( + requestValidateCodeAction()} + validateCodeActionErrorField="personalDetails" + handleSubmitForm={handleSubmitForm} + validateError={validateLoginError} + clearError={clearError} + onClose={() => { + resetValidateActionCodeSent(); + Navigation.goBack(ROUTES.SETTINGS_WALLET_CARD_MISSING_DETAILS.getRoute(cardID)); + }} + isLoading={privatePersonalDetails?.isLoading} + /> + ); +} + +ExpensifyCardMissingDetailsMagicCodePage.displayName = 'ExpensifyCardMissingDetailsMagicCodePage'; + +export default ExpensifyCardMissingDetailsMagicCodePage; diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx index 09cde28410da2..f4dfeebb22489 100644 --- a/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx +++ b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx @@ -27,6 +27,7 @@ import {convertToDisplayString, getCurrencyKeyByCountryCode} from '@libs/Currenc import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DomainCardNavigatorParamList, SettingsNavigatorParamList} from '@libs/Navigation/types'; +import {arePersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; import {buildCannedSearchQuery} from '@libs/SearchQueryUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import RedDotCardSection from '@pages/settings/Wallet/RedDotCardSection'; @@ -59,13 +60,7 @@ type LimitTypeTranslationKeys = { */ function shouldShowMissingDetailsPage(card: OnyxEntry, privatePersonalDetails: OnyxEntry): boolean { const isUKOrEUCard = card?.nameValuePairs?.feedCountry === 'GB'; - const hasMissingDetails = - !privatePersonalDetails?.legalFirstName || - !privatePersonalDetails?.legalLastName || - !privatePersonalDetails?.dob || - !privatePersonalDetails?.phoneNumber || - !privatePersonalDetails?.addresses || - privatePersonalDetails.addresses.length === 0; + const hasMissingDetails = arePersonalDetailsMissing(privatePersonalDetails); return hasMissingDetails && isUKOrEUCard; } From 537fa20ef2b7f744e9cf8985fa0ae9795bab39df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Wed, 12 Nov 2025 11:40:44 +0100 Subject: [PATCH 5/7] oops --- src/libs/PersonalDetailsUtils.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 328ef1798e2cc..1eced4d84cdb6 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -421,6 +421,20 @@ const getPhoneNumber = (details: OnyxEntry): string | undefined return login ? Str.removeSMSDomain(login) : ''; }; +/** + * Checks whether any personal details are missing + */ +function arePersonalDetailsMissing(privatePersonalDetails: OnyxEntry): boolean { + return ( + !privatePersonalDetails?.legalFirstName || + !privatePersonalDetails?.legalLastName || + !privatePersonalDetails?.dob || + !privatePersonalDetails?.phoneNumber || + isEmptyObject(privatePersonalDetails?.addresses) || + privatePersonalDetails.addresses.length === 0 + ); +} + export { getDisplayNameOrDefault, getPersonalDetailsByIDs, @@ -440,4 +454,5 @@ export { getShortMentionIfFound, getLoginByAccountID, getPhoneNumber, + arePersonalDetailsMissing, }; From 4e847354cb90715c92bc2a96858084f615ab78fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Wed, 12 Nov 2025 11:42:12 +0100 Subject: [PATCH 6/7] remove stashed file --- ...pensifyCardMissingDetailsMagicCodePage.tsx | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx deleted file mode 100644 index 2afae718b098b..0000000000000 --- a/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardMissingDetailsMagicCodePage.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, {useCallback, useEffect, useMemo} from 'react'; -import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import {clearDraftValues} from '@libs/actions/FormActions'; -import {clearPersonalDetailsErrors, setPersonalDetailsAndRevealExpensifyCard} from '@libs/actions/PersonalDetails'; -import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User'; -import {normalizeCountryCode} from '@libs/CountryUtils'; -import {getLatestError} from '@libs/ErrorUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import {arePersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; -import {getSubstepValues} from '@pages/MissingPersonalDetails/utils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import type {PersonalDetailsForm} from '@src/types/form'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import useExpensifyCardContext from './useExpensifyCardContext'; - -type ExpensifyCardMissingDetailsMagicCodePageProps = PlatformStackScreenProps; - -function ExpensifyCardMissingDetailsMagicCodePage({ - route: { - params: {cardID = ''}, - }, -}: ExpensifyCardMissingDetailsMagicCodePageProps) { - const {translate} = useLocalize(); - const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: false}); - const [draftValues] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: false}); - const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); - - const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); - - const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true}); - const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined; - const validateLoginError = getLatestError(privateDetailsErrors); - const primaryLogin = account?.primaryLogin ?? ''; - const {setIsCardDetailsLoading, setCardsDetails, setCardsDetailsErrors} = useExpensifyCardContext(); - - const missingDetails = arePersonalDetailsMissing(privatePersonalDetails); - - useEffect(() => { - if (missingDetails || !!privateDetailsErrors) { - return; - } - - clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); - Navigation.dismissModal(); - }, [missingDetails, privateDetailsErrors]); - - const clearError = () => { - if (isEmptyObject(validateLoginError) && isEmptyObject(validateCodeAction?.errorFields)) { - return; - } - clearPersonalDetailsErrors(); - }; - - const values = useMemo(() => normalizeCountryCode(getSubstepValues(privatePersonalDetails, draftValues)) as PersonalDetailsForm, [privatePersonalDetails, draftValues]); - - const handleSubmitForm = useCallback( - (validateCode: string) => { - setIsCardDetailsLoading((prevState: Record) => ({ - ...prevState, - [cardID]: true, - })); - - setPersonalDetailsAndRevealExpensifyCard(values, validateCode, countryCode, Number.parseInt(cardID, 10)) - .then((value) => { - setCardsDetails((prevState) => ({...prevState, [cardID]: value})); - setCardsDetailsErrors((prevState) => ({ - ...prevState, - [cardID]: '', - })); - Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID)); - }) - .catch((error: string) => { - setCardsDetailsErrors((prevState) => ({ - ...prevState, - [cardID]: error, - })); - Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID)); - }) - .finally(() => { - setIsCardDetailsLoading((prevState: Record) => ({...prevState, [cardID]: false})); - }); - }, - [cardID, countryCode, setCardsDetails, setCardsDetailsErrors, setIsCardDetailsLoading, values], - ); - - return ( - requestValidateCodeAction()} - validateCodeActionErrorField="personalDetails" - handleSubmitForm={handleSubmitForm} - validateError={validateLoginError} - clearError={clearError} - onClose={() => { - resetValidateActionCodeSent(); - Navigation.goBack(ROUTES.SETTINGS_WALLET_CARD_MISSING_DETAILS.getRoute(cardID)); - }} - isLoading={privatePersonalDetails?.isLoading} - /> - ); -} - -ExpensifyCardMissingDetailsMagicCodePage.displayName = 'ExpensifyCardMissingDetailsMagicCodePage'; - -export default ExpensifyCardMissingDetailsMagicCodePage; From f9066947741f3b6642049672dc5ae878b78c2784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Musia=C5=82?= Date: Wed, 12 Nov 2025 12:14:17 +0100 Subject: [PATCH 7/7] use selectors --- .../MissingPersonalDetailsMagicCodePage.tsx | 13 ++++++++----- src/selectors/Account.ts | 4 +++- src/selectors/PersonalDetails.ts | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx index 6c3663f7e874e..965035ab40551 100644 --- a/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx +++ b/src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage.tsx @@ -1,4 +1,5 @@ import React, {useCallback, useEffect, useMemo} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -12,24 +13,26 @@ import {arePersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {primaryLoginSelector} from '@src/selectors/Account'; import type {PersonalDetailsForm} from '@src/types/form'; +import type {CardList} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {getSubstepValues} from './utils'; +const areAllCardsShippedSelector = (cardList: OnyxEntry) => Object.values(cardList ?? {})?.every((card) => card?.state !== CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED); + function MissingPersonalDetailsMagicCodePage() { const {translate} = useLocalize(); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: false}); const [draftValues] = useOnyx(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM_DRAFT, {canBeMissing: false}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); - const [cardList] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true}); - const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); + const [areAllCardsShipped] = useOnyx(ONYXKEYS.CARD_LIST, {selector: areAllCardsShippedSelector, canBeMissing: true}); + const [primaryLogin] = useOnyx(ONYXKEYS.ACCOUNT, {selector: primaryLoginSelector, canBeMissing: true}); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true}); const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined; const validateLoginError = getLatestError(privateDetailsErrors); - const primaryLogin = account?.primaryLogin ?? ''; - const areAllCardsShipped = Object.values(cardList ?? {})?.every((card) => card?.state !== CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED); const missingDetails = arePersonalDetailsMissing(privatePersonalDetails); @@ -61,7 +64,7 @@ function MissingPersonalDetailsMagicCodePage() { return ( requestValidateCodeAction()} validateCodeActionErrorField="personalDetails" handleSubmitForm={handleSubmitForm} diff --git a/src/selectors/Account.ts b/src/selectors/Account.ts index 28b3333d6ad49..ed688df9a4739 100644 --- a/src/selectors/Account.ts +++ b/src/selectors/Account.ts @@ -5,4 +5,6 @@ const isActingAsDelegateSelector = (account: OnyxEntry) => !!account?.d const isUserValidatedSelector = (account: OnyxEntry) => account?.validated; -export {isActingAsDelegateSelector, isUserValidatedSelector}; +const primaryLoginSelector = (account: OnyxEntry) => account?.primaryLogin; + +export {isActingAsDelegateSelector, isUserValidatedSelector, primaryLoginSelector}; diff --git a/src/selectors/PersonalDetails.ts b/src/selectors/PersonalDetails.ts index 8a787a47962c8..a11d3ff3dea6c 100644 --- a/src/selectors/PersonalDetails.ts +++ b/src/selectors/PersonalDetails.ts @@ -11,5 +11,4 @@ const createPersonalDetailsSelector = (personalDetails: OnyxEntry) => personalDetails ? lodashMapKeys(personalDetails, (value, key) => value?.login ?? key) : undefined; -// eslint-disable-next-line import/prefer-default-export export {createPersonalDetailsSelector, personalDetailsByEmailSelector};