Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#45949 & #45950 Divide workflows page into three sections & Update payment section with bank icon #46020

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,8 @@ export default {
title: 'Authorized payer',
genericErrorMessage: 'The authorized payer could not be changed. Please try again.',
admins: 'Admins',
payer: 'Payer',
paymentAccount: 'Payment account',
},
reportFraudPage: {
title: 'Report virtual card fraud',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,8 @@ export default {
title: 'Pagador autorizado',
genericErrorMessage: 'El pagador autorizado no se pudo cambiar. Por favor, inténtalo mas tarde.',
admins: 'Administradores',
payer: 'Pagador',
paymentAccount: 'Cuenta de pago',
},
reportFraudPage: {
title: 'Reportar fraude con la tarjeta virtual',
Expand Down
9 changes: 7 additions & 2 deletions src/libs/PaymentUtils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type {ValueOf} from 'type-fest';
import getBankIcon from '@components/Icon/BankIcons';
import type {ThemeStyles} from '@styles/index';
import CONST from '@src/CONST';
import type BankAccount from '@src/types/onyx/BankAccount';
import type Fund from '@src/types/onyx/Fund';
import type PaymentMethod from '@src/types/onyx/PaymentMethod';
import type {ACHAccount} from '@src/types/onyx/Policy';
import * as Localize from './Localize';
import BankAccountModel from './models/BankAccount';

type AccountType = BankAccount['accountType'] | Fund['accountType'];
type AccountType = ValueOf<typeof CONST.PAYMENT_METHODS> | undefined;

/**
* Check to see if user has either a debit card or personal bank account added that can be used with a wallet.
Expand All @@ -25,11 +27,14 @@ function hasExpensifyPaymentMethod(fundList: Record<string, Fund>, bankAccountLi
return validBankAccount || (shouldIncludeDebitCard && validDebitCard);
}

function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData']): string {
function getPaymentMethodDescription(accountType: AccountType, account: BankAccount['accountData'] | Fund['accountData'] | ACHAccount): string {
if (account) {
if (accountType === CONST.PAYMENT_METHODS.PERSONAL_BANK_ACCOUNT && 'accountNumber' in account) {
return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`;
}
if (accountType === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT && 'accountNumber' in account) {
return `${Localize.translateLocal('paymentMethodList.accountLastFour')} ${account.accountNumber?.slice(-4)}`;
}
if (accountType === CONST.PAYMENT_METHODS.DEBIT_CARD && 'cardNumber' in account) {
return `${Localize.translateLocal('paymentMethodList.cardLastFour')} ${account.cardNumber?.slice(-4)}`;
}
Expand Down
135 changes: 65 additions & 70 deletions src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import {ActivityIndicator, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import ConfirmModal from '@components/ConfirmModal';
import getBankIcon from '@components/Icon/BankIcons';
import type {BankName} from '@components/Icon/BankIconsUtils';
import * as Illustrations from '@components/Icon/Illustrations';
import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Section from '@components/Section';
import Text from '@components/Text';
Expand All @@ -17,6 +20,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getPaymentMethodDescription} from '@libs/PaymentUtils';
import Permissions from '@libs/Permissions';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
Expand Down Expand Up @@ -51,7 +55,6 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr

const policyApproverEmail = policy?.approver;
const policyApproverName = useMemo(() => PersonalDetailsUtils.getPersonalDetailByEmail(policyApproverEmail ?? '')?.displayName ?? policyApproverEmail, [policyApproverEmail]);
const containerStyle = useMemo(() => [styles.ph8, styles.mhn8, styles.ml11, styles.pv3, styles.pr0, styles.pl4, styles.mr0, styles.widthAuto, styles.mt4], [styles]);
const canUseDelayedSubmission = Permissions.canUseWorkflowsDelayedSubmission(betas);
const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false);

Expand Down Expand Up @@ -87,6 +90,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
const optionItems: ToggleSettingOptionRowProps[] = useMemo(() => {
const {accountNumber, addressName, bankName, bankAccountID} = policy?.achAccount ?? {};
const shouldShowBankAccount = !!bankAccountID && policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES;
const bankIcon = getBankIcon({bankName: bankName as BankName, isCard: false, styles});

let bankDisplayName = bankName ?? addressName;
if (accountNumber && bankDisplayName !== accountNumber) {
Expand All @@ -100,7 +104,6 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
...(canUseDelayedSubmission
? [
{
icon: Illustrations.ReceiptEnvelope,
title: translate('workflowsPage.delaySubmissionTitle'),
subtitle: translate('workflowsPage.delaySubmissionDescription'),
switchAccessibilityLabel: translate('workflowsPage.delaySubmissionDescription'),
Expand All @@ -111,20 +114,19 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
);
},
subMenuItems: (
<MenuItem
title={translate('workflowsPage.submissionFrequency')}
titleStyle={styles.textLabelSupportingNormal}
descriptionTextStyle={styles.textNormalThemeText}
onPress={onPressAutoReportingFrequency}
// Instant submit is the equivalent of delayed submissions being turned off, so we show the feature as disabled if the frequency is instant
description={
<MenuItemWithTopDescription
title={
getAutoReportingFrequencyDisplayNames(preferredLocale)[
(PolicyUtils.getCorrectedAutoReportingFrequency(policy) as AutoReportingFrequencyKey) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY
]
}
titleStyle={styles.textNormalThemeText}
descriptionTextStyle={styles.textLabelSupportingNormal}
onPress={onPressAutoReportingFrequency}
// Instant submit is the equivalent of delayed submissions being turned off, so we show the feature as disabled if the frequency is instant
description={translate('workflowsPage.submissionFrequency')}
shouldShowRightIcon
wrapperStyle={containerStyle}
hoverAndPressStyle={[styles.mr0, styles.br2]}
wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]}
brickRoadIndicator={hasDelayedSubmissionError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
),
Expand All @@ -136,23 +138,21 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
]
: []),
{
icon: Illustrations.Approval,
title: translate('workflowsPage.addApprovalsTitle'),
subtitle: translate('workflowsPage.addApprovalsDescription'),
switchAccessibilityLabel: translate('workflowsPage.addApprovalsDescription'),
onToggle: (isEnabled: boolean) => {
Policy.setWorkspaceApprovalMode(route.params.policyID, policy?.owner ?? '', isEnabled ? CONST.POLICY.APPROVAL_MODE.BASIC : CONST.POLICY.APPROVAL_MODE.OPTIONAL);
},
subMenuItems: (
<MenuItem
title={translate('workflowsPage.approver')}
titleStyle={styles.textLabelSupportingNormal}
descriptionTextStyle={styles.textNormalThemeText}
description={policyApproverName ?? ''}
<MenuItemWithTopDescription
title={policyApproverName ?? ''}
titleStyle={styles.textNormalThemeText}
descriptionTextStyle={styles.textLabelSupportingNormal}
description={translate('workflowsPage.approver')}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVER.getRoute(route.params.policyID))}
shouldShowRightIcon
wrapperStyle={containerStyle}
hoverAndPressStyle={[styles.mr0, styles.br2]}
wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]}
brickRoadIndicator={hasApprovalError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
),
Expand All @@ -162,7 +162,6 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
onCloseError: () => Policy.clearPolicyErrorField(policy?.id ?? '-1', CONST.POLICY.COLLECTION_KEYS.APPROVAL_MODE),
},
{
icon: Illustrations.WalletAlt,
title: translate('workflowsPage.makeOrTrackPaymentsTitle'),
subtitle: translate('workflowsPage.makeOrTrackPaymentsDescription'),
switchAccessibilityLabel: translate('workflowsPage.makeOrTrackPaymentsDescription'),
Expand All @@ -188,23 +187,32 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
/>
) : (
<>
{shouldShowBankAccount && (
<View style={[styles.sectionMenuItemTopDescription, styles.mt5, styles.mbn3, styles.pb1, styles.pt1]}>
<Text style={[styles.textLabelSupportingNormal, styles.colorMuted]}>{translate('workflowsPayerPage.paymentAccount')}</Text>
</View>
)}
<MenuItem
titleStyle={shouldShowBankAccount ? styles.textLabelSupportingNormal : styles.textLabelSupportingEmptyValue}
descriptionTextStyle={styles.textNormalThemeText}
title={shouldShowBankAccount ? translate('common.bankAccount') : translate('workflowsPage.connectBankAccount')}
description={bankDisplayName}
disabled={isOffline || !isPolicyAdmin}
shouldGreyOutWhenDisabled={!policy?.pendingFields?.reimbursementChoice}
title={shouldShowBankAccount ? addressName : translate('workflowsPage.connectBankAccount')}
titleStyle={shouldShowBankAccount ? undefined : styles.textLabelSupportingEmptyValue}
description={getPaymentMethodDescription(CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT, policy?.achAccount ?? {})}
onPress={() => {
if (!Policy.isCurrencySupportedForDirectReimbursement(policy?.outputCurrency ?? '')) {
setIsCurrencyModalOpen(true);
return;
}
navigateToBankAccountRoute(route.params.policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(route.params.policyID));
}}
icon={shouldShowBankAccount ? bankIcon.icon : undefined}
iconHeight={bankIcon.iconHeight ?? bankIcon.iconSize}
iconWidth={bankIcon.iconWidth ?? bankIcon.iconSize}
iconStyles={bankIcon.iconStyles}
disabled={isOffline || !isPolicyAdmin}
shouldGreyOutWhenDisabled={!policy?.pendingFields?.reimbursementChoice}
shouldShowRightIcon={!isOffline && isPolicyAdmin}
wrapperStyle={containerStyle}
hoverAndPressStyle={[styles.mr0, styles.br2]}
wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]}
displayInDefaultIconColor
brickRoadIndicator={hasReimburserError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
{shouldShowBankAccount && (
<OfflineWithFeedback
Expand All @@ -214,15 +222,14 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
onClose={() => Policy.clearPolicyErrorField(policy?.id ?? '', CONST.POLICY.COLLECTION_KEYS.REIMBURSER)}
errorRowStyles={[styles.ml7]}
>
<MenuItem
titleStyle={styles.textLabelSupportingNormal}
descriptionTextStyle={styles.textNormalThemeText}
title={translate('workflowsPayerPage.title')}
description={displayNameForAuthorizedPayer}
<MenuItemWithTopDescription
title={displayNameForAuthorizedPayer ?? ''}
titleStyle={styles.textNormalThemeText}
descriptionTextStyle={styles.textLabelSupportingNormal}
description={translate('workflowsPayerPage.payer')}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_PAYER.getRoute(route.params.policyID))}
shouldShowRightIcon
wrapperStyle={[...containerStyle, styles.mt0]}
hoverAndPressStyle={[styles.mr0, styles.br2]}
wrapperStyle={[styles.sectionMenuItemTopDescription, styles.mt3, styles.mbn3]}
brickRoadIndicator={hasReimburserError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
</OfflineWithFeedback>
Expand All @@ -238,29 +245,24 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
];
}, [
policy,
route.params.policyID,
styles,
canUseDelayedSubmission,
translate,
policyApproverName,
containerStyle,
onPressAutoReportingFrequency,
preferredLocale,
canUseDelayedSubmission,
displayNameForAuthorizedPayer,
onPressAutoReportingFrequency,
policyApproverName,
isOffline,
theme.spinner,
isPolicyAdmin,
theme,
displayNameForAuthorizedPayer,
route.params.policyID,
]);

const renderOptionItem = (item: ToggleSettingOptionRowProps, index: number) => (
<View
style={styles.mt7}
key={`toggleSettingOptionItem-${index}`}
>
const renderOptionItem = (item: ToggleSettingOptionRowProps) => (
<Section containerStyles={isSmallScreenWidth ? styles.p5 : styles.p8}>
<ToggleSettingOptionRow
icon={item.icon}
title={item.title}
titleStyle={styles.textStrong}
titleStyle={[styles.textHeadline, styles.cardSectionTitle, styles.accountSettingsSectionTitle, styles.textStrong]}
subtitle={item.subtitle}
switchAccessibilityLabel={item.switchAccessibilityLabel}
onToggle={item.onToggle}
Expand All @@ -270,7 +272,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
errors={item.errors}
onCloseError={item.onCloseError}
/>
</View>
</Section>
);

const isPaidGroupPolicy = PolicyUtils.isPaidGroupPolicy(policy);
Expand All @@ -293,26 +295,19 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr
shouldUseScrollView
>
<View style={[styles.mt3, styles.textStrong, isSmallScreenWidth ? styles.workspaceSectionMobile : styles.workspaceSection]}>
<Section
title={translate('workflowsPage.workflowTitle')}
titleStyles={styles.textStrong}
containerStyles={isSmallScreenWidth ? styles.p5 : styles.p8}
>
<View>
<Text style={[styles.mt3, styles.textSupporting]}>{translate('workflowsPage.workflowDescription')}</Text>
{optionItems.map(renderOptionItem)}
<ConfirmModal
title={translate('workspace.bankAccount.workspaceCurrency')}
isVisible={isCurrencyModalOpen}
onConfirm={confirmCurrencyChangeAndHideModal}
onCancel={() => setIsCurrencyModalOpen(false)}
prompt={translate('workspace.bankAccount.updateCurrencyPrompt')}
confirmText={translate('workspace.bankAccount.updateToUSD')}
cancelText={translate('common.cancel')}
danger
/>
</View>
</Section>
<View>
{optionItems.map(renderOptionItem)}
<ConfirmModal
title={translate('workspace.bankAccount.workspaceCurrency')}
isVisible={isCurrencyModalOpen}
onConfirm={confirmCurrencyChangeAndHideModal}
onCancel={() => setIsCurrencyModalOpen(false)}
prompt={translate('workspace.bankAccount.updateCurrencyPrompt')}
confirmText={translate('workspace.bankAccount.updateToUSD')}
cancelText={translate('common.cancel')}
danger
/>
</View>
</View>
</WorkspacePageWithSections>
</AccessOrNotFoundWrapper>
Expand Down
1 change: 1 addition & 0 deletions src/types/onyx/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1576,4 +1576,5 @@ export type {
SageIntacctDataElement,
SageIntacctConnectionsConfig,
SageIntacctExportConfig,
ACHAccount,
};
Loading