Skip to content
Draft
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
93 changes: 58 additions & 35 deletions src/components/ReceiptEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';
import type {StyleProp, ViewStyle} from 'react-native';
import useFilesValidation from '@hooks/useFilesValidation';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {FileObject} from '@src/types/utils/Attachment';
import AttachmentPicker from './AttachmentPicker';
import Icon from './Icon';
import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback';
import ReceiptAlternativeMethods from './ReceiptAlternativeMethods';
Expand Down Expand Up @@ -34,6 +38,9 @@ type ReceiptEmptyStateProps = {

/** Whether it's displayed in Wide RHP */
isDisplayedInWideRHP?: boolean;

/** Callback to be called when a receipt is selected */
onReplaceReceipt?: (files: FileObject[]) => void;
};

// Returns an SVG icon indicating that the user should attach a receipt
Expand All @@ -46,12 +53,14 @@ function ReceiptEmptyState({
style,
onLoad,
isDisplayedInWideRHP = false,
onReplaceReceipt = () => {},
}: ReceiptEmptyStateProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const theme = useTheme();
const isLoadedRef = useRef(false);
const icons = useMemoizedLazyExpensifyIcons(['ReceiptPlaceholderPlus', 'Receipt']);
const {validateFiles, PDFValidationComponent, ErrorModal} = useFilesValidation(onReplaceReceipt);

const Wrapper = onPress ? PressableWithoutFeedback : View;
const containerStyle = [
Expand All @@ -73,42 +82,56 @@ function ReceiptEmptyState({
}, [onLoad]);

return (
<Wrapper
accessibilityRole="imagebutton"
accessibilityLabel={translate('receipt.upload')}
onPress={onPress}
disabled={disabled}
disabledStyle={styles.cursorDefault}
style={containerStyle}
>
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter]}>
<View style={[styles.alignItemsCenter, styles.justifyContentCenter]}>
<View style={styles.pRelative}>
<Icon
fill={theme.border}
src={icons.Receipt}
width={variables.eReceiptEmptyIconWidth}
height={variables.eReceiptEmptyIconWidth}
/>
{!isThumbnail && (
<Icon
src={icons.ReceiptPlaceholderPlus}
width={variables.avatarSizeSmall}
height={variables.avatarSizeSmall}
additionalStyles={styles.moneyRequestAttachReceiptThumbnailIcon}
/>
)}
<AttachmentPicker acceptedFileTypes={[...CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS]}>
{({openPicker}) => (
<Wrapper
accessibilityRole="imagebutton"
accessibilityLabel={translate('receipt.upload')}
onPress={() => {
if (isDisplayedInWideRHP) {
openPicker({
onPicked: validateFiles,
});
return;
}
onPress?.();
}}
disabled={disabled}
disabledStyle={styles.cursorDefault}
style={containerStyle}
>
{PDFValidationComponent}
{ErrorModal}
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter]}>
<View style={[styles.alignItemsCenter, styles.justifyContentCenter]}>
<View style={styles.pRelative}>
<Icon
fill={theme.border}
src={icons.Receipt}
width={variables.eReceiptEmptyIconWidth}
height={variables.eReceiptEmptyIconWidth}
/>
{!isThumbnail && (
<Icon
src={icons.ReceiptPlaceholderPlus}
width={variables.avatarSizeSmall}
height={variables.avatarSizeSmall}
additionalStyles={styles.moneyRequestAttachReceiptThumbnailIcon}
/>
)}
</View>
{!isThumbnail && isDisplayedInWideRHP && (
<>
<Text style={[styles.textHeadline, styles.mt4]}>{translate('receipt.addAReceipt.phrase1')}</Text>
<Text style={[styles.textSupporting, styles.textNormal]}>{translate('receipt.addAReceipt.phrase2')}</Text>
</>
)}
</View>
</View>
{!isThumbnail && isDisplayedInWideRHP && (
<>
<Text style={[styles.textHeadline, styles.mt4]}>{translate('receipt.addAReceipt.phrase1')}</Text>
<Text style={[styles.textSupporting, styles.textNormal]}>{translate('receipt.addAReceipt.phrase2')}</Text>
</>
)}
</View>
</View>
{isDisplayedInWideRHP && !disabled && <ReceiptAlternativeMethods />}
</Wrapper>
{isDisplayedInWideRHP && !disabled && <ReceiptAlternativeMethods />}
</Wrapper>
)}
</AttachmentPicker>
);
}

Expand Down
19 changes: 18 additions & 1 deletion src/components/ReportActionItem/MoneyRequestReceiptView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
} from '@libs/TransactionUtils';
import ViolationsUtils, {filterReceiptViolations} from '@libs/Violations/ViolationsUtils';
import Navigation from '@navigation/Navigation';
import {cleanUpMoneyRequest} from '@userActions/IOU';
import {cleanUpMoneyRequest, replaceReceipt} from '@userActions/IOU';
import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report';
import {clearAllRelatedReportActionErrors} from '@userActions/ReportActions';
import {clearError, getLastModifiedExpense, revert} from '@userActions/Transaction';
Expand All @@ -47,6 +47,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type {TransactionPendingFieldsKey} from '@src/types/onyx/Transaction';
import type {FileObject} from '@src/types/utils/Attachment';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import ReportActionItemImage from './ReportActionItemImage';

Expand Down Expand Up @@ -123,6 +124,7 @@ function MoneyRequestReceiptView({

const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(linkedTransactionID)}`, {canBeMissing: true});
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport?.policyID}`, {canBeMissing: true});
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${moneyRequestReport?.policyID}`, {canBeMissing: true});
const transactionViolations = useTransactionViolations(transaction?.transactionID);

const isDistanceRequest = isDistanceRequestTransactionUtils(transaction);
Expand Down Expand Up @@ -276,6 +278,20 @@ function MoneyRequestReceiptView({
</View>
);

const onReplaceReceipt = (files: FileObject[]) => {
if (files.length === 0) {
return;
}

const file = files.at(0);

if (!file || !linkedTransactionID) {
return;
}
const source = URL.createObjectURL(file as Blob);
replaceReceipt({transactionID: linkedTransactionID, file: file as File, source, transactionPolicy: policy, transactionPolicyCategories: policyCategories});
};

// For empty receipt should be fullHeight
// For the rest, expand to match the content
return (
Expand Down Expand Up @@ -308,6 +324,7 @@ function MoneyRequestReceiptView({
isInMoneyRequestView
style={receiptStyle}
isDisplayedInWideRHP={isDisplayedInWideRHP}
onReplaceReceipt={onReplaceReceipt}
/>
</OfflineWithFeedback>
)}
Expand Down
Loading