-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Decompose scan pr3 variants router #89293
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
cristipaval
merged 43 commits into
Expensify:main
from
callstack-internal:decompose-scan-pr3-variants-router
May 27, 2026
Merged
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
80d2049
feat: add shared scan utilities
rinej 44124b2
feat: add separate variants
rinej d18c133
refactor: decompose IOURequestStepScan into variant router
rinej 3803d61
fix: address CI failures in scan decomposition PR
rinej d6d415b
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej cff9c8b
chore: lint fixes
rinej 0005ef1
fix: adjust ScanGlobalCreate report logic
rinej 6bc2d67
fix: scanGlobalCreate cleanup
rinej 06af066
add support for camera permission issue
rinej aa8ed07
chore: consts refactor
rinej 7c817b2
fix: restore nav state loss handling, wire multi-scan cleanup, add lo…
rinej 4e7da49
fix: restore multi-scan, thumbnail pregen and draft cleanup
rinej 02f2987
refactor: split ScanRouter, dedupe scan variants, complete Camera pro…
rinej f63f86a
fix: spellcheck fix
rinej 89926ee
fix: write optimistic receipt fields before replaceReceipt in edit
rinej 6f05e33
fix lint
rinej 60c2147
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej 1fdd062
fix: update scan start span, close span on empty size
rinej c9bd8a1
refactor: simplify onPicked logic
rinej f9f869c
refactor: reuse camera telemetry hook and subcomponents in Camera.native
rinej 06589b8
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej 220377f
fix: restore multi-scan receipt preview ribbon
rinej d311279
fix: add multiscan parity
rinej c9a42f9
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej 6a7122b
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej 4a1c356
fix: removed onCameraInitialized from the destructured props
rinej 108aaa6
fix: adjust startScanProcessSpan in navigateGlobalCreate
rinej 7495f6d
refactor: submitWithGpsCheck to avoid duplication
rinej 2795ed1
refactor: rename onMultiScanSubmit
rinej e51ae36
refactor: remove isDraggingOverWrapper
rinej 8798027
fix: multiscan back flow upload fix
rinej 592c0c2
fix: add skipConfirmation for CREATE iouType in scan router
rinej 6989719
Revert "fix: add skipConfirmation for CREATE iouType in scan router"
rinej 8efe2f4
fix: cleanup for leftovers for scan drafts
rinej c66a536
refactor: use draftTransactionIDsToCleanUp cleanup
rinej ac7d1d6
Merge branch 'main' into decompose-scan-pr3-variants-router
rinej cb84388
Merge branch 'main' into decompose-scan-pr3-variants-router
rinej d290b7c
ts fixes after main changes
rinej e4925fc
fix: add local replace logic for drafts
rinej fd45aad
fix: use action to check saved vs draft receipt replace
rinej d915ab0
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej a2da36a
Merge remote-tracking branch 'upstream/main' into decompose-scan-pr3-…
rinej 7ca2575
Merge branch 'main' into decompose-scan-pr3-variants-router
rinej 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
149 changes: 149 additions & 0 deletions
149
src/pages/iou/request/step/IOURequestStepScan/ScanRouter.tsx
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,149 @@ | ||
| import React from 'react'; | ||
| import type {OnyxEntry} from 'react-native-onyx'; | ||
| import useOnyx from '@hooks/useOnyx'; | ||
| import useReportIsArchived from '@hooks/useReportIsArchived'; | ||
| import {isPolicyExpenseChat} from '@libs/ReportUtils'; | ||
| import CONST from '@src/CONST'; | ||
| import type {IOUAction, IOUType} from '@src/CONST'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
| import type {Route} from '@src/ROUTES'; | ||
| import type {Policy, Report} from '@src/types/onyx'; | ||
| import type Transaction from '@src/types/onyx/Transaction'; | ||
| import MultiScanGate from './components/MultiScanGate'; | ||
| import ScanEditReceipt from './components/ScanEditReceipt'; | ||
| import ScanFromReport from './components/ScanFromReport'; | ||
| import ScanGlobalCreate from './components/ScanGlobalCreate'; | ||
| import ScanSkipConfirmation from './components/ScanSkipConfirmation'; | ||
|
|
||
| type ScanRouterProps = { | ||
| report: OnyxEntry<Report>; | ||
| action: IOUAction; | ||
| iouType: IOUType; | ||
| reportID: string; | ||
| transactionID: string; | ||
| transaction: OnyxEntry<Transaction>; | ||
| backTo: Route | undefined; | ||
| backToReport: string | undefined; | ||
| }; | ||
|
|
||
| type NonGlobalCreateProps = { | ||
| report: OnyxEntry<Report>; | ||
| iouType: IOUType; | ||
| reportID: string; | ||
| transactionID: string; | ||
| transaction: OnyxEntry<Transaction>; | ||
| backToReport: string | undefined; | ||
| }; | ||
|
|
||
| type NewReceiptProps = NonGlobalCreateProps; | ||
|
|
||
| const policyRequiresTagOrCategorySelector = (policy: OnyxEntry<Policy>) => !!policy?.requiresCategory || !!policy?.requiresTag; | ||
|
|
||
| /** | ||
| * Owns the policy + skip-confirmation subscriptions so the edit and global-create branches don't pay for them. | ||
| * Decides between SkipConfirmation (quick action) and FromReport (report (+) entry). | ||
| */ | ||
| function ScanNonGlobalCreate({report, iouType, reportID, transactionID, transaction, backToReport}: NonGlobalCreateProps) { | ||
| const [policyRequiresTagOrCategory] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`, {selector: policyRequiresTagOrCategorySelector}); | ||
| const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); | ||
| const shouldSkipConfirmation = !!skipConfirmation && !!report?.reportID && !(isPolicyExpenseChat(report) && policyRequiresTagOrCategory); | ||
|
|
||
| if (shouldSkipConfirmation) { | ||
| return ( | ||
| <ScanSkipConfirmation | ||
| report={report} | ||
| iouType={iouType} | ||
| reportID={reportID} | ||
| transactionID={transactionID} | ||
| transaction={transaction} | ||
| backToReport={backToReport} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <ScanFromReport | ||
| report={report} | ||
| iouType={iouType} | ||
| reportID={reportID} | ||
| transactionID={transactionID} | ||
| transaction={transaction} | ||
| backToReport={backToReport} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| ScanNonGlobalCreate.displayName = 'ScanNonGlobalCreate'; | ||
|
|
||
| /** | ||
| * Splits new-receipt flows: global-create (FAB) vs. report-scoped (FromReport / SkipConfirm). | ||
| * The archived-report check lives here so neither global-create nor non-global-create variants need to subscribe. | ||
| */ | ||
| function ScanNewReceipt({report, iouType, reportID, transactionID, transaction, backToReport}: NewReceiptProps) { | ||
| const isArchived = useReportIsArchived(report?.reportID); | ||
| const isFromGlobalCreate = !!transaction?.isFromGlobalCreate; | ||
|
|
||
| if (!isFromGlobalCreate && !isArchived && iouType !== CONST.IOU.TYPE.CREATE) { | ||
| return ( | ||
| <ScanNonGlobalCreate | ||
| report={report} | ||
| iouType={iouType} | ||
| reportID={reportID} | ||
| transactionID={transactionID} | ||
| transaction={transaction} | ||
| backToReport={backToReport} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <ScanGlobalCreate | ||
| iouType={iouType} | ||
| reportID={reportID} | ||
| transactionID={transactionID} | ||
| transaction={transaction} | ||
| backToReport={backToReport} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| ScanNewReceipt.displayName = 'ScanNewReceipt'; | ||
|
|
||
| /** | ||
| * ScanRouter — selects the appropriate scan variant based on route params and transaction state. | ||
| * | ||
| * Edit branch is a fast-path that subscribes to nothing extra. Non-edit branches go through MultiScanGate | ||
| * and the layered ScanNewReceipt/ScanNonGlobalCreate components, which scope their subscriptions to the | ||
| * narrowest variant that needs them. | ||
| */ | ||
| function ScanRouter({report, action, iouType, reportID, transactionID, transaction, backTo, backToReport}: ScanRouterProps) { | ||
| const isEditing = action === CONST.IOU.ACTION.EDIT; | ||
|
|
||
| if (backTo || isEditing) { | ||
| return ( | ||
| <ScanEditReceipt | ||
| report={report} | ||
| transactionID={transactionID} | ||
| backTo={backTo} | ||
| isEditing={isEditing} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <MultiScanGate> | ||
| <ScanNewReceipt | ||
| report={report} | ||
| iouType={iouType} | ||
| reportID={reportID} | ||
| transactionID={transactionID} | ||
| transaction={transaction} | ||
| backToReport={backToReport} | ||
| /> | ||
| </MultiScanGate> | ||
| ); | ||
| } | ||
|
|
||
| ScanRouter.displayName = 'ScanRouter'; | ||
|
|
||
| export default ScanRouter; | ||
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not a big deal, but we subscribe to policy, archived state, and skipConfirmation for edit case too. I'm thinking of this plan:
ScanNewReceiptcomponent in this file (ScanRouter) that handles rendering the non-edit caseScanNewReceipt, we can separate again into 2 components, 1 for the global create case, and 1 for the non-global create case (ScanNonGlobalCreate). The subscription for policy and skipConfirmation will be done in ScanNonGlobalCreate.requiresCategoryandrequiresTagonly.the code example