Skip to content
Open
Changes from 2 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
42 changes: 34 additions & 8 deletions src/components/SettlementButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import type {GestureResponderEvent} from 'react-native';
import type {TupleToUnion} from 'type-fest';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import * as Expensicons from '@components/Icon/Expensicons';

Check warning on line 8 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used by a pattern. Direct imports from Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details

Check warning on line 8 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used. Direct imports from @components/Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details
import {Bank} from '@components/Icon/Expensicons';

Check warning on line 9 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used by a pattern. Direct imports from Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details

Check warning on line 9 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used. Direct imports from @components/Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details
import KYCWall from '@components/KYCWall';
import {KYCWallContext} from '@components/KYCWall/KYCWallContext';
import type {ContinueActionParams, PaymentMethod} from '@components/KYCWall/types';
Expand All @@ -18,15 +18,15 @@
import usePermissions from '@hooks/usePermissions';
import usePolicy from '@hooks/usePolicy';
import useThemeStyles from '@hooks/useThemeStyles';
import {isCurrencySupportedForDirectReimbursement} from '@libs/actions/Policy/Policy';
import {createWorkspace, isCurrencySupportedForDirectReimbursement} from '@libs/actions/Policy/Policy';
import {navigateToBankAccountRoute} from '@libs/actions/ReimbursementAccount';
import {getLastPolicyBankAccountID, getLastPolicyPaymentMethod} from '@libs/actions/Search';
import Navigation from '@libs/Navigation/Navigation';
import {formatPaymentMethods, getActivePaymentType} from '@libs/PaymentUtils';
import {getActiveAdminWorkspaces, getPolicyEmployeeAccountIDs} from '@libs/PolicyUtils';
import {getActiveAdminWorkspaces, getPolicyEmployeeAccountIDs, isPaidGroupPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import {hasRequestFromCurrentAccount} from '@libs/ReportActionsUtils';
import {
doesReportBelongToWorkspace,
getBankAccountRoute,
hasViolations as hasViolationsReportUtils,
isBusinessInvoiceRoom,
isExpenseReport as isExpenseReportUtil,
Expand Down Expand Up @@ -96,7 +96,7 @@
const {translate, localeCompare} = useLocalize();
const {isOffline} = useNetwork();
const policy = usePolicy(policyID);
const {accountID, email} = useCurrentUserPersonalDetails();
const {accountID, email, localCurrencyCode} = useCurrentUserPersonalDetails();

// The app would crash due to subscribing to the entire report collection if chatReportID is an empty string. So we should have a fallback ID here.
// eslint-disable-next-line rulesdir/no-default-id-values
Expand All @@ -119,8 +119,13 @@

const lastBankAccountID = getLastPolicyBankAccountID(policyIDKey, lastPaymentMethods, iouReport?.type as keyof LastPaymentMethodType);
const [fundList] = useOnyx(ONYXKEYS.FUND_LIST, {canBeMissing: true});
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});

