Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7d1c0e2
workflow approval limit followup
abzokhattab Dec 23, 2025
4bff09f
filter out removed members from "expenses from" selection list
abzokhattab Dec 23, 2025
7142c13
Reapply "Bring overLimitForwardsTo configuration into New Expensify"
abzokhattab Dec 25, 2025
9da18e9
Merge remote-tracking branch 'origin/main' into workflow-approval-lim…
abzokhattab Dec 25, 2025
d5d026a
fix reset amount on unselecting the approval
abzokhattab Dec 29, 2025
0c10055
Merge remote-tracking branch 'origin/main' into workflow-approval-lim…
abzokhattab Dec 29, 2025
d8b6313
Merge into main
abzokhattab Jan 4, 2026
22db274
dimiss modal on clicking back insde the confirm
abzokhattab Jan 4, 2026
a81fba9
Merge remote-tracking branch 'origin/main' into workflow-approval-lim…
abzokhattab Jan 4, 2026
db3814b
minor edit
abzokhattab Jan 4, 2026
863f071
Merge remote-tracking branch 'origin/main' into workflow-approval-lim…
abzokhattab Jan 6, 2026
60b187f
fixing eslint
abzokhattab Jan 6, 2026
f2ed06d
Use lazy icon loading in ApprovalWorkflowEditor
abzokhattab Jan 6, 2026
f693e3c
fix: clear overLimitForwardsTo and approvalLimit when additional appr…
abzokhattab Jan 6, 2026
eba5e8a
Merge remote-tracking branch 'origin/main' into workflow-approval-lim…
abzokhattab Jan 6, 2026
7cf9e7f
Revert changes in NumberWithSymbolForm
abzokhattab Jan 6, 2026
f4bd906
fixing long email displayed in the approver description
abzokhattab Jan 6, 2026
ae3c936
fix: extract onPress handler to useCallback in ApprovalWorkflowEditor
abzokhattab Jan 6, 2026
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
16 changes: 13 additions & 3 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1754,9 +1754,19 @@ const ROUTES = {
},
WORKSPACE_WORKFLOWS_APPROVALS_APPROVER: {
route: 'workspaces/:policyID/workflows/approvals/approver',
getRoute: (policyID: string, approverIndex: number, backTo?: string) =>
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getUrlWithBackToParam(`workspaces/${policyID}/workflows/approvals/approver?approverIndex=${approverIndex}` as const, backTo),
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/approver?approverIndex=${approverIndex}` as const,
},
WORKSPACE_WORKFLOWS_APPROVALS_APPROVER_CHANGE: {
route: 'workspaces/:policyID/workflows/approvals/approver-change',
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/approver-change?approverIndex=${approverIndex}` as const,
},
WORKSPACE_WORKFLOWS_APPROVALS_APPROVAL_LIMIT: {
route: 'workspaces/:policyID/workflows/approvals/approval-limit',
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/approval-limit?approverIndex=${approverIndex}` as const,
},
WORKSPACE_WORKFLOWS_APPROVALS_OVER_LIMIT_APPROVER: {
route: 'workspaces/:policyID/workflows/approvals/over-limit-approver',
getRoute: (policyID: string, approverIndex: number) => `workspaces/${policyID}/workflows/approvals/over-limit-approver?approverIndex=${approverIndex}` as const,
},
WORKSPACE_WORKFLOWS_PAYER: {
route: 'workspaces/:policyID/workflows/payer',
Expand Down
3 changes: 3 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,9 @@ const SCREENS = {
WORKFLOWS_APPROVALS_EDIT: 'Workspace_Approvals_Edit',
WORKFLOWS_APPROVALS_EXPENSES_FROM: 'Workspace_Workflows_Approvals_Expenses_From',
WORKFLOWS_APPROVALS_APPROVER: 'Workspace_Workflows_Approvals_Approver',
WORKFLOWS_APPROVALS_APPROVER_CHANGE: 'Workspace_Workflows_Approvals_Approver_Change',
WORKFLOWS_APPROVALS_APPROVAL_LIMIT: 'Workspace_Workflows_Approvals_Approval_Limit',
WORKFLOWS_APPROVALS_OVER_LIMIT_APPROVER: 'Workspace_Workflows_Approvals_Over_Limit_Approver',
WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency',
WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset',
WORKFLOWS_CONNECT_EXISTING_BANK_ACCOUNT: 'Workspace_Workflows_Connect_Existing_Bank_Account',
Expand Down
18 changes: 17 additions & 1 deletion src/components/AmountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {getCurrencyDecimals, getLocalizedCurrencySymbol} from '@libs/CurrencyUtils';
import CONST from '@src/CONST';
import NumberWithSymbolForm from './NumberWithSymbolForm';
import type {NumberWithSymbolFormRef} from './NumberWithSymbolForm';
import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types';

type AmountFormProps = {
Expand Down Expand Up @@ -44,8 +45,17 @@ type AmountFormProps = {
/** Whether to hide the currency symbol */
hideCurrencySymbol?: boolean;

/** Whether the input should be disabled */
disabled?: boolean;

/** Reference to the outer element */
ref?: ForwardedRef<BaseTextInputRef>;

/** Reference to the number form for imperative updates */
numberFormRef?: ForwardedRef<NumberWithSymbolFormRef>;

/** Callback when the user presses the submit key (Enter) */
onSubmitEditing?: () => void;
} & Pick<BaseTextInputProps, 'autoFocus' | 'autoGrowExtraSpace' | 'autoGrowMarginSide'>;

/**
Expand All @@ -63,10 +73,13 @@ function AmountForm({
label,
decimals: decimalsProp,
hideCurrencySymbol = false,
disabled = false,
autoFocus,
autoGrowExtraSpace,
autoGrowMarginSide,
onSubmitEditing,
ref,
numberFormRef,
}: AmountFormProps) {
const {preferredLocale} = useLocalize();
const styles = useThemeStyles();
Expand All @@ -89,6 +102,7 @@ function AmountForm({
ref.current = newRef;
}
}}
numberFormRef={numberFormRef}
symbol={getLocalizedCurrencySymbol(preferredLocale, currency) ?? ''}
symbolPosition={CONST.TEXT_INPUT_SYMBOL_POSITION.PREFIX}
isSymbolPressable={isCurrencyPressable}
Expand All @@ -101,9 +115,11 @@ function AmountForm({
autoFocus={autoFocus}
autoGrowExtraSpace={autoGrowExtraSpace}
autoGrowMarginSide={autoGrowMarginSide}
onSubmitEditing={onSubmitEditing}
disabled={disabled}
/>
);
}

export default AmountForm;
export type {AmountFormProps};
export type {AmountFormProps, NumberWithSymbolFormRef};
39 changes: 23 additions & 16 deletions src/components/ApprovalWorkflowSection.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import {Str} from 'expensify-common';
import React, {useCallback, useMemo} from 'react';
import React from 'react';
import {View} from 'react-native';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {sortAlphabetically} from '@libs/OptionsListUtils';
import {getApprovalLimitDescription} from '@libs/WorkflowUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsByEmailSelector} from '@src/selectors/PersonalDetails';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import Icon from './Icon';
import MenuItem from './MenuItem';
Expand All @@ -19,30 +24,30 @@ type ApprovalWorkflowSectionProps = {

/** A function that is called when the section is pressed */
onPress: () => void;

/** Currency used for formatting approval limits */
currency?: string;
};

function ApprovalWorkflowSection({approvalWorkflow, onPress}: ApprovalWorkflowSectionProps) {
function ApprovalWorkflowSection({approvalWorkflow, onPress, currency = CONST.CURRENCY.USD}: ApprovalWorkflowSectionProps) {
const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'Lightbulb', 'Users', 'UserCheck']);
const styles = useThemeStyles();
const theme = useTheme();
const {translate, toLocaleOrdinal, localeCompare} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [personalDetailsByEmail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {
canBeMissing: true,
selector: personalDetailsByEmailSelector,
});

const approverTitle = useCallback(
(index: number) =>
approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`,
[approvalWorkflow.approvers.length, toLocaleOrdinal, translate],
);

