-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Extract secondary action hooks and components from MoneyReportHeader #87361
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
Merged
rlinoz
merged 48 commits into
Expensify:main
from
callstack-internal:perf/extract-money-report-header-actions
Apr 15, 2026
Merged
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
edcc094
initial refactor of secondary actions
TMisiukiewicz caee100
Extract MoneyReportHeaderHoldMenu gate with early return
TMisiukiewicz 30f290d
Deduplicate types, extract useTransactionThreadReport, wire up Action…
TMisiukiewicz 790214c
ActionsBar renders sub-components directly, no ReactNode props
TMisiukiewicz 1ad3d28
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 07205df
Collapse ActionsBar into index, adopt upstream modals context
TMisiukiewicz ec34c1f
cleanup MoneyReportHeader
TMisiukiewicz 3e9c1ec
Extract useReportPrimaryAction, remove animation knowledge from getRe…
TMisiukiewicz 2361cab
Remove dead isSelectionModePaymentRef and unused selectedTransactionIDs
TMisiukiewicz e81ea4c
Restore clearSelectedTransactions and hold menu onConfirm callbacks
TMisiukiewicz 2b49674
Pass isReportInSearch to useExpenseActions instead of hardcoding false
TMisiukiewicz 926e9be
update MoneyReportHeaderActions
TMisiukiewicz e74d1a7
updateMoneyReportHeaderActions
TMisiukiewicz f20ccf4
create animation context
TMisiukiewicz 0228a9e
Replace modal handler props with direct context consumption
TMisiukiewicz 96b9eac
prettier
TMisiukiewicz d7841e4
Rename shouldShow booleans to descriptive hasX/isX naming
TMisiukiewicz 20ffd04
Replace deprecated MutableRefObject with RefObject
TMisiukiewicz df29a4b
Fix isOnSearch, backTo route, and missing duplicate expense guards
TMisiukiewicz 7275d98
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz a24ee65
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 4c8dcfc
update MoneyReportHeaderSecondaryActions
TMisiukiewicz 87ab763
update MoneyReportHeaderSelectionDropdown
TMisiukiewicz 58b5f43
update secondary actions
TMisiukiewicz cdabe00
update useExpenseActions
TMisiukiewicz 2faecc9
update hold reject actions
TMisiukiewicz c614e6c
update useLifecycleActions
TMisiukiewicz a6b06bf
cleanup MoneyReportHeader
TMisiukiewicz efaaed1
Merge branch 'main' into perf/extract-money-report-header-actions
TMisiukiewicz c46c02b
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 2d95972
Add delegateEmail to approve/selectPaymentType calls after merge
TMisiukiewicz 2e1736d
fix codex comments
TMisiukiewicz 43b7c57
Merge origin/main into perf/extract-money-report-header-actions
TMisiukiewicz a577b13
Propagate main changes into extracted MoneyReportHeaderSelectionDropdown
TMisiukiewicz f6fcd9c
Fix archived chat check and hold guard in selection dropdown
TMisiukiewicz dee3d1a
fix prettier
TMisiukiewicz cfb56c2
address CR comments
TMisiukiewicz 79ee634
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 2de9329
fix: address PR review feedback for MoneyReportHeader decomposition
TMisiukiewicz 669ee47
CR comments
TMisiukiewicz 7832945
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 2ec79e7
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 5220aed
CR Updates
TMisiukiewicz 17c3333
Merge remote-tracking branch 'origin/main' into perf/extract-money-re…
TMisiukiewicz 87d8edc
remove non-reimbursable payment modal logic deleted on main
TMisiukiewicz d915471
address PR review comments
TMisiukiewicz e9a5713
Add isAccountLocked guard to secondary actions payment flow and JSDoc…
TMisiukiewicz d02a6af
Remove unnecessary checkForNecessaryAction from onPaymentSelect
TMisiukiewicz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
397 changes: 397 additions & 0 deletions
397
src/components/MoneyReportHeaderActions/MoneyReportHeaderSecondaryActions.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
532 changes: 532 additions & 0 deletions
532
src/components/MoneyReportHeaderActions/MoneyReportHeaderSelectionDropdown.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import React, {useEffect, useRef} from 'react'; | ||
| import {View} from 'react-native'; | ||
| import type {ValueOf} from 'type-fest'; | ||
| import type {ButtonWithDropdownMenuRef} from '@components/ButtonWithDropdownMenu/types'; | ||
| import MoneyReportHeaderPrimaryAction from '@components/MoneyReportHeaderPrimaryAction'; | ||
| import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext'; | ||
| import useExportAgainModal from '@hooks/useExportAgainModal'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
| import useResponsiveLayoutOnWideRHP from '@hooks/useResponsiveLayoutOnWideRHP'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import useTransactionThreadReport from '@hooks/useTransactionThreadReport'; | ||
| import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; | ||
| import CONST from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import MoneyReportHeaderSecondaryActions from './MoneyReportHeaderSecondaryActions'; | ||
| import MoneyReportHeaderSelectionDropdown from './MoneyReportHeaderSelectionDropdown'; | ||
| import type {MoneyReportHeaderActionsProps} from './types'; | ||
|
|
||
| /** | ||
| * Narrow the wide primaryAction union to what report-level secondary actions accept. | ||
| * TRANSACTION_PRIMARY_ACTIONS values (e.g. "keepThisOne") are irrelevant here. | ||
| */ | ||
| function narrowPrimaryAction(primaryAction: MoneyReportHeaderActionsProps['primaryAction']): ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS> | '' { | ||
| if ((Object.values(CONST.REPORT.PRIMARY_ACTIONS) as string[]).includes(primaryAction)) { | ||
| return primaryAction as ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS>; | ||
| } | ||
| return ''; | ||
| } | ||
|
|
||
| function MoneyReportHeaderActions({reportID, primaryAction, isReportInSearch, backTo}: MoneyReportHeaderActionsProps) { | ||
| const styles = useThemeStyles(); | ||
| const dropdownMenuRef = useRef<ButtonWithDropdownMenuRef>(null) as React.RefObject<ButtonWithDropdownMenuRef>; | ||
|
|
||
| // We need isSmallScreenWidth for the hold expense modal layout https://github.com/Expensify/App/pull/47990#issuecomment-2362382026 | ||
| // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth | ||
| const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout(); | ||
| const shouldDisplayNarrowVersion = shouldUseNarrowLayout || isMediumScreenWidth; | ||
| const {isWideRHPDisplayedOnWideLayout, isSuperWideRHPDisplayedOnWideLayout} = useResponsiveLayoutOnWideRHP(); | ||
| const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || isWideRHPDisplayedOnWideLayout || isSuperWideRHPDisplayedOnWideLayout; | ||
|
|
||
| const [moneyRequestReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); | ||
| const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(moneyRequestReport?.chatReportID)}`); | ||
|
|
||
| const {transactionThreadReportID} = useTransactionThreadReport(reportID); | ||
|
|
||
| const {triggerExportOrConfirm} = useExportAgainModal(moneyRequestReport?.reportID, moneyRequestReport?.policyID); | ||
|
|
||
| const {selectedTransactionIDs} = useSearchStateContext(); | ||
| const {clearSelectedTransactions} = useSearchActionsContext(); | ||
| const hasSelectedTransactions = !!selectedTransactionIDs.length; | ||
| const isTransactionThread = !!transactionThreadReportID; | ||
|
|
||
| useEffect(() => { | ||
| if (!transactionThreadReportID) { | ||
| return; | ||
| } | ||
|
|
||
| clearSelectedTransactions(true); | ||
| }, [transactionThreadReportID]); // eslint-disable-line react-hooks/exhaustive-deps | ||
|
|
||
| const narrowedPrimaryAction = narrowPrimaryAction(primaryAction); | ||
|
|
||
| if (hasSelectedTransactions && !isTransactionThread) { | ||
|
TMisiukiewicz marked this conversation as resolved.
|
||
| return ( | ||
| <View style={shouldDisplayNarrowMoreButton ? undefined : [styles.dFlex, styles.w100, styles.ph5, styles.pb3]}> | ||
| <MoneyReportHeaderSelectionDropdown | ||
| reportID={reportID} | ||
| primaryAction={narrowedPrimaryAction} | ||
| isReportInSearch={isReportInSearch} | ||
| wrapperStyle={shouldDisplayNarrowMoreButton ? undefined : styles.w100} | ||
| /> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <View style={[styles.flexRow, styles.gap2, ...(!shouldDisplayNarrowMoreButton ? [styles.pb3, styles.ph5, styles.w100, styles.alignItemsCenter, styles.justifyContentCenter] : [])]}> | ||
| {!!primaryAction && ( | ||
| <View style={!shouldDisplayNarrowMoreButton ? [styles.flex1] : undefined}> | ||
| <MoneyReportHeaderPrimaryAction | ||
| reportID={reportID} | ||
| chatReportID={chatReport?.reportID} | ||
| primaryAction={primaryAction} | ||
| onExportModalOpen={() => triggerExportOrConfirm(CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION)} | ||
| /> | ||
| </View> | ||
| )} | ||
| <MoneyReportHeaderSecondaryActions | ||
|
TMisiukiewicz marked this conversation as resolved.
|
||
| reportID={reportID} | ||
| primaryAction={narrowedPrimaryAction} | ||
| isReportInSearch={isReportInSearch} | ||
| backTo={backTo} | ||
| dropdownMenuRef={dropdownMenuRef} | ||
| /> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| export default MoneyReportHeaderActions; | ||
| export type {MoneyReportHeaderActionsProps}; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import type {ValueOf} from 'type-fest'; | ||
| import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; | ||
| import type {PopoverMenuItem} from '@components/PopoverMenu'; | ||
| import type CONST from '@src/CONST'; | ||
| import type {Route} from '@src/ROUTES'; | ||
|
|
||
| type SecondaryActionEntry = DropdownOption<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> & Pick<PopoverMenuItem, 'backButtonText' | 'rightIcon'>; | ||
|
|
||
| type MoneyReportHeaderActionsProps = { | ||
| reportID: string | undefined; | ||
| primaryAction: ValueOf<typeof CONST.REPORT.PRIMARY_ACTIONS> | ValueOf<typeof CONST.REPORT.TRANSACTION_PRIMARY_ACTIONS> | ''; | ||
| isReportInSearch?: boolean; | ||
| backTo?: Route; | ||
| }; | ||
|
|
||
| export type {SecondaryActionEntry, MoneyReportHeaderActionsProps}; | ||
|
TMisiukiewicz marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import React, {createContext, useContext} from 'react'; | ||
| import usePaymentAnimations from '@hooks/usePaymentAnimations'; | ||
|
|
||
| type PaymentAnimationsContextType = ReturnType<typeof usePaymentAnimations>; | ||
|
|
||
| const PaymentAnimationsContext = createContext<PaymentAnimationsContextType | null>(null); | ||
|
|
||
| function usePaymentAnimationsContext(): PaymentAnimationsContextType { | ||
| const context = useContext(PaymentAnimationsContext); | ||
| if (!context) { | ||
| throw new Error('usePaymentAnimationsContext must be used within a PaymentAnimationsProvider'); | ||
| } | ||
| return context; | ||
| } | ||
|
|
||
| function PaymentAnimationsProvider({children}: {children: React.ReactNode}) { | ||
| const animations = usePaymentAnimations(); | ||
| return <PaymentAnimationsContext.Provider value={animations}>{children}</PaymentAnimationsContext.Provider>; | ||
| } | ||
|
|
||
| export {PaymentAnimationsProvider, usePaymentAnimationsContext}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.