Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
63f573d
Add useDragAndDropSupport hook for native and web environment
samranahm Feb 24, 2026
6b6563c
Refactor IOURequestStepScan to use isMobileWeb and canUseDragAndDrop
samranahm Feb 24, 2026
0490ef1
fix prettier
samranahm Feb 24, 2026
ffcdcf5
remove useDragAndDropSupport hook
samranahm Feb 26, 2026
711c503
remove isMobileWeb from mobileCameraView since we only show this if i…
samranahm Feb 26, 2026
e07e6d5
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 1, 2026
3866b79
Add DesktopWebUploadView and MobileWebCameraView components
samranahm Mar 1, 2026
e3c6c50
Refactor IOURequestStepScan component to use MobileWebCameraView and …
samranahm Mar 1, 2026
79a1b74
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 5, 2026
21cba79
update dir for IOURequestStepScan
samranahm Mar 5, 2026
bd15189
refactor: consolidate IOURequestStepScan components and hooks
samranahm Mar 5, 2026
6921d17
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 5, 2026
3d3966d
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 5, 2026
5f632c1
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 9, 2026
763fe54
move screen wrapper to each platform component
samranahm Mar 9, 2026
eb16fb9
Refactor receipt scanning logic to streamline mobile and web components.
samranahm Mar 9, 2026
73b696e
move useMobileReceiptScan test to seprate file
samranahm Mar 9, 2026
49b5b44
prettier
samranahm Mar 9, 2026
839ffad
Add isDraggingOverWrapper from useDragAndDropState in DesktopWebUploa…
samranahm Mar 10, 2026
c7cdc51
add UseMobileReceiptScanParams type
samranahm Mar 10, 2026
c29310e
Move LocationPermissionFlowSelector to Selectors
samranahm Mar 10, 2026
36d8ac9
reafactor the hooks to pass required params only
samranahm Mar 10, 2026
cd58af9
updated test to remove createMockReceiptScan mock
samranahm Mar 10, 2026
6c8a190
lint
samranahm Mar 10, 2026
8b126c2
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 10, 2026
bef83fd
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 10, 2026
f1e0dc2
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 11, 2026
feabace
Refactor IOURequestStepScan components and hooks for clarity
samranahm Mar 11, 2026
9f39222
Merge branch 'Expensify:main' into 79929/IOURequestStepScan-clean-up-p3
samranahm Mar 12, 2026
c8be9a4
Merge remote-tracking branch 'upstream/main' into 79929/IOURequestSte…
samranahm Mar 12, 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
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import CameraPermission from '@pages/iou/request/step/IOURequestStepScan/CameraPermission';
import NavigationAwareCamera from '@pages/iou/request/step/IOURequestStepScan/components/NavigationAwareCamera/Camera';
import {cropImageToAspectRatio} from '@pages/iou/request/step/IOURequestStepScan/cropImageToAspectRatio';
import type {ImageObject} from '@pages/iou/request/step/IOURequestStepScan/cropImageToAspectRatio';
import NavigationAwareCamera from '@pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/Camera';
import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper';
import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound';
import type {WithFullTransactionOrNotFoundProps} from '@pages/iou/request/step/withFullTransactionOrNotFound';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import {shouldUseTransactionDraft} from '@libs/IOUUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import NavigationAwareCamera from '@pages/iou/request/step/IOURequestStepScan/components/NavigationAwareCamera/WebCamera';
import {cropImageToAspectRatio} from '@pages/iou/request/step/IOURequestStepScan/cropImageToAspectRatio';
import type {ImageObject} from '@pages/iou/request/step/IOURequestStepScan/cropImageToAspectRatio';
import NavigationAwareCamera from '@pages/iou/request/step/IOURequestStepScan/NavigationAwareCamera/WebCamera';
import StepScreenDragAndDropWrapper from '@pages/iou/request/step/StepScreenDragAndDropWrapper';
import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound';
import type {WithFullTransactionOrNotFoundProps} from '@pages/iou/request/step/withFullTransactionOrNotFound';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, {useRef, useState} from 'react';
import {PanResponder, View} from 'react-native';
import AttachmentPicker from '@components/AttachmentPicker';
import Button from '@components/Button';
import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
import {useDragAndDropState} from '@components/DragAndDrop/Provider';
import DropZoneUI from '@components/DropZone/DropZoneUI';
import Icon from '@components/Icon';
import ReceiptAlternativeMethods from '@components/ReceiptAlternativeMethods';
import Text from '@components/Text';
import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import StepScreenDragAndDropWrapper from '@pages/iou/request/step/StepScreenDragAndDropWrapper';
import CONST from '@src/CONST';
import type {FileObject} from '@src/types/utils/Attachment';

type DesktopWebUploadViewProps = {
PDFValidationComponent: React.ReactNode;
shouldAcceptMultipleFiles: boolean;
isReplacingReceipt: boolean;
onLayout: () => void;
validateFiles: (files: FileObject[], items?: DataTransferItem[]) => void;
onBackButtonPress: () => void;
shouldShowWrapper: boolean;
};

