Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2892,6 +2892,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) => {
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,7 @@ const ShareModalStackNavigator = createModalStackNavigator<ShareNavigatorParamLi

const MissingPersonalDetailsModalStackNavigator = createModalStackNavigator<MissingPersonalDetailsParamList>({
[SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: () => require<ReactComponentModule>('../../../../pages/MissingPersonalDetails').default,
[SCREENS.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE]: () => require<ReactComponentModule>('../../../../pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodePage').default,
});

const AddUnreportedExpenseModalStackNavigator = createModalStackNavigator<AddUnreportedExpensesParamList>({
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1628,6 +1628,7 @@ const config: LinkingOptions<RootNavigatorParamList>['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]: {
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2632,6 +2632,7 @@ type RestrictedActionParamList = {

type MissingPersonalDetailsParamList = {
[SCREENS.MISSING_PERSONAL_DETAILS_ROOT]: undefined;
[SCREENS.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE]: undefined;
};

type SplitExpenseParamList = {
Expand Down
15 changes: 15 additions & 0 deletions src/libs/PersonalDetailsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
let personalDetails: Array<PersonalDetails | null> = [];
let allPersonalDetails: OnyxEntry<PersonalDetailsList> = {};
let emailToPersonalDetailsCache: Record<string, PersonalDetails> = {};
Onyx.connect({

Check warning on line 25 in src/libs/PersonalDetailsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (val) => {
personalDetails = Object.values(val ?? {});
Expand All @@ -39,7 +39,7 @@
let hiddenTranslation = '';
let youTranslation = '';

Onyx.connect({

Check warning on line 42 in src/libs/PersonalDetailsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.ARE_TRANSLATIONS_LOADING,
initWithStoredValues: false,
callback: (value) => {
Expand Down Expand Up @@ -421,6 +421,20 @@
return login ? Str.removeSMSDomain(login) : '';
};

/**
* Checks whether any personal details are missing
*/
function arePersonalDetailsMissing(privatePersonalDetails: OnyxEntry<PrivatePersonalDetails>): boolean {
return (
!privatePersonalDetails?.legalFirstName ||
!privatePersonalDetails?.legalLastName ||
!privatePersonalDetails?.dob ||
!privatePersonalDetails?.phoneNumber ||
isEmptyObject(privatePersonalDetails?.addresses) ||
privatePersonalDetails.addresses.length === 0
);
}
Comment on lines +427 to +436
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets also add a test for this method if there is none @jmusial feel free to do that in a follow up 🙇


export {
getDisplayNameOrDefault,
getPersonalDetailsByIDs,
Expand All @@ -440,4 +454,5 @@
getShortMentionIfFound,
getLoginByAccountID,
getPhoneNumber,
arePersonalDetailsMissing,
};
10 changes: 2 additions & 8 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
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';
Expand All @@ -57,7 +57,7 @@
type MemberChangeMessageElement = MessageTextElement | MemberChangeMessageUserMentionElement | MemberChangeMessageRoomReferenceElement;

let allReportActions: OnyxCollection<ReportActions>;
Onyx.connect({

Check warning on line 60 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
waitForCollectionCallback: true,
callback: (actions) => {
Expand All @@ -69,7 +69,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 72 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -78,14 +78,14 @@
});

let isNetworkOffline = false;
Onyx.connect({

Check warning on line 81 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NETWORK,
callback: (val) => (isNetworkOffline = val?.isOffline ?? false),
});

let currentUserAccountID: number | undefined;
let currentEmail = '';
Onyx.connect({

Check warning on line 88 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, value is undefined
Expand All @@ -99,7 +99,7 @@
});

let privatePersonalDetails: PrivatePersonalDetails | undefined;
Onyx.connect({

Check warning on line 102 in src/libs/ReportActionsUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS,
callback: (personalDetails) => {
privatePersonalDetails = personalDetails;
Expand Down Expand Up @@ -3115,13 +3115,7 @@
}

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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ 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';
Expand Down Expand Up @@ -44,7 +43,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
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<InteractiveStepSubHeaderHandle> = useRef(null);

Expand All @@ -56,8 +54,12 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
if (!values) {
return;
}
if (!onComplete) {
Navigation.navigate(ROUTES.MISSING_PERSONAL_DETAILS_CONFIRM_MAGIC_CODE);
return;
}
setIsValidateCodeActionModalVisible(true);
}, [values]);
}, [onComplete, values]);

const {
componentToRender: SubStep,
Expand Down Expand Up @@ -90,13 +92,12 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea

const handleSubmitForm = useCallback(
(validateCode: string) => {
if (onComplete) {
onComplete(values, validateCode);
if (!onComplete) {
return;
}
updatePersonalDetailsAndShipExpensifyCards(values, validateCode, countryCode);
onComplete(values, validateCode);
},
[countryCode, values, onComplete],
[values, onComplete],
);

const handleNextScreen = useCallback(() => {
Expand All @@ -122,7 +123,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={MissingPersonalDetailsContent.displayName}
shouldShowOfflineIndicatorInWideScreen={!!isValidateCodeActionModalVisible}
>
<HeaderWithBackButton
title={headerTitle ?? translate('workspace.expensifyCard.addShippingDetails')}
Expand All @@ -142,7 +142,6 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues, hea
screenIndex={screenIndex}
personalDetailsValues={values}
/>

<MissingPersonalDetailsMagicCodeModal
onClose={() => setIsValidateCodeActionModalVisible(false)}
isValidateCodeActionModalVisible={isValidateCodeActionModalVisible}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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';
import {clearDraftValues} from '@libs/actions/FormActions';
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 {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<CardList>) => 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});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canBeMissing: true -> false

What's this change for? Would be good to have some context.

Copy link
Contributor Author

@jmusial jmusial Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assumed this was misissue of {canBeMissing: true}.

Following canBeMissing docstring

If the component calling this does not load the data then you should set it to false, which means that if the data is not there, it will log an alert, as it means we are using data that no one loaded and that's most probably a bug.

This pages` logic relies on privatePersonalDetails so changed it to 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 [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 missingDetails = arePersonalDetailsMissing(privatePersonalDetails);

useEffect(() => {
if (missingDetails || !!privateDetailsErrors || !areAllCardsShipped) {
return;
}

clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM);
Navigation.dismissModal();
}, [missingDetails, privateDetailsErrors, areAllCardsShipped]);

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) => {
updatePersonalDetailsAndShipExpensifyCards(values, validateCode, countryCode);
},
[countryCode, values],
);

return (
<ValidateCodeActionContent
title={translate('cardPage.validateCardTitle')}
descriptionPrimary={translate('cardPage.enterMagicCode', {contactMethod: primaryLogin ?? ''})}
sendValidateCode={() => requestValidateCodeAction()}
validateCodeActionErrorField="personalDetails"
handleSubmitForm={handleSubmitForm}
validateError={validateLoginError}
clearError={clearError}
onClose={() => {
resetValidateActionCodeSent();
Navigation.goBack(ROUTES.MISSING_PERSONAL_DETAILS);
}}
isLoading={privatePersonalDetails?.isLoading}
/>
);
}

MissingPersonalDetailsMagicCodePage.displayName = 'MissingPersonalDetailsMagicCodePage';

export default MissingPersonalDetailsMagicCodePage;
9 changes: 2 additions & 7 deletions src/pages/settings/Wallet/ExpensifyCardPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -59,13 +60,7 @@ type LimitTypeTranslationKeys = {
*/
function shouldShowMissingDetailsPage(card: OnyxEntry<Card>, privatePersonalDetails: OnyxEntry<PrivatePersonalDetails>): 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;
}
Expand Down
4 changes: 3 additions & 1 deletion src/selectors/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ const isActingAsDelegateSelector = (account: OnyxEntry<Account>) => !!account?.d

const isUserValidatedSelector = (account: OnyxEntry<Account>) => account?.validated;

export {isActingAsDelegateSelector, isUserValidatedSelector};
const primaryLoginSelector = (account: OnyxEntry<Account>) => account?.primaryLogin;

export {isActingAsDelegateSelector, isUserValidatedSelector, primaryLoginSelector};
1 change: 0 additions & 1 deletion src/selectors/PersonalDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ const createPersonalDetailsSelector = <T>(personalDetails: OnyxEntry<PersonalDet
const personalDetailsByEmailSelector = (personalDetails: OnyxEntry<PersonalDetailsList>) =>
personalDetails ? lodashMapKeys(personalDetails, (value, key) => value?.login ?? key) : undefined;

// eslint-disable-next-line import/prefer-default-export
export {createPersonalDetailsSelector, personalDetailsByEmailSelector};
Loading