Skip to content

Commit

Permalink
Add support for displaying ECard Transaction in ReportPreviews
Browse files Browse the repository at this point in the history
  • Loading branch information
grgia committed Oct 12, 2023
1 parent 7bbee27 commit aa07b9c
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,7 @@ const CONST = {
MAKE_REQUEST_WITH_SIDE_EFFECTS: 'makeRequestWithSideEffects',
},

ERECEIPT_PATH: 'eReceipt/',
ERECEIPT_COLORS: {
YELLOW: 'Yellow',
ICE: 'Ice',
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ export default {
I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher',
INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal',

ERECEIPT: {
route: 'eReceipt/:transactionID',
getRoute: (transactionID: string) => `eReceipt/${transactionID}`,
},

WORKSPACE_NEW: 'workspace/new',
WORKSPACE_NEW_ROOM: 'workspace/new-room',
WORKSPACE_INITIAL: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function extractAttachmentsFromReport(report, reportActions) {

const transaction = TransactionUtils.getTransaction(transactionID);
if (TransactionUtils.hasReceipt(transaction)) {
const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename);
const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction);
attachments.unshift({
source: tryResolveUrlFromApiRoot(image),
isAuthTokenRequired: true,
Expand Down
11 changes: 11 additions & 0 deletions src/components/Attachments/AttachmentView/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL
import * as StyleUtils from '../../../styles/StyleUtils';
import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes';
import useNetwork from '../../../hooks/useNetwork';
import CONST from '../../../CONST';
import EReceipt from '../../EReceipt';

const propTypes = {
...attachmentViewPropTypes,
Expand Down Expand Up @@ -92,6 +94,15 @@ function AttachmentView({
);
}

if((_.isString(source)) && source.startsWith(CONST.ERECEIPT_PATH)) {
const transactionIDFromURL = source.split(CONST.ERECEIPT_PATH)[1];
return (
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter]}>
<EReceipt transactionID={transactionIDFromURL} />
</View>
);
}

// Check both source and file.name since PDFs dragged into the text field
// will appear with a source that is a blob
if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) {
Expand Down
3 changes: 1 addition & 2 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,7 @@ function MoneyRequestConfirmationList(props) {
);
}, [confirm, props.bankAccountRoute, props.iouCurrencyCode, props.iouType, props.isReadOnly, props.policyID, selectedParticipants, splitOrRequestOptions, translate, formError]);

const {image: receiptImage, thumbnail: receiptThumbnail} =
props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(props.receiptPath, props.receiptFilename) : {};
const {image: receiptImage, thumbnail: receiptThumbnail} = transaction ? ReceiptUtils.getThumbnailAndImageURIs(transaction) : {};

return (
<OptionsSelector
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/MoneyRequestPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function MoneyRequestPreview(props) {
!_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT;
const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant;

const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || '')] : [];
const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction)] : [];

const getSettledMessage = () => {
if (isExpensifyCardTransaction || isDistanceRequest) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/MoneyRequestView.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should
let receiptURIs;
let hasErrors = false;
if (hasReceipt) {
receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename);
receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction);
hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction);
}

Expand Down
15 changes: 14 additions & 1 deletion src/components/ReportActionItem/ReportActionItemImage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import styles from '../../styles/styles';
import Image from '../Image';
import ThumbnailImage from '../ThumbnailImage';
Expand All @@ -10,6 +11,7 @@ import {ShowContextMenuContext} from '../ShowContextMenuContext';
import Navigation from '../../libs/Navigation/Navigation';
import PressableWithoutFocus from '../Pressable/PressableWithoutFocus';
import useLocalize from '../../hooks/useLocalize';
import EReceiptThumbnail from '../EReceiptThumbnail';

const propTypes = {
/** thumbnail URI for the image */
Expand Down Expand Up @@ -38,7 +40,11 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal}) {
const imageSource = tryResolveUrlFromApiRoot(image || '');
const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || '');

const receiptImageComponent = thumbnail ? (

const isEReceipt = imageSource.startsWith(CONST.ERECEIPT_PATH);


let receiptImageComponent = thumbnail ? (
<ThumbnailImage
previewSourceURL={thumbnailSource}
style={[styles.w100, styles.h100]}
Expand All @@ -52,6 +58,13 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal}) {
/>
);

if(isEReceipt) {
const transactionIDFromURL = imageSource.split(CONST.ERECEIPT_PATH)[1];
receiptImageComponent = (
<View style={[styles.w100, styles.h100]}><EReceiptThumbnail transactionID={transactionIDFromURL}/></View>
);
}

if (enablePreviewModal) {
return (
<ShowContextMenuContext.Consumer>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/ReportPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function ReportPreview(props) {
const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action);
const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID);
const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action);
const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || ''));
const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction));

const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts;
const previewSubtitle = hasOnlyOneReceiptRequest
Expand Down
3 changes: 2 additions & 1 deletion src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ function getCardDescription(cardID: number) {
return '';
}
const cardDescriptor = card.state === CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED ? Localize.translateLocal('cardTransactions.notActivated') : card.lastFourPAN;
return `${card.bank} - ${cardDescriptor}`;
const result = cardDescriptor ? `${card.bank} - ${cardDescriptor}` : `${card.bank}`;
return result;
}