function DesktopWebUploadView({
PDFValidationComponent,
shouldAcceptMultipleFiles,
isReplacingReceipt,
onLayout,
validateFiles,
onBackButtonPress,
shouldShowWrapper,
}: DesktopWebUploadViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptStack']);
const lazyIcons = useMemoizedLazyExpensifyIcons(['ReplaceReceipt', 'SmartScan']);
const panResponder = useRef(
PanResponder.create({
onPanResponderTerminationRequest: () => false,
}),
).current;

const {isDraggingOver} = useDragAndDropState();
const [containerHeight, setContainerHeight] = useState(0);
const [desktopUploadViewHeight, setDesktopUploadViewHeight] = useState(0);
const [alternativeMethodsHeight, setAlternativeMethodsHeight] = useState(0);
const chooseFilesPaddingVertical = Number(styles.chooseFilesView(false).paddingVertical);
const shouldHideAlternativeMethods = alternativeMethodsHeight + desktopUploadViewHeight + chooseFilesPaddingVertical * 2 > containerHeight;

const handleDropReceipt = (e: DragEvent) => {
const files = Array.from(e?.dataTransfer?.files ?? []);
if (files.length === 0) {
return;
}
for (const file of files) {
// eslint-disable-next-line no-param-reassign
file.uri = URL.createObjectURL(file);
}

validateFiles(files, Array.from(e.dataTransfer?.items ?? []));
};

return (
<StepScreenDragAndDropWrapper
headerTitle={translate('common.receipt')}
onBackButtonPress={onBackButtonPress}
shouldShowWrapper={shouldShowWrapper}
testID="IOURequestStepScan"
>
{(isDraggingOverWrapper) => (
<View
onLayout={(event) => {
setContainerHeight(event.nativeEvent.layout.height);
onLayout();
}}
style={[styles.flex1, styles.chooseFilesView(false)]}
>
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter]}>
{!(isDraggingOver ?? isDraggingOverWrapper) && (
<View
style={[styles.alignItemsCenter, styles.justifyContentCenter]}
onLayout={(e) => {
setDesktopUploadViewHeight(e.nativeEvent.layout.height);
}}
>
{PDFValidationComponent}
<Icon
src={lazyIllustrations.ReceiptStack}
width={CONST.RECEIPT.ICON_SIZE}
height={CONST.RECEIPT.ICON_SIZE}
/>
<View
style={[styles.uploadFileViewTextContainer, styles.userSelectNone]}
// eslint-disable-next-line react/jsx-props-no-spreading, react-hooks/refs
{...panResponder.panHandlers}
>
<Text style={[styles.textFileUpload, styles.mb2]}>{translate(shouldAcceptMultipleFiles ? 'receipt.uploadMultiple' : 'receipt.upload')}</Text>
<Text style={[styles.textLabelSupporting, styles.textAlignCenter, styles.lineHeightLarge]}>
{translate(shouldAcceptMultipleFiles ? 'receipt.desktopSubtitleMultiple' : 'receipt.desktopSubtitleSingle')}
</Text>
</View>

<AttachmentPicker allowMultiple={shouldAcceptMultipleFiles}>
{({openPicker}) => (
<Button
success
text={translate(shouldAcceptMultipleFiles ? 'common.chooseFiles' : 'common.chooseFile')}
accessibilityLabel={translate(shouldAcceptMultipleFiles ? 'common.chooseFiles' : 'common.chooseFile')}
style={[styles.p5]}
onPress={() => {
openPicker({
onPicked: (data) => validateFiles(data),
});
}}
sentryLabel={CONST.SENTRY_LABEL.IOU_REQUEST_STEP.SCAN_SUBMIT_BUTTON}
/>
)}
</AttachmentPicker>
</View>
)}
</View>
<DragAndDropConsumer onDrop={handleDropReceipt}>
<DropZoneUI
icon={isReplacingReceipt ? lazyIcons.ReplaceReceipt : lazyIcons.SmartScan}
dropStyles={styles.receiptDropOverlay(true)}
dropTitle={isReplacingReceipt ? translate('dropzone.replaceReceipt') : translate(shouldAcceptMultipleFiles ? 'dropzone.scanReceipts' : 'quickAction.scanReceipt')}
dropTextStyles={styles.receiptDropText}
dashedBorderStyles={[styles.dropzoneArea, styles.easeInOpacityTransition, styles.activeDropzoneDashedBorder(theme.receiptDropBorderColorActive, true)]}
/>
</DragAndDropConsumer>
{!shouldHideAlternativeMethods && <ReceiptAlternativeMethods onLayout={(e) => setAlternativeMethodsHeight(e.nativeEvent.layout.height)} />}
</View>
)}
</StepScreenDragAndDropWrapper>
);
}

export default DesktopWebUploadView;
export type {DesktopWebUploadViewProps};
Loading
Loading