Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
4bf3874
feat: seperate approve btn for help expense
mkzie2 Sep 18, 2025
6ee1f5d
fix: btn
mkzie2 Sep 18, 2025
28a9a4b
chore: add missing trans
mkzie2 Sep 18, 2025
1b27fa8
fix: icon
mkzie2 Sep 18, 2025
e00cff4
Merge branch 'main' of https://github.com/Expensify/App into mkzie2-i…
mkzie2 Sep 23, 2025
fd0d9e4
fix: comments
mkzie2 Sep 23, 2025
861331b
chore: refactor code
mkzie2 Sep 23, 2025
9fac525
Merge branch 'main' of https://github.com/Expensify/App into mkzie2-i…
mkzie2 Oct 7, 2025
82c1b65
chore: update trans
mkzie2 Oct 7, 2025
4b0993c
fix: refactoring
mkzie2 Oct 7, 2025
7a9b89e
fix: refactoring
mkzie2 Oct 7, 2025
00b8ca5
chore: compress svg
mkzie2 Oct 7, 2025
a92a6bb
Merge branch 'main' of https://github.com/Expensify/App into mkzie2-i…
mkzie2 Oct 7, 2025
672d61f
fix: test
mkzie2 Oct 7, 2025
1c58480
fix: conflicts
mkzie2 Oct 9, 2025
04f6506
fix: comments
mkzie2 Oct 9, 2025
3ba8272
Merge branch 'main' of https://github.com/Expensify/App into mkzie2-i…
mkzie2 Oct 20, 2025
88ed5e1
fix: comments
mkzie2 Oct 20, 2025
a11dde5
Merge branch 'main' of https://github.com/Expensify/App into mkzie2-i…
mkzie2 Oct 23, 2025
8ed9d7f
chore: remove redundant codes
mkzie2 Oct 23, 2025
88850a0
fix: conflicts
mkzie2 Oct 24, 2025
4c3ff44
chore: prettier
mkzie2 Oct 24, 2025
1bbb3d2
chore: merge main
mkzie2 Oct 27, 2025
b16c682
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Nov 7, 2025
9816397
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Nov 12, 2025
d692b17
revert unneeded changes
mkzie2 Nov 12, 2025
f80a08e
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Nov 22, 2025
651be2a
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Nov 25, 2025
4757c2e
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Nov 26, 2025
6958c6e
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 3, 2025
3684616
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 4, 2025
47d4d26
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 5, 2025
1575a00
fix: typecheck
mkzie2 Dec 5, 2025
d1298fc
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 9, 2025
b7d4ac2
fix typecheck
mkzie2 Dec 9, 2025
3637dc6
revert: prev commit
mkzie2 Dec 9, 2025
194f68a
fix: correct params
mkzie2 Dec 9, 2025
5a31c3a
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 9, 2025
f9f4588
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 17, 2025
051f188
fix: compiler check
mkzie2 Dec 17, 2025
9f5774a
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 18, 2025
d195f41
fix: approve button in more menu
mkzie2 Dec 18, 2025
dda0aa8
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 22, 2025
447b07d
fix: apply suggestions
mkzie2 Dec 22, 2025
987b5ec
fix: typecheck
mkzie2 Dec 22, 2025
e912913
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Dec 29, 2025
80dd3cb
apply suggestions
mkzie2 Dec 29, 2025
e7ab123
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 3, 2026
6a01abe
fix lint
mkzie2 Jan 3, 2026
2e9e9f3
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 6, 2026
6c4fa33
fix: header text doesn't disappear
mkzie2 Jan 6, 2026
304cd4f
fix: remove unused prop
mkzie2 Jan 6, 2026
5611f7a
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 8, 2026
45a64ea
refactor logics
mkzie2 Jan 8, 2026
995935c
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 13, 2026
a5fb87f
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 15, 2026
49412e1
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 20, 2026
a3c1704
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 23, 2026
f14d04a
fix: inconsistent header text
mkzie2 Jan 23, 2026
6f6c7d0
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 26, 2026
f066b06
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Jan 27, 2026
601f17d
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Feb 2, 2026
303d867
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Feb 5, 2026
0e8b5dd
implement new header logics
mkzie2 Feb 5, 2026
79bc9aa
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Feb 10, 2026
0656dab
fix: remove unused props and modal not closed
mkzie2 Feb 10, 2026
53cfca3
Merge branch 'main' into mkzie2-issue/70383
mkzie2 Feb 20, 2026
0252bb8
fix: apply suggesgted fixes
mkzie2 Feb 20, 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
1 change: 1 addition & 0 deletions assets/images/document-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/components/ButtonWithDropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
shouldUseModalPaddingStyle = true,
shouldUseShortForm = false,
shouldUseOptionIcon = false,
headerTextStyles,
shouldStayNormalOnDisable = false,
sentryLabel,
shouldPutHeaderTextAfterBackButton = false,
} = props;