const [invoiceReceiverPolicy] = useOnyx(
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined}`,
{canBeMissing: true},
);
const activePolicy = usePolicy(activePolicyID);
const activeAdminPolicies = getActiveAdminWorkspaces(policies, accountID.toString()).sort((a, b) => localeCompare(a.name || '', b.name || ''));
const reportID = iouReport?.reportID;

Expand Down Expand Up @@ -295,7 +300,7 @@
}

if ((hasMultiplePolicies || hasSinglePolicy) && canUseWallet && !isPersonalOnlyOption) {
activeAdminPolicies.forEach((activePolicy) => {

Check failure on line 303 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'activePolicy' is already declared in the upper scope on line 128 column 11

Check failure on line 303 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'activePolicy' is already declared in the upper scope on line 128 column 11
const policyName = activePolicy.name;
buttonOptions.push({
text: translate('iou.payWithPolicy', {policyName: truncate(policyName, {length: CONST.ADDITIONAL_ALLOWED_CHARACTERS}), formattedAmount: ''}),
Expand All @@ -314,20 +319,41 @@
}

if (isInvoiceReport) {
const hasActivePolicyAsAdmin = activePolicy && isPolicyAdmin(activePolicy) && isPaidGroupPolicy(activePolicy);

const isCurrencySupported = isCurrencySupportedForDirectReimbursement(currency as CurrencyType);
const isActivePolicyCurrencySupported = isCurrencySupportedForDirectReimbursement(activePolicy?.outputCurrency ?? '');
const isUserCurrencySupported = isCurrencySupportedForDirectReimbursement(localCurrencyCode ?? '');
const isInvoiceReceiverPolicyCurrencySupported = isCurrencySupportedForDirectReimbursement(invoiceReceiverPolicy?.outputCurrency ?? '');

const canUseActivePolicy = hasActivePolicyAsAdmin && isActivePolicyCurrencySupported;
const isPolicyCurrencySupported = invoiceReceiverPolicy ? isInvoiceReceiverPolicyCurrencySupported : canUseActivePolicy || isUserCurrencySupported;

Check failure on line 330 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator

Check failure on line 330 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator
Copy link
Contributor

Choose a reason for hiding this comment

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

❌ Missing Error Handling

The createWorkspace() function is called within a click handler without any error handling or loading state management.

Why this matters:

  1. Workspace creation is an async operation that can fail (network issues, server errors, etc.)
  2. Users get no feedback while the workspace is being created
  3. If creation fails, navigation to navigateToBankAccountRoute(policyID) may use an invalid policyID

Suggested fix: Add proper async handling:

onSelected: async () => {
    if (payAsBusiness) {
        if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS) {
            navigateToBankAccountRoute(chatReport?.invoiceReceiver?.policyID);
        } else {
            if (!canUseActivePolicy) {
                try {
                    const {policyID} = await createWorkspace();
                    navigateToBankAccountRoute(policyID);
                } catch (error) {
                    // Handle error appropriately
                    console.error('Failed to create workspace:', error);
                }
            } else {
                navigateToBankAccountRoute(activePolicy.id);
            }
        }
    } else {
        Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT.route);
    }
},

Check if createWorkspace() returns a promise and handle it appropriately.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@bernhardoj Can you check this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left this unresolved since I'm not sure how to handle this. We create the workspace optimistically and navigate to the bank account page. Should we show not found page if the workspace is failed to be created?

if ((!isLoading && (isEmptyObject(policy) || !isPolicyAdmin(policy))) || isPendingDeletePolicy(policy)) {
return (
<ScreenWrapper testID={ReimbursementAccountPage.displayName}>
<FullPageNotFoundView
shouldShow
onBackButtonPress={goBackFromInvalidPolicy}
onLinkPress={goBackFromInvalidPolicy}
subtitleKey={isEmptyObject(policy) || isPendingDeletePolicy(policy) ? undefined : 'workspace.common.notAuthorized'}
/>
</ScreenWrapper>


const getInvoicesOptions = (payAsBusiness: boolean) => {
const addBankAccountItem = {
text: translate('bankAccount.addBankAccount'),
icon: Expensicons.Bank,
onSelected: () => {
const bankAccountRoute = getBankAccountRoute(chatReport);
Navigation.navigate(bankAccountRoute);
if (payAsBusiness) {
if (chatReport?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS) {
navigateToBankAccountRoute(chatReport?.invoiceReceiver?.policyID);
} else {
if (!canUseActivePolicy) {

Check failure on line 341 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

Unexpected if as the only statement in an else block

Check failure on line 341 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected if as the only statement in an else block
const {policyID} = createWorkspace();

Check failure on line 342 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'policyID' is already declared in the upper scope on line 78 column 5

Check failure on line 342 in src/components/SettlementButton/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'policyID' is already declared in the upper scope on line 78 column 5
navigateToBankAccountRoute(policyID);
} else {
navigateToBankAccountRoute(activePolicy.id);
}
}
} else {
Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT.route);
}
},
value: CONST.IOU.PAYMENT_TYPE.ELSEWHERE,
};
return [
...(isCurrencySupported ? getPaymentSubitems(payAsBusiness) : []),
...(isCurrencySupported ? [addBankAccountItem] : []),
...(isCurrencySupported && isPolicyCurrencySupported ? [addBankAccountItem] : []),
{
text: translate('iou.payElsewhere', {formattedAmount: ''}),
icon: Expensicons.Cash,
Expand Down
Loading