/**
Expand Down
17 changes: 14 additions & 3 deletions src/libs/ReceiptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import ReceiptHTML from '../../assets/images/receipt-html.png';
import ReceiptDoc from '../../assets/images/receipt-doc.png';
import ReceiptGeneric from '../../assets/images/receipt-generic.png';
import ReceiptSVG from '../../assets/images/receipt-svg.png';
import { Transaction } from '../types/onyx';
import ROUTES from '../ROUTES';

type ThumbnailAndImageURI = {
image: ImageSourcePropType | string;
Expand All @@ -20,12 +22,21 @@ type FileNameAndExtension = {
/**
* Grab the appropriate receipt image and thumbnail URIs based on file type
*
* @param path URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg
* @param filename of uploaded image or last part of remote URI
* @param transaction
*/
function getThumbnailAndImageURIs(path: string, filename: string): ThumbnailAndImageURI {
function getThumbnailAndImageURIs(transaction: Transaction): ThumbnailAndImageURI {

This comment has been minimized.

Copy link
@neil-marcellini

neil-marcellini Oct 19, 2023

Contributor

Hey @grgia, when you updated the params of this function you broke the receipt image for distance eReceipts, because you didn't update all uses of the function. Please be sure to follow the PR checklist item for this next time. I discovered the problem while working on this other issue.

Also this looks like a huge commit. I think keeping your commits smaller makes it easier to catch mistakes like this.

This comment has been minimized.

Copy link
@grgia

grgia Oct 19, 2023

Author Contributor

This occurred in a conflictless merge because both features were completed and in review at the same time. I think with TS this kind of issue will be resolved in the future

// URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg
const path = transaction?.receipt?.source ?? 'https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=0.752xw:1.00xh;0.175xw,0&resize=1200:*';
// filename of uploaded image or last part of remote URI
const filename = transaction?.filename ?? '';
const isReceiptImage = Str.isImage(filename);

const hasEReceipt = transaction?.hasEReceipt;

if(hasEReceipt){
return {thumbnail: null, image: ROUTES.ERECEIPT.getRoute(transaction.transactionID)};
}

// For local files, we won't have a thumbnail yet
if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) {
return {thumbnail: null, image: path};
Expand Down
16 changes: 8 additions & 8 deletions src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,15 @@ function buildOptimisticTransaction(
};
}

/**
* Check if the transaction has an Ereceipt
*/
function hasEreceipt(transaction: Transaction | undefined | null): boolean {
return !!transaction?.hasEReceipt;
}

function hasReceipt(transaction: Transaction | undefined | null): boolean {
return !!transaction?.receipt?.state;
return !!transaction?.receipt?.state || hasEreceipt(transaction);
}

function areRequiredFieldsEmpty(transaction: Transaction): boolean {
Expand Down Expand Up @@ -332,13 +339,6 @@ function hasRoute(transaction: Transaction): boolean {
return !!transaction?.routes?.route0?.geometry?.coordinates;
}

/**
* Check if the transaction has an Ereceipt
*/
function hasEreceipt(transaction: Transaction): boolean {
return !!transaction?.hasEReceipt;
}

/**
* Get the transactions related to a report preview with receipts
* Get the details linked to the IOU reportAction
Expand Down
149 changes: 149 additions & 0 deletions src/stories/ReportActionItemImages.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React from 'react';
import ReportActionItemImages from '../components/ReportActionItem/ReportActionItemImages';
import PressableWithoutFeedback from '../components/Pressable/PressableWithoutFeedback';

/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
const story = {
title: 'Components/ReportActionItemImages',
component: ReportActionItemImages,
};

function Template(args) {
return (
<PressableWithoutFeedback
accessibilityLabel="ReportActionItemImages Story"
style={{flex: 1}}
>
{({hovered}) => (
<ReportActionItemImages
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
isHovered={hovered}
/>
)}
</PressableWithoutFeedback>
);
}

// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Default = Template.bind({});
Default.args = {
images: [{image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', thumbnail: ''}],
size: 1,
total: 1,
};

const DisplayJSX = Template.bind({});
DisplayJSX.args = {
images: [{image: 'eReceipt/FAKE_3', thumbnail: ''}],
size: 1,
total: 1,
};

const TwoImages = Template.bind({});
TwoImages.args = {
images: [
{
image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg',
thumbnail: '',
},
{
image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7',
thumbnail: '',
},
],
size: 2,
total: 2,
};

const ThreeImages = Template.bind({});
ThreeImages.args = {
images: [
{
image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg',
thumbnail: '',
},
{
image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7',
thumbnail: '',
},
{
image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg',
thumbnail: '',
},
],
size: 3,
total: 3,
};

const FourImages = Template.bind({});
FourImages.args = {
images: [
{
image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg',
thumbnail: '',
},
{
image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7',
thumbnail: '',
},
{
image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg',
thumbnail: '',
},
{
image: 'https://www.alleycat.org/wp-content/uploads/2019/03/FELV-cat.jpg',
thumbnail: '',
},
],
size: 4,
total: 4,
};

const ThreePlusTwoImages = Template.bind({});
ThreePlusTwoImages.args = {
images: [
{
image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg',
thumbnail: '',
},
{
image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7',
thumbnail: '',
},
{
image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg',
thumbnail: '',
},
],
size: 3,
total: 5,
};

const ThreePlusTenImages = Template.bind({});
ThreePlusTenImages.args = {
images: [
{
image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg',
thumbnail: '',
},
{
image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7',
thumbnail: '',
},
{
image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg',
thumbnail: '',
},
],
size: 3,
total: 13,
};

export default story;
export {Default, TwoImages, ThreeImages, FourImages, ThreePlusTwoImages, ThreePlusTenImages, DisplayJSX};
1 change: 0 additions & 1 deletion src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -3303,7 +3303,6 @@ const styles = (theme) => ({
},

eReceiptContainer: {
flex: 1,
width: 335,
minHeight: 540,
borderRadius: 20,
Expand Down

0 comments on commit aa07b9c

Please sign in to comment.