const icons = useMemoizedLazyExpensifyIcons(['DownArrow']);
Expand Down Expand Up @@ -273,6 +275,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
setIsMenuVisible(false);
}
}}
headerStyles={headerTextStyles}
anchorPosition={popoverAnchorPosition}
shouldShowSelectedItemCheck={shouldShowSelectedItemCheck}
anchorRef={nullCheckRef(dropdownAnchor)}
Expand Down Expand Up @@ -302,6 +305,7 @@ function ButtonWithDropdownMenu<IValueType>({ref, ...props}: ButtonWithDropdownM
shouldCallAfterModalHide: true,
subMenuItems: item.subMenuItems?.map((subItem) => ({...subItem, shouldCallAfterModalHide: true})),
}))}
shouldPutHeaderTextAfterBackButton={shouldPutHeaderTextAfterBackButton}
/>
)}
</View>
Expand Down
6 changes: 6 additions & 0 deletions src/components/ButtonWithDropdownMenu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,14 @@ type ButtonWithDropdownMenuProps<TValueType> = WithSentryLabel & {
/** Whether to display the option icon when only one option is available */
shouldUseOptionIcon?: boolean;

/** Used to apply styles specifically to the header text */
headerTextStyles?: StyleProp<TextStyle>;

/** Reference to the outer element */
ref?: React.Ref<ButtonWithDropdownMenuRef>;

/** Whether to put the header text after the back button */
shouldPutHeaderTextAfterBackButton?: boolean;
};