const members = useMemo(() => {
if (approvalWorkflow.isDefault) {
return translate('workspace.common.everyone');
}
const approverTitle = (index: number) =>
approvalWorkflow.approvers.length > 1 ? `${toLocaleOrdinal(index + 1, true)} ${translate('workflowsPage.approver').toLowerCase()}` : `${translate('workflowsPage.approver')}`;

return sortAlphabetically(approvalWorkflow.members, 'displayName', localeCompare)
.map((m) => Str.removeSMSDomain(m.displayName))
.join(', ');
}, [approvalWorkflow.isDefault, approvalWorkflow.members, translate, localeCompare]);
const members = approvalWorkflow.isDefault
? translate('workspace.common.everyone')
: sortAlphabetically(approvalWorkflow.members, 'displayName', localeCompare)
.map((m) => Str.removeSMSDomain(m.displayName))
.join(', ');

return (
<PressableWithoutFeedback
Expand Down Expand Up @@ -100,6 +105,8 @@ function ApprovalWorkflowSection({approvalWorkflow, onPress}: ApprovalWorkflowSe
iconFill={theme.icon}
onPress={onPress}
shouldRemoveBackground
helperText={getApprovalLimitDescription({approver, currency, translate, personalDetailsByEmail})}
helperTextStyle={styles.workflowApprovalLimitText}
/>
</View>
))}
Expand Down
1 change: 1 addition & 0 deletions src/components/ApproverSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ function ApproverSelectionList({
addBottomSafeAreaPadding
shouldUpdateFocusedIndex={shouldUpdateFocusedIndex}
showScrollIndicator
isRowMultilineSupported
/>
</FullPageNotFoundView>
</ScreenWrapper>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/chunks/expensify-icons.chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ import Phone from '@assets/images/phone.svg';
import Pin from '@assets/images/pin.svg';
import Plane from '@assets/images/plane.svg';
import Play from '@assets/images/play.svg';
import PlusMinus from '@assets/images/plus-minus.svg';
import Plus from '@assets/images/plus.svg';
import Printer from '@assets/images/printer.svg';
import Profile from '@assets/images/profile.svg';
Expand Down Expand Up @@ -371,6 +372,7 @@ const Expensicons = {
Pin,
Play,
Plus,
PlusMinus,
Printer,
Profile,
QBOSquare,
Expand Down
10 changes: 7 additions & 3 deletions src/components/NumberWithSymbolForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import CONST from '@src/CONST';
import BigNumberPad from './BigNumberPad';
import Button from './Button';
import FormHelpMessage from './FormHelpMessage';
import * as Expensicons from './Icon/Expensicons';
import ScrollView from './ScrollView';
import TextInput from './TextInput';
import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused';
Expand Down Expand Up @@ -85,6 +84,9 @@ type NumberWithSymbolFormProps = {

/** Reference to the outer element */
ref?: ForwardedRef<BaseTextInputRef>;

/** Callback when the user presses the submit key (Enter) */
onSubmitEditing?: () => void;
} & Omit<TextInputWithSymbolProps, 'formattedAmount' | 'onAmountChange' | 'placeholder' | 'onSelectionChange' | 'onKeyPress' | 'onMouseDown' | 'onMouseUp'>;

type NumberWithSymbolFormRef = {
Expand Down Expand Up @@ -146,9 +148,10 @@ function NumberWithSymbolForm({
clearNegative,
ref,
disabled,
onSubmitEditing,
...props
}: NumberWithSymbolFormProps) {
const icons = useMemoizedLazyExpensifyIcons(['DownArrow']);
const icons = useMemoizedLazyExpensifyIcons(['DownArrow', 'PlusMinus']);
const styles = useThemeStyles();
const {toLocaleDigit, numberFormat, translate} = useLocalize();

Expand Down Expand Up @@ -388,6 +391,7 @@ function NumberWithSymbolForm({
autoFocus={props.autoFocus}
autoGrowExtraSpace={props.autoGrowExtraSpace}
autoGrowMarginSide={props.autoGrowMarginSide}
onSubmitEditing={onSubmitEditing}
/>
);
}
Expand Down Expand Up @@ -510,7 +514,7 @@ function NumberWithSymbolForm({
<Button
shouldShowRightIcon
small
iconRight={Expensicons.PlusMinus}
iconRight={icons.PlusMinus}
onPress={toggleNegative}
style={styles.minWidth18}
isContentCentered
Expand Down
3 changes: 2 additions & 1 deletion src/components/WorkspaceMembersSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}
.map((employee): SelectionListApprover | null => {
const email = employee.email;

if (!email) {
if (!email || employee.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
return null;
}

Expand Down Expand Up @@ -110,6 +110,7 @@ function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}
disableMaintainingScrollPosition
addBottomSafeAreaPadding
showScrollIndicator
isRowMultilineSupported
/>
);
}
Expand Down
20 changes: 19 additions & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2298,7 +2298,25 @@ ${amount} für ${merchant} – ${date}`,
},
workflowsApproverPage: {
genericErrorMessage: 'Der Genehmiger konnte nicht geändert werden. Bitte versuche es erneut oder kontaktiere den Support.',
header: 'Zur Genehmigung an dieses Mitglied senden:',
title: 'Zur Genehmigung an dieses Mitglied senden:',
description: 'Diese Person wird die Ausgaben genehmigen.',
},
workflowsApprovalLimitPage: {
title: 'Genehmiger',
header: '(Optional) Möchten Sie ein Genehmigungslimit hinzufügen?',
description: ({approverName}: {approverName: string}) =>
approverName
? `Fügen Sie einen weiteren Genehmiger hinzu, wenn <strong>${approverName}</strong> Genehmiger ist und der Bericht den folgenden Betrag überschreitet:`
: 'Fügen Sie einen weiteren Genehmiger hinzu, wenn der Bericht den folgenden Betrag überschreitet:',
reportAmountLabel: 'Berichtsbetrag',
additionalApproverLabel: 'Zusätzlicher Genehmiger',
skip: 'Überspringen',
next: 'Weiter',
removeLimit: 'Limit entfernen',
enterAmountError: 'Bitte geben Sie einen gültigen Betrag ein',
enterApproverError: 'Ein Genehmiger ist erforderlich, wenn Sie ein Berichtslimit festlegen',
enterBothError: 'Geben Sie einen Berichtsbetrag und einen zusätzlichen Genehmiger ein',
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) => `Berichte über ${approvalLimit} werden an ${approverName} weitergeleitet`,
},
workflowsPayerPage: {
title: 'Autorisierter Zahler',
Expand Down
20 changes: 19 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2251,7 +2251,25 @@ const translations = {
},
workflowsApproverPage: {
genericErrorMessage: "The approver couldn't be changed. Please try again or contact support.",
header: 'Send to this member for approval:',
title: 'Set approver',
description: 'This person will approve the expenses.',
},
workflowsApprovalLimitPage: {
title: 'Approver',
header: '(Optional) Want to add an approval limit?',
description: ({approverName}: {approverName: string}) =>
approverName
? `Add another approver when <strong>${approverName}</strong> is approver and report exceeds the amount below:`
: 'Add another approver when a report exceeds the amount below:',
reportAmountLabel: 'Report amount',
additionalApproverLabel: 'Additional approver',
skip: 'Skip',
next: 'Next',
removeLimit: 'Remove limit',
enterAmountError: 'Please enter a valid amount',
enterApproverError: 'Approver is required when you set a report limit',
enterBothError: 'Enter a report amount and additional approver',
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) => `Reports above ${approvalLimit} forward to ${approverName}`,
},
workflowsPayerPage: {
title: 'Authorized payer',
Expand Down
20 changes: 19 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1942,7 +1942,25 @@ ${amount} para ${merchant} - ${date}`,
},
workflowsApproverPage: {
genericErrorMessage: 'El aprobador no pudo ser cambiado. Por favor, inténtelo de nuevo o contacte al soporte.',
header: 'Enviar a este miembro para su aprobación:',
title: 'Establecer aprobador',
description: 'Esta persona aprobará los gastos.',
},
workflowsApprovalLimitPage: {
title: 'Aprobador',
header: '(Opcional) ¿Quieres añadir un límite de aprobación?',
description: ({approverName}: {approverName: string}) =>
approverName
? `Añadir otro aprobador cuando <strong>${approverName}</strong> es aprobador y el informe supera el importe indicado:`
: 'Añadir otro aprobador cuando el informe supera el importe indicado:',
reportAmountLabel: 'Importe del informe',
additionalApproverLabel: 'Aprobador adicional',
skip: 'Omitir',
next: 'Siguiente',
removeLimit: 'Eliminar límite',
enterAmountError: 'Por favor, introduce un importe válido',
enterApproverError: 'Se requiere un aprobador cuando estableces un límite de informe',
enterBothError: 'Introduce un importe del informe y un aprobador adicional',
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) => `Los informes superiores a ${approvalLimit} se envían a ${approverName}`,
},
workflowsPayerPage: {
title: 'Pagador autorizado',
Expand Down
21 changes: 20 additions & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2301,7 +2301,26 @@ ${amount} pour ${merchant} - ${date}`,
},
workflowsApproverPage: {
genericErrorMessage: 'Le valideur n’a pas pu être modifié. Veuillez réessayer ou contacter l’assistance.',
header: 'Envoyer à ce membre pour approbation :',
title: 'Définir l’approbateur',
description: 'Cette personne approuvera les dépenses.',
},
workflowsApprovalLimitPage: {
title: 'Approbateur',
header: "(Optionnel) Voulez-vous ajouter une limite d'approbation ?",
description: ({approverName}: {approverName: string}) =>
approverName
? `Ajoutez un autre approbateur lorsque <strong>${approverName}</strong> est approbateur et que le rapport dépasse le montant ci-dessous :`
: 'Ajoutez un autre approbateur lorsque le rapport dépasse le montant ci-dessous :',
reportAmountLabel: 'Montant du rapport',
additionalApproverLabel: 'Approbateur supplémentaire',
skip: 'Passer',
next: 'Suivant',
removeLimit: 'Supprimer la limite',
enterAmountError: 'Veuillez entrer un montant valide',
enterApproverError: 'Un approbateur est requis lorsque vous définissez une limite de rapport',
enterBothError: 'Entrez un montant de rapport et un approbateur supplémentaire',
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) =>
`Les rapports supérieurs à ${approvalLimit} sont transférés à ${approverName}`,
},
workflowsPayerPage: {
title: 'Payeur autorisé',
Expand Down
21 changes: 20 additions & 1 deletion src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2290,7 +2290,26 @@ ${amount} per ${merchant} - ${date}`,
},
workflowsApproverPage: {
genericErrorMessage: "Non è stato possibile modificare l'approvatore. Riprova o contatta l'assistenza.",
header: 'Invia a questo membro per approvazione:',
title: 'Invia a questo membro per approvazione:',
description: 'Questa persona approverà le spese.',
},
workflowsApprovalLimitPage: {
title: 'Approvatore',
header: '(Opzionale) Vuoi aggiungere un limite di approvazione?',
description: ({approverName}: {approverName: string}) =>
approverName
? `Aggiungi un altro approvatore quando <strong>${approverName}</strong> è approvatore e il report supera l'importo seguente:`
: "Aggiungi un altro approvatore quando il report supera l'importo seguente:",
reportAmountLabel: 'Importo del report',
additionalApproverLabel: 'Approvatore aggiuntivo',
skip: 'Salta',
next: 'Avanti',
removeLimit: 'Rimuovi limite',
enterAmountError: 'Inserisci un importo valido',
enterApproverError: 'Un approvatore è richiesto quando imposti un limite di report',
enterBothError: 'Inserisci un importo del report e un approvatore aggiuntivo',
forwardLimitDescription: ({approvalLimit, approverName}: {approvalLimit: string; approverName: string}) =>
`I report superiori a ${approvalLimit} vengono inoltrati a ${approverName}`,
},
workflowsPayerPage: {
title: 'Pagatore autorizzato',
Expand Down
Loading
Loading