Skip to content

Commit 5a8a260

Browse files
authored
Merge pull request #55510 from DylanDylann/dylann-fix-55026
2 parents 22e7d27 + d546e46 commit 5a8a260

File tree

6 files changed

+118
-51
lines changed

6 files changed

+118
-51
lines changed

src/components/ReportActionItem/IssueCardMessage.tsx

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import RenderHTML from '@components/RenderHTML';
66
import useLocalize from '@hooks/useLocalize';
77
import useThemeStyles from '@hooks/useThemeStyles';
88
import Navigation from '@libs/Navigation/Navigation';
9-
import * as PolicyUtils from '@libs/PolicyUtils';
10-
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
9+
import {getPolicy, getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils';
10+
import {getCardIssuedMessage, getOriginalMessage, isActionOfType, shouldShowAddMissingDetails} from '@libs/ReportActionsUtils';
1111
import CONST from '@src/CONST';
1212
import ONYXKEYS from '@src/ONYXKEYS';
1313
import ROUTES from '@src/ROUTES';
@@ -23,42 +23,27 @@ type IssueCardMessageProps = {
2323
function IssueCardMessage({action, policyID}: IssueCardMessageProps) {
2424
const {translate} = useLocalize();
2525
const styles = useThemeStyles();
26-
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
27-
const [session] = useOnyx(ONYXKEYS.SESSION);
28-
const workspaceAccountID = PolicyUtils.getWorkspaceAccountID(policyID ?? '-1');
26+
const workspaceAccountID = getWorkspaceAccountID(policyID);
2927
const [cardList = {}] = useOnyx(ONYXKEYS.CARD_LIST);
28+
const [session] = useOnyx(ONYXKEYS.SESSION);
3029
const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`);
31-
32-
const assigneeAccountID = (ReportActionsUtils.getOriginalMessage(action) as IssueNewCardOriginalMessage)?.assigneeAccountID;
33-
34-
const missingDetails =
35-
!privatePersonalDetails?.legalFirstName ||
36-
!privatePersonalDetails?.legalLastName ||
37-
!privatePersonalDetails?.dob ||
38-
!privatePersonalDetails?.phoneNumber ||
39-
isEmptyObject(privatePersonalDetails?.addresses) ||
40-
privatePersonalDetails.addresses.length === 0;
41-
42-
const isAssigneeCurrentUser = !isEmptyObject(session) && session.accountID === assigneeAccountID;
43-
44-
const cardIssuedActionOriginalMessage = ReportActionsUtils.isActionOfType(
30+
const assigneeAccountID = (getOriginalMessage(action) as IssueNewCardOriginalMessage)?.assigneeAccountID;
31+
const cardIssuedActionOriginalMessage = isActionOfType(
4532
action,
4633
CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED,
4734
CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL,
4835
CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS,
4936
)
50-
? ReportActionsUtils.getOriginalMessage(action)
37+
? getOriginalMessage(action)
5138
: undefined;
52-
const cardID = cardIssuedActionOriginalMessage?.cardID ?? -1;
53-
const isPolicyAdmin = PolicyUtils.isPolicyAdmin(PolicyUtils.getPolicy(policyID));
54-
const card = isPolicyAdmin ? cardsList?.[cardID] : cardList[cardID];
55-
const shouldShowAddMissingDetailsButton = !isEmptyObject(card) && action?.actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && missingDetails && isAssigneeCurrentUser;
39+
const cardID = cardIssuedActionOriginalMessage?.cardID ?? CONST.DEFAULT_NUMBER_ID;
40+
const card = isPolicyAdmin(getPolicy(policyID)) ? cardsList?.[cardID] : cardList[cardID];
41+
const isAssigneeCurrentUser = !isEmptyObject(session) && session.accountID === assigneeAccountID;
42+
const shouldShowAddMissingDetailsButton = isAssigneeCurrentUser && shouldShowAddMissingDetails(action?.actionName, card);
5643

5744
return (
5845
<>
59-
<RenderHTML
60-
html={`<muted-text>${ReportActionsUtils.getCardIssuedMessage({reportAction: action, shouldRenderHTML: true, policyID, shouldDisplayLinkToCard: !!card})}</muted-text>`}
61-
/>
46+
<RenderHTML html={`<muted-text>${getCardIssuedMessage({reportAction: action, shouldRenderHTML: true, policyID, card})}</muted-text>`} />
6247
{shouldShowAddMissingDetailsButton && (
6348
<Button
6449
onPress={() => Navigation.navigate(ROUTES.MISSING_PERSONAL_DETAILS)}

src/libs/ReportActionsUtils.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import CONST from '@src/CONST';
99
import type {TranslationPaths} from '@src/languages/types';
1010
import ONYXKEYS from '@src/ONYXKEYS';
1111
import ROUTES from '@src/ROUTES';
12-
import type {Locale, OnyxInputOrEntry, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx';
12+
import type {Card, Locale, OnyxInputOrEntry, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx';
1313
import type {JoinWorkspaceResolution, OriginalMessageChangeLog, OriginalMessageExportIntegration} from '@src/types/onyx/OriginalMessage';
1414
import type {PolicyReportFieldType} from '@src/types/onyx/Policy';
1515
import type Report from '@src/types/onyx/Report';
@@ -2116,17 +2116,29 @@ function isCardIssuedAction(reportAction: OnyxEntry<ReportAction>) {
21162116
);
21172117
}
21182118

2119+
function shouldShowAddMissingDetails(actionName?: ReportActionName, card?: Card) {
2120+
const missingDetails =
2121+
!privatePersonalDetails?.legalFirstName ||
2122+
!privatePersonalDetails?.legalLastName ||
2123+
!privatePersonalDetails?.dob ||
2124+
!privatePersonalDetails?.phoneNumber ||
2125+
isEmptyObject(privatePersonalDetails?.addresses) ||
2126+
privatePersonalDetails.addresses.length === 0;
2127+
2128+
return actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && (card?.state === CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED || missingDetails);
2129+
}
2130+
21192131
function getCardIssuedMessage({
21202132
reportAction,
21212133
shouldRenderHTML = false,
21222134
policyID = '-1',
2123-
shouldDisplayLinkToCard = false,
2135+
card,
21242136
personalDetails,
21252137
}: {
21262138
reportAction: OnyxEntry<ReportAction>;
21272139
shouldRenderHTML?: boolean;
21282140
policyID?: string;
2129-
shouldDisplayLinkToCard?: boolean;
2141+
card?: Card;
21302142
personalDetails?: Partial<PersonalDetailsList>;
21312143
}) {
21322144
const cardIssuedActionOriginalMessage = isActionOfType(
@@ -2150,24 +2162,12 @@ function getCardIssuedMessage({
21502162
const assignee = shouldRenderHTML ? `<mention-user accountID="${assigneeAccountID}"/>` : assigneeDetails?.firstName ?? assigneeDetails?.login ?? '';
21512163
const navigateRoute = isPolicyAdmin ? ROUTES.EXPENSIFY_CARD_DETAILS.getRoute(policyID, String(cardID)) : ROUTES.SETTINGS_DOMAINCARD_DETAIL.getRoute(String(cardID));
21522164
const expensifyCardLink =
2153-
shouldRenderHTML && shouldDisplayLinkToCard
2154-
? `<a href='${environmentURL}/${navigateRoute}'>${translateLocal('cardPage.expensifyCard')}</a>`
2155-
: translateLocal('cardPage.expensifyCard');
2165+
shouldRenderHTML && !!card ? `<a href='${environmentURL}/${navigateRoute}'>${translateLocal('cardPage.expensifyCard')}</a>` : translateLocal('cardPage.expensifyCard');
21562166
const companyCardLink = shouldRenderHTML
21572167
? `<a href='${environmentURL}/${ROUTES.SETTINGS_WALLET}'>${translateLocal('workspace.companyCards.companyCard')}</a>`
21582168
: translateLocal('workspace.companyCards.companyCard');
2159-
2160-
const missingDetails =
2161-
!privatePersonalDetails?.legalFirstName ||
2162-
!privatePersonalDetails?.legalLastName ||
2163-
!privatePersonalDetails?.dob ||
2164-
!privatePersonalDetails?.phoneNumber ||
2165-
isEmptyObject(privatePersonalDetails?.addresses) ||
2166-
privatePersonalDetails.addresses.length === 0;
2167-
21682169
const isAssigneeCurrentUser = currentUserAccountID === assigneeAccountID;
2169-
2170-
const shouldShowAddMissingDetailsMessage = !isAssigneeCurrentUser || (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS && missingDetails);
2170+
const shouldShowAddMissingDetailsMessage = !isAssigneeCurrentUser || shouldShowAddMissingDetails(reportAction?.actionName, card);
21712171
switch (reportAction?.actionName) {
21722172
case CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED:
21732173
return translateLocal('workspace.expensifyCard.issuedCard', {assignee});
@@ -2322,6 +2322,7 @@ export {
23222322
getActionableJoinRequestPendingReportAction,
23232323
getReportActionsLength,
23242324
wasActionCreatedWhileOffline,
2325+
shouldShowAddMissingDetails,
23252326
getWorkspaceCategoryUpdateMessage,
23262327
getWorkspaceUpdateFieldMessage,
23272328
getWorkspaceNameUpdatedMessage,

src/pages/MissingPersonalDetails/MissingPersonalDetailsMagicCodeModal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {clearPersonalDetailsErrors} from '@libs/actions/PersonalDetails';
77
import {requestValidateCodeAction} from '@libs/actions/User';
88
import {getLatestError} from '@libs/ErrorUtils';
99
import Navigation from '@libs/Navigation/Navigation';
10+
import CONST from '@src/CONST';
1011
import ONYXKEYS from '@src/ONYXKEYS';
1112
import {isEmptyObject} from '@src/types/utils/EmptyObject';
1213

@@ -19,11 +20,13 @@ type MissingPersonalDetailsMagicCodeModalProps = {
1920
function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModalVisible, handleSubmitForm}: MissingPersonalDetailsMagicCodeModalProps) {
2021
const {translate} = useLocalize();
2122
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS);
23+
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
2224
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
2325
const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE);
2426
const privateDetailsErrors = privatePersonalDetails?.errors ?? undefined;
2527
const validateLoginError = getLatestError(privateDetailsErrors);
2628
const primaryLogin = account?.primaryLogin ?? '';
29+
const areAllCardsShipped = Object.values(cardList ?? {})?.every((card) => card?.state !== CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED);
2730

2831
const missingDetails =
2932
!privatePersonalDetails?.legalFirstName ||
@@ -34,13 +37,13 @@ function MissingPersonalDetailsMagicCodeModal({onClose, isValidateCodeActionModa
3437
privatePersonalDetails.addresses.length === 0;
3538

3639
useEffect(() => {
37-
if (missingDetails || !!privateDetailsErrors) {
40+
if (missingDetails || !!privateDetailsErrors || !areAllCardsShipped) {
3841
return;
3942
}
4043

4144
clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM);
4245
Navigation.goBack();
43-
}, [missingDetails, privateDetailsErrors]);
46+
}, [missingDetails, privateDetailsErrors, areAllCardsShipped]);
4447

4548
const onBackButtonPress = () => {
4649
onClose?.();

src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ function BaseReportActionContextMenu({
346346
openOverflowMenu,
347347
setIsEmojiPickerActive,
348348
moneyRequestAction,
349-
hasCard: !!card,
349+
card,
350350
};
351351

352352
if ('renderContent' in contextAction) {

src/pages/home/report/ContextMenu/ContextMenuActions.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ import {
116116
import CONST from '@src/CONST';
117117
import type {TranslationPaths} from '@src/languages/types';
118118
import ROUTES from '@src/ROUTES';
119-
import type {Beta, Download as DownloadOnyx, OnyxInputOrEntry, ReportAction, ReportActionReactions, Report as ReportType, Transaction, User} from '@src/types/onyx';
119+
import type {Beta, Card, Download as DownloadOnyx, OnyxInputOrEntry, ReportAction, ReportActionReactions, Report as ReportType, Transaction, User} from '@src/types/onyx';
120120
import type IconAsset from '@src/types/utils/IconAsset';
121121
import KeyboardUtils from '@src/utils/keyboard';
122122
import type {ContextMenuAnchor} from './ReportActionContextMenu';
@@ -179,7 +179,7 @@ type ContextMenuActionPayload = {
179179
setIsEmojiPickerActive?: (state: boolean) => void;
180180
anchorRef?: MutableRefObject<View | null>;
181181
moneyRequestAction: ReportAction | undefined;
182-
hasCard?: boolean;
182+
card?: Card;
183183
};
184184

185185
type OnPress = (closePopover: boolean, payload: ContextMenuActionPayload, selection?: string, reportID?: string, draftMessage?: string) => void;
@@ -459,7 +459,7 @@ const ContextMenuActions: ContextMenuAction[] = [
459459
// If return value is true, we switch the `text` and `icon` on
460460
// `ContextMenuItem` with `successText` and `successIcon` which will fall back to
461461
// the `text` and `icon`
462-
onPress: (closePopover, {reportAction, transaction, selection, report, reportID, hasCard}) => {
462+
onPress: (closePopover, {reportAction, transaction, selection, report, reportID, card}) => {
463463
const isReportPreviewAction = isReportPreviewActionReportActionsUtils(reportAction);
464464
const messageHtml = getActionHtml(reportAction);
465465
const messageText = getReportActionMessageText(reportAction);
@@ -599,7 +599,7 @@ const ContextMenuActions: ContextMenuAction[] = [
599599
const {label, errorMessage} = getOriginalMessage(reportAction) ?? {label: '', errorMessage: ''};
600600
setClipboardMessage(translateLocal('report.actions.type.integrationSyncFailed', {label, errorMessage}));
601601
} else if (isCardIssuedAction(reportAction)) {
602-
setClipboardMessage(getCardIssuedMessage({reportAction, shouldRenderHTML: true, policyID: report?.policyID, shouldDisplayLinkToCard: hasCard}));
602+
setClipboardMessage(getCardIssuedMessage({reportAction, shouldRenderHTML: true, policyID: report?.policyID, card}));
603603
} else if (isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_INTEGRATION)) {
604604
setClipboardMessage(getRemovedConnectionMessage(reportAction));
605605
} else if (content) {

tests/unit/ReportActionsUtilsTest.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,4 +636,82 @@ describe('ReportActionsUtils', () => {
636636
expect(expectedFragments).toEqual([{text: expectedMessage, html: `<muted-text>${expectedMessage}</muted-text>`, type: 'COMMENT'}]);
637637
});
638638
});
639+
640+
describe('shouldShowAddMissingDetails', () => {
641+
it('should return true if personal detail is not completed', async () => {
642+
const card = {
643+
cardID: 1,
644+
state: CONST.EXPENSIFY_CARD.STATE.STATE_DEACTIVATED,
645+
bank: 'vcf',
646+
domainName: 'expensify',
647+
lastUpdated: '2022-11-09 22:27:01.825',
648+
fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
649+
};
650+
const mockPersonalDetail = {
651+
address: {
652+
street: '123 Main St',
653+
city: 'New York',
654+
state: 'NY',
655+
postalCode: '10001',
656+
},
657+
};
658+
await Onyx.set(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, mockPersonalDetail);
659+
const res = ReportActionsUtils.shouldShowAddMissingDetails(CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS, card);
660+
expect(res).toEqual(true);
661+
});
662+
it('should return true if card state is STATE_NOT_ISSUED', async () => {
663+
const card = {
664+
cardID: 1,
665+
state: CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED,
666+
bank: 'vcf',
667+
domainName: 'expensify',
668+
lastUpdated: '2022-11-09 22:27:01.825',
669+
fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
670+
};
671+
const mockPersonalDetail = {
672+
addresses: [
673+
{
674+
street: '123 Main St',
675+
city: 'New York',
676+
state: 'NY',
677+
postalCode: '10001',
678+
},
679+
],
680+
legalFirstName: 'John',
681+
legalLastName: 'David',
682+
phoneNumber: '+162992973',
683+
dob: '9-9-2000',
684+
};
685+
await Onyx.set(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, mockPersonalDetail);
686+
const res = ReportActionsUtils.shouldShowAddMissingDetails(CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS, card);
687+
expect(res).toEqual(true);
688+
});
689+
it('should return false if no condition is matched', async () => {
690+
const card = {
691+
cardID: 1,
692+
state: CONST.EXPENSIFY_CARD.STATE.OPEN,
693+
bank: 'vcf',
694+
domainName: 'expensify',
695+
lastUpdated: '2022-11-09 22:27:01.825',
696+
fraud: CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN,
697+
};
698+
const mockPersonalDetail = {
699+
addresses: [
700+
{
701+
street: '123 Main St',
702+
city: 'New York',
703+
state: 'NY',
704+
postalCode: '10001',
705+
},
706+
],
707+
legalFirstName: 'John',
708+
legalLastName: 'David',
709+
phoneNumber: '+162992973',
710+
dob: '9-9-2000',
711+
};
712+
await Onyx.set(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, mockPersonalDetail);
713+
const res = ReportActionsUtils.shouldShowAddMissingDetails(CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS, card);
714+
expect(res).toEqual(false);
715+
});
716+
});
639717
});

0 commit comments

Comments
 (0)