Skip to content
Draft
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
5 changes: 3 additions & 2 deletions src/components/AvatarWithDisplayName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ function AvatarWithDisplayName({
isAnonymous,
isMoneyRequestOrReport,
);
const pressableStyles = [styles.alignSelfStart, styles.mw100];

const multipleAvatars = (
<ReportActionAvatars
Expand Down Expand Up @@ -346,10 +347,10 @@ function AvatarWithDisplayName({
{Object.keys(parentNavigationSubtitleData).length > 0 && (
<ParentNavigationSubtitle
parentNavigationSubtitleData={parentNavigationSubtitleData}
reportID={report?.reportID}
hasParentAccess={report?.hasParentAccess}
parentReportID={report?.parentReportID}
parentReportActionID={report?.parentReportActionID}
pressableStyles={[styles.alignSelfStart, styles.mw100]}
pressableStyles={pressableStyles}
openParentReportInCurrentTab={openParentReportInCurrentTab}
statusText={statusText}
statusTooltipText={statusTooltipText}
Expand Down
93 changes: 36 additions & 57 deletions src/components/KYCWall/BaseKYCWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu';

import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useParentReportAction from '@hooks/useParentReportAction';
import useReportTransactions from '@hooks/useReportTransactions';

import {openPersonalBankAccountSetupView, setPersonalBankAccountContinueKYCOnSuccess} from '@libs/actions/BankAccounts';
import {completePaymentOnboarding, savePreferredPaymentMethod} from '@libs/actions/IOU/PayMoneyRequest';
import {navigateToBankAccountRoute} from '@libs/actions/ReimbursementAccount';
import {moveIOUReportToPolicy, moveIOUReportToPolicyAndInviteSubmitter} from '@libs/actions/Report';
import {doesPolicyHavePartiallySetupBankAccount} from '@libs/BankAccountUtils';
import getClickedTargetLocation from '@libs/getClickedTargetLocation';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import Log from '@libs/Log';
import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNavigationActionToMicrotaskQueue';
import Navigation from '@libs/Navigation/Navigation';
Expand All @@ -28,22 +26,32 @@ import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import {doesPersonalDetailExistSelector, personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import {lastWorkspaceNumberSelector} from '@src/selectors/Policy';
import type {BankAccountList, PersonalDetailsList, Policy} from '@src/types/onyx';
import type {BankAccountList, Policy, Transaction} from '@src/types/onyx';
import {getEmptyObject} from '@src/types/utils/EmptyObject';
import viewRef from '@src/types/utils/viewRef';

import type {EmitterSubscription, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';

import {hasSeenTourSelector} from '@selectors/Onboarding';
import {getParentReportActionSelector} from '@selectors/ReportAction';
import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import {Dimensions} from 'react-native';
import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';

import type {AnchorPosition, ContinueActionParams, DomRect, KYCWallProps, PaymentMethod} from './types';

// This sets the Horizontal anchor position offset for POPOVER MENU.
const POPOVER_MENU_ANCHOR_POSITION_HORIZONTAL_OFFSET = 20;

// Sync mirror of useReportTransactions, for reads at handler-invocation time.
function getReportTransactionsForReport(reportID: string | undefined): Transaction[] {
const transactions = OnyxUtils.getCachedCollection(ONYXKEYS.COLLECTION.TRANSACTION);
if (!transactions || !reportID) {
return [];
}
return Object.values(transactions).filter((transaction): transaction is Transaction => !!transaction && transaction.reportID === reportID);
}

// This component allows us to block various actions by forcing the user to first add a default payment method and successfully make it through our Know Your Customer flow
// before continuing to take whatever action they originally intended to take. It requires a button as a child and a native event so we can get the coordinates and use it
// to render the AddPaymentMethodMenu in the correct location.
Expand All @@ -67,33 +75,11 @@ function KYCWall({
shouldShowPersonalBankAccountOption = false,
ref,
}: KYCWallProps) {
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST);
const [bankAccountList = getEmptyObject<BankAccountList>()] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`);
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
const [betas] = useOnyx(ONYXKEYS.BETAS);
const ownerAccountID = iouReport?.ownerAccountID;
const employeeLoginSelector = useCallback((personalDetailsList: OnyxEntry<PersonalDetailsList>) => personalDetailsLoginSelector(ownerAccountID)(personalDetailsList), [ownerAccountID]);
const [employeeLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: employeeLoginSelector});
const doesSubmitterPersonalDetailExistSelector = useCallback(
(personalDetailsList: OnyxEntry<PersonalDetailsList>) => doesPersonalDetailExistSelector(ownerAccountID)(personalDetailsList),
[ownerAccountID],
);
const [doesSubmitterPersonalDetailExist] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: doesSubmitterPersonalDetailExistSelector});

const {translate} = useLocalize();
const currentUserDetails = useCurrentUserPersonalDetails();
const currentUserAccountID = currentUserDetails.accountID;
const currentUserEmail = currentUserDetails.email ?? '';
const localCurrency = currentUserDetails.localCurrencyCode ?? CONST.CURRENCY.USD;
const reportPreviewAction = useParentReportAction(iouReport);
const reportTransactions = useReportTransactions(iouReport?.reportID);
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const anchorRef = useRef<HTMLDivElement | View>(null);
const transferBalanceButtonRef = useRef<HTMLDivElement | View | null>(null);

Expand All @@ -107,8 +93,6 @@ function KYCWall({
anchorPositionHorizontal: 0,
});

const [lastPaymentMethod] = useOnyx(ONYXKEYS.NVP_LAST_PAYMENT_METHOD);

const getAnchorPosition = useCallback(
(domRect: DomRect): AnchorPosition => {
if (anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP) {
Expand Down Expand Up @@ -148,6 +132,23 @@ function KYCWall({

const selectPaymentMethod = useCallback(
(paymentMethod?: PaymentMethod, policy?: Policy, personalBankAccountOnSuccessFallbackRoute?: Route) => {
const bankAccountList = OnyxUtils.get(ONYXKEYS.BANK_ACCOUNT_LIST) ?? getEmptyObject<BankAccountList>();
const policies = OnyxUtils.getCachedCollection(ONYXKEYS.COLLECTION.POLICY);
const allReports = OnyxUtils.getCachedCollection(ONYXKEYS.COLLECTION.REPORT);
const allReportActions = OnyxUtils.getCachedCollection(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const chatReport = OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}` as const);
const lastPaymentMethod = OnyxUtils.get(ONYXKEYS.NVP_LAST_PAYMENT_METHOD);
const introSelected = OnyxUtils.get(ONYXKEYS.NVP_INTRO_SELECTED);
const isSelfTourViewed = hasSeenTourSelector(OnyxUtils.get(ONYXKEYS.NVP_ONBOARDING));
const betas = OnyxUtils.get(ONYXKEYS.BETAS);
const personalDetailsList = OnyxUtils.get(ONYXKEYS.PERSONAL_DETAILS_LIST);
const employeeLogin = personalDetailsLoginSelector(iouReport?.ownerAccountID)(personalDetailsList);
const doesSubmitterPersonalDetailExist = doesPersonalDetailExistSelector(iouReport?.ownerAccountID)(personalDetailsList);
const reportPreviewAction = getParentReportActionSelector(
OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReport?.parentReportID)}` as const),
iouReport?.parentReportActionID,
);
const reportTransactions = getReportTransactionsForReport(iouReport?.reportID);
const canLinkExistingBusinessBankAccount = getEligibleExistingBusinessBankAccounts(bankAccountList, policy?.outputCurrency, true).length > 0;

if (paymentMethod) {
Expand Down Expand Up @@ -252,27 +253,15 @@ function KYCWall({
}
},
[
bankAccountList,
getPersonalBankAccountOnSuccessFallbackRoute,
onSelectPaymentMethod,
iouReport,
addDebitCardRoute,
addBankAccountRoute,
chatReport,
policies,
reportPreviewAction,
chatReportID,
currentUserAccountID,
currentUserEmail,
employeeLogin,
doesSubmitterPersonalDetailExist,
introSelected,
translate,
reportTransactions,
allReports,
allReportActions,
lastPaymentMethod,
isSelfTourViewed,
betas,
localCurrency,
],
);
Expand All @@ -286,6 +275,10 @@ function KYCWall({
const continueAction = useCallback(
(params?: ContinueActionParams) => {
const {event, iouPaymentType, paymentMethod, policy, goBackRoute, personalBankAccountOnSuccessFallbackRoute} = params ?? {};
const walletTerms = OnyxUtils.get(ONYXKEYS.WALLET_TERMS);
const fundList = OnyxUtils.get(ONYXKEYS.FUND_LIST);
const bankAccountList = OnyxUtils.get(ONYXKEYS.BANK_ACCOUNT_LIST) ?? getEmptyObject<BankAccountList>();
const userWallet = OnyxUtils.get(ONYXKEYS.USER_WALLET);
const currentSource = walletTerms?.source ?? source;

/**
Expand Down Expand Up @@ -375,21 +368,7 @@ function KYCWall({

onSuccessfulKYC(iouPaymentType, currentSource);
},
[
bankAccountList,
chatReportID,
enablePaymentsRoute,
fundList,
getAnchorPosition,
iouReport,
onSuccessfulKYC,
selectPaymentMethod,
shouldIncludeDebitCard,
shouldShowAddPaymentMenu,
source,
userWallet?.tierName,
walletTerms?.source,
],
[chatReportID, enablePaymentsRoute, getAnchorPosition, iouReport, onSuccessfulKYC, selectPaymentMethod, shouldIncludeDebitCard, shouldShowAddPaymentMenu, source],
);

useEffect(() => {
Expand Down
23 changes: 10 additions & 13 deletions src/components/ParentNavigationSubtitle.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import useHover from '@hooks/useHover';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useRootNavigationState from '@hooks/useRootNavigationState';
import useStyleUtils from '@hooks/useStyleUtils';
Expand All @@ -12,7 +10,7 @@ import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName';
import Navigation from '@libs/Navigation/Navigation';
import type {RightModalNavigatorParamList} from '@libs/Navigation/types';
import {getReportAction, isReportActionVisible} from '@libs/ReportActionsUtils';
import {canUserPerformWriteAction as canUserPerformWriteActionReportUtils, isMoneyRequestReport} from '@libs/ReportUtils';
import {canUserPerformWriteAction as canUserPerformWriteActionReportUtils, isArchivedReport, isMoneyRequestReport} from '@libs/ReportUtils';

import CONST from '@src/CONST';
import type {ParentNavigationSummaryParams} from '@src/languages/params';
Expand All @@ -26,6 +24,7 @@ import type {ColorValue, StyleProp, TextStyle, ViewStyle} from 'react-native';
import {useRoute} from '@react-navigation/native';
import React from 'react';
import {View} from 'react-native';
import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';

import StatusBadge from './StatusBadge';
import Text from './Text';
Expand All @@ -34,8 +33,8 @@ import TextLink from './TextLink';
type ParentNavigationSubtitleProps = {
parentNavigationSubtitleData: ParentNavigationSummaryParams;

/** Current Report ID (to check hasParentAccess) */
reportID?: string;
/** Whether the user has access to the parent report */
hasParentAccess?: boolean;

/** parent Report ID */
parentReportID?: string;
Expand Down Expand Up @@ -82,7 +81,6 @@ type ParentNavigationSubtitleProps = {

function ParentNavigationSubtitle({
parentNavigationSubtitleData,
reportID = '',
parentReportActionID,
parentReportID = '',
pressableStyles,
Expand All @@ -97,6 +95,7 @@ function ParentNavigationSubtitle({
humanAgentAccountID,
humanAgentName,
shouldShowFromPrefix = true,
hasParentAccess,
}: ParentNavigationSubtitleProps) {
const currentRoute = useRoute();
// We intentionally use isSmallScreenWidth (real device width), not shouldUseNarrowLayout — the latter is
Expand All @@ -113,12 +112,7 @@ function ParentNavigationSubtitle({

const {workspaceName, reportName} = parentNavigationSubtitleData;
const {translate} = useLocalize();
const [currentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`);
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`);
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS);
const isReportArchived = useReportIsArchived(report?.reportID);
const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isReportArchived);
const hasAccessToParentReport = currentReport?.hasParentAccess !== false;
const hasAccessToParentReport = hasParentAccess !== false;
const {currentFullScreenRoute, currentFocusedNavigator} = useRootNavigationState((state) => {
// Find the tab navigator, which wraps all full-screen navigators
const tabNavigatorRoute = state?.routes?.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR);
Expand Down Expand Up @@ -154,8 +148,11 @@ function ParentNavigationSubtitle({
}

const onPress = () => {
const report = OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}` as const);
const isParentReportArchived = !!isArchivedReport(OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${parentReportID}` as const));
const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isParentReportArchived);
const parentAction = getReportAction(parentReportID, parentReportActionID);
const isVisibleAction = isReportActionVisible(parentAction, parentReportID, canUserPerformWriteAction, visibleReportActionsData);
const isVisibleAction = isReportActionVisible(parentAction, parentReportID, canUserPerformWriteAction, OnyxUtils.get(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS));

const focusedNavigatorState = currentFocusedNavigator?.state;
const currentReportIndex = focusedNavigatorState?.index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type {OnyxEntry} from 'react-native-onyx';

import {isTrackIntentUserSelector} from '@selectors/Onboarding';
import React from 'react';
import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';

type PayActionCellProps = {
isLoading: boolean;
Expand All @@ -47,10 +48,6 @@ function PayActionCell({isLoading, policyID, reportID, hash, amount, shouldDisab
const [iouReport, transactions] = useReportWithTransactionsAndViolations(reportID);
const policy = usePolicy(policyID);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS);
const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector});

const invoiceReceiverPolicyID = chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined;
const invoiceReceiverPolicy = usePolicy(invoiceReceiverPolicyID);
Expand Down Expand Up @@ -94,11 +91,12 @@ function PayActionCell({isLoading, policyID, reportID, hash, amount, shouldDisab
}

const additionalOnyxData = getSearchPayOnyxData(hash, reportID);
const isTrackIntentUser = isTrackIntentUserSelector(OnyxUtils.get(ONYXKEYS.NVP_INTRO_SELECTED));

if (isInvoiceReport(iouReport)) {
const existingB2BInvoiceReport = getParticipantsInvoiceReport(
allReports,
reportNameValuePairs,
OnyxUtils.getCachedCollection(ONYXKEYS.COLLECTION.REPORT),
OnyxUtils.getCachedCollection(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS),
activePolicyID,
CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS,
invoiceReceiverPolicyID ?? chatReport?.policyID,
Expand All @@ -108,8 +106,9 @@ function PayActionCell({isLoading, policyID, reportID, hash, amount, shouldDisab
// when paying an individual invoice room as a business. `payAsBusiness` is only known at click time, so pick
// the right report's actions here in the function scope.
const shouldUseB2BInvoiceReport = !!payAsBusiness && !!existingB2BInvoiceReport && isIndividualInvoiceRoom(chatReport);
const chatReportActions =
allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(shouldUseB2BInvoiceReport ? existingB2BInvoiceReport?.reportID : chatReport?.reportID)}`];
const chatReportActions = OnyxUtils.get(
`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(shouldUseB2BInvoiceReport ? existingB2BInvoiceReport?.reportID : chatReport?.reportID)}` as const,
);

payInvoice({
paymentMethodType: type,
Expand Down Expand Up @@ -155,7 +154,7 @@ function PayActionCell({isLoading, policyID, reportID, hash, amount, shouldDisab
ownerBillingGracePeriodEnd,
methodID: type === CONST.IOU.PAYMENT_TYPE.VBBA ? methodID : undefined,
additionalOnyxData,
chatReportActions: allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(chatReport?.reportID)}`],
chatReportActions: OnyxUtils.get(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(chatReport?.reportID)}` as const),
delegateAccountID,
isTrackIntentUser,
});
Expand Down
Loading
Loading