Skip to content
Open
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
1 change: 0 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,6 @@ const CONST = {
SUGGESTED_FOLLOWUPS: 'suggestedFollowups',
BULK_EDIT: 'bulkEdit',
BULK_EDIT_WORKSPACES: 'bulkEditWorkspaces',
NEW_MANUAL_EXPENSE_FLOW: 'newManualExpenseFlow',
SUBMIT_2026: 'submit2026',
BULK_SUBMIT_APPROVE_PAY: 'bulkSubmitApprovePay',
WORKSPACE_ROOMS_PAGE: 'workspaceRoomsPage',
Expand Down
5 changes: 0 additions & 5 deletions src/components/MoneyRequestConfirmationFields/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ type ProviderProps = {
/** Whether we're editing an existing split expense */
isEditingSplitBill?: boolean;

/** Whether the new manual expense flow beta is enabled */
isNewManualExpenseFlowEnabled?: boolean;

/** Whether the surface is in a policy-expense chat */
isPolicyExpenseChat?: boolean;

Expand Down Expand Up @@ -84,7 +81,6 @@ function Provider({
isReadOnly = false,
didConfirm = false,
isEditingSplitBill = false,
isNewManualExpenseFlowEnabled = false,
isPolicyExpenseChat = false,
isDistanceRequest = false,
isPerDiemRequest = false,
Expand All @@ -107,7 +103,6 @@ function Provider({
isReadOnly,
didConfirm,
isEditingSplitBill,
isNewManualExpenseFlowEnabled,
isPolicyExpenseChat,
isDistanceRequest,
isPerDiemRequest,
Expand Down
1 change: 0 additions & 1 deletion src/components/MoneyRequestConfirmationFields/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type ConfirmationFieldsContextValue = {
isReadOnly: boolean;
didConfirm: boolean;
isEditingSplitBill: boolean;
isNewManualExpenseFlowEnabled: boolean;
isPolicyExpenseChat: boolean;

// Mode — *what kind* of expense is being confirmed
Expand Down
18 changes: 2 additions & 16 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'
import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
import useLocalize from '@hooks/useLocalize';
import {MouseProvider} from '@hooks/useMouseContext';
import usePermissions from '@hooks/usePermissions';
import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses';
import usePolicyForTransaction from '@hooks/usePolicyForTransaction';
import usePreferredPolicy from '@hooks/usePreferredPolicy';
Expand All @@ -14,7 +13,6 @@ import {isCategoryDescriptionRequired} from '@libs/CategoryUtils';
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
import {isMovingTransactionFromTrackExpense as isMovingTransactionFromTrackExpenseUtil} from '@libs/IOUUtils';
import {shouldShowConfirmationDate} from '@libs/MoneyRequestUtils';
import Navigation from '@libs/Navigation/Navigation';
import {hasEnabledOptions} from '@libs/OptionsListUtils';
import {arePolicyRulesEnabled, isTaxTrackingEnabled} from '@libs/PolicyUtils';
import type {OptionData} from '@libs/ReportUtils';
Expand All @@ -32,7 +30,6 @@ import {

import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {Participant} from '@src/types/onyx/IOU';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
Expand Down Expand Up @@ -220,8 +217,6 @@ function MoneyRequestConfirmationList({
const transactionReport = useTransactionReportForConfirmation(transaction?.reportID);
const {policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses();
const isMovingTransactionFromTrackExpense = isMovingTransactionFromTrackExpenseUtil(action);
const {isBetaEnabled} = usePermissions();
const isNewManualExpenseFlowEnabled = isBetaEnabled(CONST.BETAS.NEW_MANUAL_EXPENSE_FLOW);
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
const isInLandscapeMode = useIsInLandscapeMode();
Expand Down Expand Up @@ -325,7 +320,7 @@ function MoneyRequestConfirmationList({
});

const isManualRequest = transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL;
const shouldForceTopEmptySections = isNewManualExpenseFlowEnabled && (iouType === CONST.IOU.TYPE.CREATE || isManualRequest || isScanRequest);
const shouldForceTopEmptySections = iouType === CONST.IOU.TYPE.CREATE || isManualRequest || isScanRequest;

const isFocused = useIsFocused();

Expand Down Expand Up @@ -360,7 +355,6 @@ function MoneyRequestConfirmationList({
routeError,
isTypeSplit,
shouldShowReadOnlySplits,
isNewManualExpenseFlowEnabled,
isDistanceRequest,
});

Expand Down Expand Up @@ -390,7 +384,6 @@ function MoneyRequestConfirmationList({
receiptPath,
isDistanceRequestWithPendingRoute,
isPerDiemRequest,
isNewManualExpenseFlowEnabled,
});

const selectedParticipants = selectedParticipantsProp.filter((participant) => participant.selected);
Expand Down Expand Up @@ -447,13 +440,7 @@ function MoneyRequestConfirmationList({
return;
}

if (isNewManualExpenseFlowEnabled) {
onOpenParticipantPicker?.();
return;
}

const newIOUType = iouType === CONST.IOU.TYPE.SUBMIT || iouType === CONST.IOU.TYPE.TRACK ? CONST.IOU.TYPE.CREATE : iouType;
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(newIOUType, transactionID, transaction?.reportID, Navigation.getActiveRoute(), action));
onOpenParticipantPicker?.();
};

const {validate} = useConfirmationValidation({
Expand Down Expand Up @@ -483,7 +470,6 @@ function MoneyRequestConfirmationList({
isPerDiemRequest,
isTimeRequest,
routeError,
isNewManualExpenseFlowEnabled,
isReadOnly,
shouldShowDate: shouldShowConfirmationDate(shouldShowSmartScanFields, isDistanceRequest),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,14 @@ type UseConfirmationCtaTextParams = {

/** Whether the transaction is a per-diem request */
isPerDiemRequest: boolean;

/** Whether the new manual expense flow beta is enabled */
isNewManualExpenseFlowEnabled: boolean;
};

/**
* Computes the primary confirm button label for the Money Request confirmation flow.
*
* Picks between create / create-with-amount / split / invoice / next variants based on
* the IOU type, manual-expense-flow beta, bulk-expense count, and amount, returning a
* single-entry DropdownOption array shaped for the ButtonWithDropdownMenu consumer.
* Picks between create / split / invoice / next variants based on the IOU type,
* bulk-expense count, and amount, returning a single-entry DropdownOption array shaped
* for the ButtonWithDropdownMenu consumer.
*/
function useConfirmationCtaText({
expensesNumber,
Expand All @@ -70,7 +67,6 @@ function useConfirmationCtaText({
receiptPath,
isDistanceRequestWithPendingRoute,
isPerDiemRequest,
isNewManualExpenseFlowEnabled,
}: UseConfirmationCtaTextParams): Array<DropdownOption<string>> {
const {translate} = useLocalize();

Expand All @@ -85,27 +81,14 @@ function useConfirmationCtaText({
}
} else if (isTypeTrackExpense) {
text = translate('iou.createExpense');
if (iouAmount !== 0 && !isNewManualExpenseFlowEnabled) {
text = translate('iou.createExpenseWithAmount', {amount: formattedAmount});
}
} else if (isTypeSplit && iouAmount === 0) {
text = translate('iou.splitExpense');
} else if ((receiptPath && isTypeRequest) || isDistanceRequestWithPendingRoute || isPerDiemRequest) {
text = translate('iou.createExpense');
if (iouAmount !== 0 && !isNewManualExpenseFlowEnabled) {
text = translate('iou.createExpenseWithAmount', {amount: formattedAmount});
}
} else if (isTypeSplit) {
text = translate('iou.splitAmount', formattedAmount);
if (isNewManualExpenseFlowEnabled) {
text = translate('iou.splitExpense');
}
} else if (iouAmount === 0) {
text = translate('iou.createExpense');
} else if (isNewManualExpenseFlowEnabled) {
text = translate('iou.createExpense');
text = translate('iou.splitExpense');
} else {
text = translate('iou.createExpenseWithAmount', {amount: formattedAmount});
text = translate('iou.createExpense');
}
return [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@ type UseConfirmationValidationParams = {
/** Truthy when the route to the confirmation page has a known error */
routeError: string | null | undefined;

/** Whether the new manual expense flow is enabled */
isNewManualExpenseFlowEnabled: boolean;

/** Whether the confirmation fields are read-only (date is not inline-editable) */
isReadOnly: boolean;

Expand Down Expand Up @@ -163,7 +160,6 @@ function useConfirmationValidation({
isPerDiemRequest,
isTimeRequest,
routeError,
isNewManualExpenseFlowEnabled,
isReadOnly,
shouldShowDate,
}: UseConfirmationValidationParams): {validate: (paymentType?: PaymentMethodType) => ValidationResult | null} {
Expand All @@ -186,11 +182,10 @@ function useConfirmationValidation({
return {errorKey: 'common.error.invalidAmount'};
}
// isAmountSet only applies to manual expenses — scan, per diem, distance, and time set amount programmatically.
if (isNewManualExpenseFlowEnabled && transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL && !transaction?.isAmountSet) {
if (transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL && !transaction?.isAmountSet) {
return {errorKey: 'common.error.fieldRequired'};
Comment on lines +185 to 186

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't require isAmountSet on existing manual transactions

When this confirmation is used for existing manual/split expenses that predate isAmountSet (or are built without it, e.g. split transactions from createSplitsAndOnyxData), the transaction can still have a valid non-zero amount but no isAmountSet flag. With the beta guard removed, this branch now treats those persisted amounts as missing and returns fieldRequired, so users can be blocked from saving/editing until they re-enter the amount; distinguish an explicitly cleared new draft from older data that simply lacks the flag.

Useful? React with 👍 / 👎.

}
if (
isNewManualExpenseFlowEnabled &&
transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL &&
transaction?.isAmountSet &&
!isScanRequestUtil(transaction) &&
Expand All @@ -205,7 +200,7 @@ function useConfirmationValidation({
// (manual, distance, time, invoice, ...). Block confirmation when the user cleared it. Gating on the same
// `shouldShowDate && !isReadOnly` condition that renders the inline picker keeps validation and UI in sync,
// and skips read-only/scan flows where the date is populated server-side.
if (isNewManualExpenseFlowEnabled && shouldShowDate && !isReadOnly && isCreatedMissing(transaction)) {
if (shouldShowDate && !isReadOnly && isCreatedMissing(transaction)) {
return {errorKey: 'common.error.fieldRequired'};
}
const merchantValue = iouMerchant ?? '';
Expand Down Expand Up @@ -268,7 +263,7 @@ function useConfirmationValidation({
// In the new manual expense flow the tax amount is edited inline, so the standalone tax amount step's
// guard (tax amount can't exceed the tax computed from the rate and the expense amount) runs here.
// This also blocks creation when an invalid tax amount was persisted to the draft and then reloaded.
if (isNewManualExpenseFlowEnabled && shouldShowTax && !isDistanceRequest) {
if (shouldShowTax && !isDistanceRequest) {
const decimals = getCurrencyDecimals(iouCurrencyCode);
const maxTaxAmount = getCalculatedTaxAmount(policy, transaction, iouCurrencyCode, decimals);
const currentTaxAmount = convertToFrontendAmountAsString(Math.abs(getTaxAmount(transaction, false)), decimals);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ type UseFormErrorManagementParams = {
/** Whether splits are rendered read-only (suppresses some field errors) */
shouldShowReadOnlySplits: boolean;

/** Whether the new manual expense flow is enabled (amount/date errors surface inline) */
isNewManualExpenseFlowEnabled: boolean;

/** Whether the transaction is a distance request (its amount is read-only, so amount errors are not shown inline) */
isDistanceRequest: boolean;
};
Expand Down Expand Up @@ -141,7 +138,6 @@ function useFormErrorManagement({
routeError,
isTypeSplit,
shouldShowReadOnlySplits,
isNewManualExpenseFlowEnabled,
isDistanceRequest,
}: UseFormErrorManagementParams): UseFormErrorManagementResult {
const isFocused = useIsFocused();
Expand Down Expand Up @@ -246,15 +242,15 @@ function useFormErrorManagement({
if (formError === 'iou.error.invalidTaxAmount') {
return undefined;
}
// In the new manual expense flow the amount/date/merchant fields surface these required/invalid errors inline, so
// The amount/date/merchant fields surface these required/invalid errors inline, so
// don't repeat them at the bottom of the form (which would show "This field is required" twice).
if (isNewManualExpenseFlowEnabled && (formError === 'common.error.fieldRequired' || formError === 'iou.error.invalidMerchant')) {
if (formError === 'common.error.fieldRequired' || formError === 'iou.error.invalidMerchant') {
return undefined;
}
// `common.error.invalidAmount` is only surfaced inline when the editable amount input is rendered. Distance requests
// disable that input (the amount falls back to a read-only menu row that doesn't show this error), so keep the
// distance-amount validation error in the footer — otherwise an invalid distance expense would fail silently.
if (isNewManualExpenseFlowEnabled && !isDistanceRequest && formError === 'common.error.invalidAmount') {
if (!isDistanceRequest && formError === 'common.error.invalidAmount') {
return undefined;
}
return formError ? translate(formError) : undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ type AmountFieldProps = {
distanceRateCurrency: string;
iouCurrencyCode: string | undefined;
isDistanceRequest: boolean;
isNewManualExpenseFlowEnabled: boolean;
didConfirm: boolean;
isReadOnly: boolean;
shouldShowTimeRequestFields: boolean;
Expand All @@ -66,7 +65,6 @@ function AmountField({
distanceRateCurrency,
iouCurrencyCode,
isDistanceRequest,
isNewManualExpenseFlowEnabled,
didConfirm,
isReadOnly,
shouldShowTimeRequestFields,
Expand Down Expand Up @@ -100,10 +98,7 @@ function AmountField({
const [isCurrencyPickerVisible, setIsCurrencyPickerVisible] = useState(false);

const isAmountFieldDisabled = didConfirm || isReadOnly || shouldShowTimeRequestFields || isDistanceRequest;
const firstParticipant = transactionSlice?.participants?.at(0);
const isP2P = isNewManualExpenseFlowEnabled
? isParticipantP2P(getMoneyRequestParticipantsFromReport(report, currentUserPersonalDetails.accountID).at(0))
: !!(firstParticipant?.accountID && !firstParticipant?.isPolicyExpenseChat);
const isP2P = isParticipantP2P(getMoneyRequestParticipantsFromReport(report, currentUserPersonalDetails.accountID).at(0));
// `common.error.fieldRequired` is shared with the date field, so only surface it on the amount input when the
// amount itself is the missing value.
const shouldShowAmountRequiredError = formError === 'common.error.fieldRequired' && !transactionSlice?.isAmountSet;
Expand All @@ -122,17 +117,17 @@ function AmountField({
// touches it). Once the user explicitly sets an amount – including 0 – isAmountSet becomes true and we show the
// real value. This avoids showing "$0.00" as a pre-filled default. Scan and other non-manual flows populate
// amount programmatically and never set isAmountSet.
const shouldShowEmptyAmount = isNewManualExpenseFlowEnabled && !transactionSlice?.isAmountSet && transactionSlice?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL;
const shouldShowEmptyAmount = !transactionSlice?.isAmountSet && transactionSlice?.iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL;
const transactionAmount = shouldShowEmptyAmount ? '' : convertToFrontendAmountAsString(amount, decimals);
const allowNegative = shouldEnableNegative(report, policy, iouType, transactionSlice?.participants, isNewManualExpenseFlowEnabled);
const allowNegative = shouldEnableNegative(report, policy, iouType, transactionSlice?.participants, true);

// `autoFocus` on our TextInput only runs on mount. Closing and reopening the RHP often keeps the same mounted
// instance, so autofocus does not run again. We re-focus when the parent-owned participant picker closes
// (visible → hidden) so the amount input gains focus once the user selects a participant in the new manual
// expense flow. The setTimeout defers focus past the RHP entry / picker close animation so the input reliably
// receives focus.
useEffect(() => {
if (!autoFocus || isAmountFieldDisabled || !isNewManualExpenseFlowEnabled || isParticipantPickerVisible) {
if (!autoFocus || isAmountFieldDisabled || isParticipantPickerVisible) {
return;
}

Expand All @@ -144,7 +139,7 @@ function AmountField({
}
clearTimeout(focusTimeoutRef.current);
};
}, [autoFocus, isAmountFieldDisabled, isNewManualExpenseFlowEnabled, isParticipantPickerVisible]);
}, [autoFocus, isAmountFieldDisabled, isParticipantPickerVisible]);

const showCurrencyPicker = () => {
setIsCurrencyPickerVisible(true);
Expand Down Expand Up @@ -307,7 +302,7 @@ function AmountField({
value={effectiveCurrency}
onInputChange={updateCurrency}
/>
{isNewManualExpenseFlowEnabled && !isAmountFieldDisabled ? (
{!isAmountFieldDisabled ? (
<View style={[styles.mh4, styles.mv2]}>
<NumberWithSymbolForm
ref={amountInputRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ type DateFieldProps = {
shouldDisplayFieldError: boolean;
didConfirm: boolean;
isReadOnly: boolean;
isNewManualExpenseFlowEnabled: boolean;
formError: string;
clearFormErrors: (errors: string[]) => void;
transactionID: string | undefined;
Expand All @@ -43,19 +42,7 @@ type DateFieldProps = {
reportActionID: string | undefined;
};

function DateField({
shouldDisplayFieldError,
didConfirm,
isReadOnly,
isNewManualExpenseFlowEnabled,
formError,
clearFormErrors,
transactionID,
action,
iouType,
reportID,
reportActionID,
}: DateFieldProps) {
function DateField({shouldDisplayFieldError, didConfirm, isReadOnly, formError, clearFormErrors, transactionID, action, iouType, reportID, reportActionID}: DateFieldProps) {
const {isEditingSplitBill} = useConfirmationFields();
const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down Expand Up @@ -117,7 +104,7 @@ function DateField({
}
};

if (isNewManualExpenseFlowEnabled && !isReadOnly) {
if (!isReadOnly) {
return (
<View style={[styles.mh4, styles.mb2]}>
<DatePicker
Expand Down
Loading
Loading