type ButtonWithDropdownMenuRef = {
Expand Down
151 changes: 151 additions & 0 deletions src/components/ExpenseHeaderApprovalButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
import type {LocaleContextProps} from './LocaleContextProvider';

type ExpenseHeaderApprovalButtonProps = {
/** Whether any transaction is on hold */
isAnyTransactionOnHold: boolean;

/** Whether delegate access is restricted */
isDelegateAccessRestricted: boolean;

/** Whether the report has only held expenses */
hasOnlyHeldExpenses: boolean;

/** Whether there is a valid non-held amount */
hasValidNonHeldAmount: boolean;

/** The non-held amount string */
nonHeldAmount: string | undefined;

/** The full amount string */
fullAmount: string;

/** Callback when approval is confirmed */
onApprove: (isFullApproval: boolean) => void;

/** Whether the button is disabled */
isDisabled?: boolean;
};

type ApprovalOption = {
value: string;
text: string;
icon: IconAsset;
onSelected: () => void;
keyForList: string;
};

type ApprovalDropdownOptionProps = {
nonHeldAmount: string | undefined;
fullAmount: string;
hasValidNonHeldAmount: boolean;
hasOnlyHeldExpenses: boolean;
onPartialApprove: () => void;
onFullApprove: () => void;
translate: LocaleContextProps['translate'];
illustrations: Record<'ThumbsUp' | 'DocumentCheck', IconAsset>;
};

/**
* Generates dropdown options for approve button when there are held expenses
*/
function getApprovalDropdownOptions({
nonHeldAmount,
fullAmount,
hasValidNonHeldAmount,
hasOnlyHeldExpenses,
onPartialApprove,
onFullApprove,
translate,
illustrations,
}: ApprovalDropdownOptionProps): ApprovalOption[] {
const APPROVE_PARTIAL = 'approve_partial';
const APPROVE_FULL = 'approve_full';
const options: ApprovalOption[] = [];

if (nonHeldAmount && hasValidNonHeldAmount && !hasOnlyHeldExpenses) {
options.push({
value: APPROVE_PARTIAL,
text: `${translate('iou.approveOnly')} ${nonHeldAmount}`,
icon: illustrations.ThumbsUp,
onSelected: onPartialApprove,
keyForList: APPROVE_PARTIAL,
});
}

options.push({
value: APPROVE_FULL,
text: `${translate('iou.approve')} ${fullAmount}`,
icon: illustrations.DocumentCheck,
onSelected: onFullApprove,
keyForList: APPROVE_FULL,
});

return options;
}

function ExpenseHeaderApprovalButton({
isAnyTransactionOnHold,
isDelegateAccessRestricted,
hasOnlyHeldExpenses,
hasValidNonHeldAmount,
nonHeldAmount,
fullAmount,
onApprove,
isDisabled = false,
}: ExpenseHeaderApprovalButtonProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const illustrations = useMemoizedLazyExpensifyIcons(['ThumbsUp', 'DocumentCheck']);

const shouldShowDropdown = isAnyTransactionOnHold && !isDelegateAccessRestricted;

if (shouldShowDropdown) {
const approvalOptions = getApprovalDropdownOptions({
nonHeldAmount: !hasOnlyHeldExpenses && hasValidNonHeldAmount ? nonHeldAmount : undefined,
fullAmount,
hasValidNonHeldAmount,
hasOnlyHeldExpenses,
onPartialApprove: () => onApprove(false),
onFullApprove: () => onApprove(true),
translate,
illustrations,
});

if (approvalOptions.length > 1) {
return (
<ButtonWithDropdownMenu
success
options={approvalOptions}
menuHeaderText={translate('iou.confirmApprovalWithHeldAmount')}
onPress={() => {}}
customText={translate('iou.approve')}
headerTextStyles={styles.lineHeightNormal}
shouldAlwaysShowDropdownMenu
isSplitButton={false}
isDisabled={isDisabled}
/>
);
}
}

return (
<Button
success
onPress={() => onApprove(true)}
text={translate('iou.approve')}
sentryLabel={CONST.SENTRY_LABEL.REPORT_PREVIEW.APPROVE_BUTTON}
isDisabled={isDisabled}
/>
);
}

export default ExpenseHeaderApprovalButton;
export {getApprovalDropdownOptions};
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import Copy from '@assets/images/copy.svg';
import CreditCardExclamation from '@assets/images/credit-card-exclamation.svg';
import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg';
import CreditCard from '@assets/images/creditcard.svg';
import DocumentCheck from '@assets/images/document-check.svg';
import DocumentMerge from '@assets/images/document-merge.svg';
import Document from '@assets/images/document.svg';
import DotIndicatorUnfilled from '@assets/images/dot-indicator-unfilled.svg';
Expand Down Expand Up @@ -325,6 +326,7 @@ export {
UserLock,
NetSuiteExport,
QBOExport,
DocumentCheck,
SageIntacctExport,
XeroExport,
LuggageWithLines,
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 @@ -65,6 +65,7 @@ import CreditCardExclamation from '@assets/images/credit-card-exclamation.svg';
import CreditCardHourglass from '@assets/images/credit-card-hourglass.svg';
import CreditCard from '@assets/images/creditcard.svg';
import Crosshair from '@assets/images/crosshair.svg';
import DocumentCheck from '@assets/images/document-check.svg';
import DocumentMerge from '@assets/images/document-merge.svg';
import Document from '@assets/images/document.svg';
import DotIndicatorUnfilled from '@assets/images/dot-indicator-unfilled.svg';
Expand Down Expand Up @@ -487,6 +488,7 @@ const Expensicons = {
XeroExport,
ArrowCircleClockwise,
LuggageWithLines,
DocumentCheck,
TreasureChestGreenWithSparkle,
UserShield,
};
Expand Down
Loading
Loading