From 894c310bcd560b6135f4d10246bfbd472d225128 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 11 Apr 2024 04:14:11 +0200 Subject: [PATCH 001/219] Preserve transactions amount in create IOU --- src/components/transactionPropTypes.js | 3 +++ src/libs/CurrencyUtils.ts | 20 +++++++++++++++--- src/libs/actions/IOU.ts | 6 +++--- src/pages/iou/request/IOURequestStartPage.js | 9 +++++++- .../iou/request/step/IOURequestStepAmount.js | 9 +++++++- .../iou/steps/MoneyRequestAmountForm.tsx | 21 +++++++++---------- src/types/onyx/Transaction.ts | 3 +++ tests/unit/CurrencyUtilsTest.ts | 20 +++++++++++++++--- 8 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index 7eb1b776358c..46d246948460 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -93,4 +93,7 @@ export default PropTypes.shape({ /** Server side errors keyed by microtime */ errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), + + /** Whether the original input should be shown */ + shouldShowOriginalAmount: PropTypes.bool, }); diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 95970d2a9582..4530dc105e03 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -87,9 +87,22 @@ function convertToBackendAmount(amountAsFloat: number): number { * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmount(amountAsInt: number): number { +function convertToFrontendAmountAsInteger(amountAsInt: number): number { return Math.trunc(amountAsInt) / 100.0; } + +/** + * Takes an amount in "cents" as an integer and converts it to a string amount used in the frontend. + * + * @note we do not support any currencies with more than two decimal places. + */ +function convertToFrontendAmountAsString(amountAsInt: number | null | undefined): string { + if (amountAsInt === null || amountAsInt === undefined) { + return ''; + } + return convertToFrontendAmountAsInteger(amountAsInt).toFixed(2); +} + /** * Given an amount in the "cents", convert it to a string for display in the UI. * The backend always handle things in "cents" (subunit equal to 1/100) @@ -98,7 +111,7 @@ function convertToFrontendAmount(amountAsInt: number): number { * @param currency - IOU currency */ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { - const convertedAmount = convertToFrontendAmount(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, @@ -139,7 +152,8 @@ export { getCurrencySymbol, isCurrencySymbolLTR, convertToBackendAmount, - convertToFrontendAmount, + convertToFrontendAmountAsInteger, + convertToFrontendAmountAsString, convertToDisplayString, convertAmountToDisplayString, isValidCurrencyCode, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index dab0eed1653a..8e3ead229a61 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -357,12 +357,12 @@ function startMoneyRequest(iouType: ValueOf, reportID: st } // eslint-disable-next-line @typescript-eslint/naming-convention -function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false) { +function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false, shouldShowOriginalAmount = false) { if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null, shouldShowOriginalAmount}); return; } - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, shouldShowOriginalAmount}); } // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index e9057fef9226..2a96e5b1e1eb 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -166,7 +166,14 @@ function IOURequestStartPage({ onTabSelected={resetIOUTypeIfChanged} tabBar={TabSelector} > - {() => } + + {() => ( + + )} + {() => } {shouldDisplayDistanceRequest && {() => }} diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index b392ee310205..0dd4d0247629 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,6 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import lodashIsEmpty from 'lodash/isEmpty'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -39,6 +40,9 @@ const propTypes = { /** The draft transaction object being modified in Onyx */ draftTransaction: transactionPropTypes, + + /** Whether the user input should be kept or not */ + shouldKeepUserInput: PropTypes.bool, }; const defaultProps = { @@ -46,6 +50,7 @@ const defaultProps = { transaction: {}, splitDraftTransaction: {}, draftTransaction: {}, + shouldKeepUserInput: false, }; function IOURequestStepAmount({ @@ -56,6 +61,7 @@ function IOURequestStepAmount({ transaction, splitDraftTransaction, draftTransaction, + shouldKeepUserInput, }) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -125,7 +131,7 @@ function IOURequestStepAmount({ isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); - IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true); + IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true, shouldKeepUserInput); if (backTo) { Navigation.goBack(backTo); @@ -183,6 +189,7 @@ function IOURequestStepAmount({ currency={currency} amount={Math.abs(transactionAmount)} ref={(e) => (textInput.current = e)} + shouldKeepUserInput={transaction.shouldShowOriginalAmount} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={saveAmountAndCurrency} selectedTab={iouRequestType} diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index c8b418f301c9..77a953dada90 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -49,6 +49,9 @@ type MoneyRequestAmountFormProps = { /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ selectedTab?: SelectedTabRequest; + + /** Whether the user input should be kept or not */ + shouldKeepUserInput?: boolean; }; type Selection = { @@ -66,7 +69,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => - isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount)); + isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmountAsInteger(Math.abs(taxAmount)); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; @@ -82,6 +85,7 @@ function MoneyRequestAmountForm( onCurrencyButtonPress, onSubmitButtonPress, selectedTab = CONST.TAB_REQUEST.MANUAL, + shouldKeepUserInput = false, }: MoneyRequestAmountFormProps, forwardedRef: ForwardedRef, ) { @@ -93,7 +97,7 @@ function MoneyRequestAmountForm( const isTaxAmountForm = Navigation.getActiveRoute().includes('taxAmount'); const decimals = CurrencyUtils.getCurrencyDecimals(currency); - const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmountAsString(amount) : ''; const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); const [formError, setFormError] = useState(''); @@ -135,7 +139,7 @@ function MoneyRequestAmountForm( }; const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount) : ''; setCurrentAmount(frontendAmount); setSelection({ start: frontendAmount.length, @@ -144,13 +148,13 @@ function MoneyRequestAmountForm( }, []); useEffect(() => { - if (!currency || typeof amount !== 'number') { + if (!currency || typeof amount !== 'number' || shouldKeepUserInput) { return; } initializeAmount(amount); // we want to re-initialize the state only when the selected tab or amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTab, amount]); + }, [selectedTab, amount, shouldKeepUserInput]); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -264,13 +268,8 @@ function MoneyRequestAmountForm( return; } - // Update display amount string post-edit to ensure consistency with backend amount - // Reference: https://github.com/Expensify/App/issues/30505 - const backendAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)); - initializeAmount(backendAmount); - onSubmitButtonPress({amount: currentAmount, currency}); - }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount]); + }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 281b6b4228ce..6b4bd1fc0d19 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -215,6 +215,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates transaction loading */ isLoading?: boolean; + + /** Whether the user input should be kept */ + shouldShowOriginalAmount?: boolean; }, keyof Comment >; diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index a1e4b03fa715..089cdf8426a8 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -105,15 +105,29 @@ describe('CurrencyUtils', () => { }); }); - describe('convertToFrontendAmount', () => { + describe('convertToFrontendAmountAsInteger', () => { test.each([ [2500, 25], [2550, 25.5], [25, 0.25], [2500, 25], [2500.5, 25], // The backend should never send a decimal .5 value - ])('Correctly converts %s to amount in units handled in frontend', (amount, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmount(amount)).toBe(expectedResult); + ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount)).toBe(expectedResult); + }); + }); + + describe('convertToFrontendAmountAsString', () => { + test.each([ + [2500, '25.00'], + [2550, '25.50'], + [25, '0.25'], + [2500.5, '25.00'], + [null, ''], + [undefined, ''], + [0, '0.00'], + ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsString(input)).toBe(expectedResult); }); }); From 5f0df225b999649f6a7c7cccefe138e66ccc3071 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Mon, 15 Apr 2024 20:42:38 +0200 Subject: [PATCH 002/219] fixing types --- src/pages/iou/request/step/IOURequestStepAmount.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index a747084ac2b3..ca662d1a2a66 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -33,15 +33,15 @@ type IOURequestStepAmountOnyxProps = { /** The draft transaction object being modified in Onyx */ draftTransaction: OnyxEntry; - - /** Whether the user input should be kept or not */ - shouldKeepUserInput: PropTypes.bool; }; type IOURequestStepAmountProps = IOURequestStepAmountOnyxProps & WithWritableReportOrNotFoundProps & { /** The transaction object being modified in Onyx */ transaction: OnyxEntry; + + /** Whether the user input should be kept or not */ + shouldKeepUserInput?: boolean; }; function IOURequestStepAmount({ @@ -169,7 +169,7 @@ function IOURequestStepAmount({ currency={currency} amount={Math.abs(transactionAmount)} ref={(e) => (textInput.current = e)} - shouldKeepUserInput={transaction.shouldShowOriginalAmount} + shouldKeepUserInput={transaction?.shouldShowOriginalAmount} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={saveAmountAndCurrency} selectedTab={iouRequestType} From df364c5fe9c13b3e8c39c51b2f85d6f798ad54b3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 18 Apr 2024 17:50:35 +0530 Subject: [PATCH 003/219] fix: IOU Scan - In dark mode, the damaged PDF - file is barely visible. Signed-off-by: Krishna Gupta --- src/components/PDFThumbnail/index.tsx | 8 ++++++-- src/components/PDFThumbnail/types.ts | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index a5b911deb6ff..8663357fb508 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -4,6 +4,8 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {Document, pdfjs, Thumbnail} from 'react-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import type PDFThumbnailProps from './types'; @@ -12,8 +14,9 @@ if (!pdfjs.GlobalWorkerOptions.workerSrc) { pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(new Blob([pdfWorkerSource], {type: 'text/javascript'})); } -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, errorLabelStyles}: PDFThumbnailProps) { const styles = useThemeStyles(); + const {translate} = useLocalize(); const thumbnail = useMemo( () => ( @@ -26,13 +29,14 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena }} externalLinkTarget="_blank" onPassword={onPassword} + error={{translate('attachmentView.failedToLoadPDF')}} > ), - [isAuthTokenRequired, previewSourceURL, onPassword], + [isAuthTokenRequired, previewSourceURL, onPassword, errorLabelStyles, translate, styles.textLabel], ); return ( diff --git a/src/components/PDFThumbnail/types.ts b/src/components/PDFThumbnail/types.ts index 11253e462aca..5891ac133da1 100644 --- a/src/components/PDFThumbnail/types.ts +++ b/src/components/PDFThumbnail/types.ts @@ -1,4 +1,4 @@ -import type {StyleProp, ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; type PDFThumbnailProps = { /** Source URL for the preview PDF */ @@ -15,6 +15,9 @@ type PDFThumbnailProps = { /** Callback to call if PDF is password protected */ onPassword?: () => void; + + /** Styles for the error label */ + errorLabelStyles?: StyleProp; }; export default PDFThumbnailProps; From 1e046d8530b62a4c0defc37e1b20e411d6ebb91c Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 22 Apr 2024 18:35:09 +0530 Subject: [PATCH 004/219] fix: show corrupted pdf alert for native devices. Signed-off-by: Krishna Gupta --- src/components/PDFThumbnail/index.native.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index 0232dba99f05..3da6d5b4fa6e 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -1,19 +1,24 @@ -import React from 'react'; +import React, {useState} from 'react'; import {View} from 'react-native'; import Pdf from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import type PDFThumbnailProps from './types'; -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, errorLabelStyles}: PDFThumbnailProps) { const styles = useThemeStyles(); const sizeStyles = [styles.w100, styles.h100]; + const {translate} = useLocalize(); + + const [isCorrupted, setIsCorrupted] = useState(false); return ( - {enabled && ( + {enabled && !isCorrupted && ( { + if ('message' in error && typeof error.message === 'string' && error.message.match(/corrupted/i)) { + setIsCorrupted(true); + } + if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) { return; } + if (!onPassword) { return; } @@ -32,6 +42,7 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena }} /> )} + {isCorrupted && {translate('attachmentView.failedToLoadPDF')}} ); From 2288b186406b8eac3351a9aca5f4972584efe6d6 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 22 Apr 2024 18:36:34 +0530 Subject: [PATCH 005/219] spacing improvement. Signed-off-by: Krishna Gupta --- src/components/PDFThumbnail/index.native.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index 3da6d5b4fa6e..c008d6823a00 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -12,7 +12,6 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena const styles = useThemeStyles(); const sizeStyles = [styles.w100, styles.h100]; const {translate} = useLocalize(); - const [isCorrupted, setIsCorrupted] = useState(false); return ( @@ -30,11 +29,9 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena if ('message' in error && typeof error.message === 'string' && error.message.match(/corrupted/i)) { setIsCorrupted(true); } - if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) { return; } - if (!onPassword) { return; } From 0bef6caa6f162e7f30ad033f222f3d33a68982ee Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Sun, 28 Apr 2024 05:15:23 +0530 Subject: [PATCH 006/219] fix: Workspace switcher search bar is in the wrong place. Signed-off-by: Krishna Gupta --- .../WorkspacesSectionHeader.tsx | 2 +- src/pages/WorkspaceSwitcherPage/index.tsx | 54 ++++++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx b/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx index 85e13ba4c0a5..044ded08e35d 100644 --- a/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx +++ b/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx @@ -19,7 +19,7 @@ function WorkspacesSectionHeader() { const {translate} = useLocalize(); return ( - + ; function WorkspaceSwitcherPage() { + const styles = useThemeStyles(); + const theme = useTheme(); const {isOffline} = useNetwork(); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const {translate} = useLocalize(); @@ -138,33 +144,26 @@ function WorkspaceSwitcherPage() { const sections = useMemo(() => { const options: Array> = [ { - title: translate('workspace.switcher.everythingSection'), + data: filteredAndSortedUserWorkspaces, shouldShow: true, - indexOffset: 0, - data: [ - { - text: CONST.WORKSPACE_SWITCHER.NAME, - policyID: '', - icons: [{source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}], - brickRoadIndicator: getIndicatorTypeForPolicy(undefined), - isSelected: activeWorkspaceID === undefined, - keyForList: CONST.WORKSPACE_SWITCHER.NAME, - }, - ], + indexOffset: 1, }, ]; - options.push({ - CustomSectionHeader: WorkspacesSectionHeader, - data: filteredAndSortedUserWorkspaces, - shouldShow: true, - indexOffset: 1, - }); return options; - }, [activeWorkspaceID, filteredAndSortedUserWorkspaces, getIndicatorTypeForPolicy, translate]); + }, [filteredAndSortedUserWorkspaces]); const headerMessage = filteredAndSortedUserWorkspaces.length === 0 && usersWorkspaces.length ? translate('common.noResultsFound') : ''; const shouldShowCreateWorkspace = usersWorkspaces.length === 0; + const defaultPolicy = { + text: CONST.WORKSPACE_SWITCHER.NAME, + policyID: '', + icons: [{source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}], + brickRoadIndicator: getIndicatorTypeForPolicy(undefined), + keyForList: CONST.WORKSPACE_SWITCHER.NAME, + isSelected: activeWorkspaceID === undefined, + }; + return ( + + + {translate('workspace.switcher.everythingSection')} + + + + selectPolicy(defaultPolicy)} + /> + + ListItem={UserListItem} sections={sections} From 7c396dcca34f72f8409e0c5e37c0a77436a18f8f Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 29 Apr 2024 03:49:57 +0530 Subject: [PATCH 007/219] minor fix. Signed-off-by: Krishna Gupta --- src/components/SelectionList/UserListItem.tsx | 2 ++ src/pages/WorkspaceSwitcherPage/index.tsx | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx index 68349293e134..daf24d0bb3ca 100644 --- a/src/components/SelectionList/UserListItem.tsx +++ b/src/components/SelectionList/UserListItem.tsx @@ -29,6 +29,7 @@ function UserListItem({ rightHandSideComponent, onFocus, shouldSyncFocus, + pressableStyle, }: UserListItemProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -61,6 +62,7 @@ function UserListItem({ rightHandSideComponent={rightHandSideComponent} errors={item.errors} pendingAction={item.pendingAction} + pressableStyle={pressableStyle} FooterComponent={ item.invitedSecondaryLogin ? ( diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index f01e6a997258..1be8b38a56d8 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -181,14 +181,13 @@ function WorkspaceSwitcherPage() { {translate('workspace.switcher.everythingSection')} - - selectPolicy(defaultPolicy)} - /> - + selectPolicy(defaultPolicy)} + pressableStyle={styles.flexRow} + /> ListItem={UserListItem} From 7d069ac3ba095abee5d6a08e7b9230ef943aa63b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 5 May 2024 18:36:15 +0530 Subject: [PATCH 008/219] add new chat type --- src/CONST.ts | 1 + src/components/ReportActionItem/TripDetailsView.tsx | 0 2 files changed, 1 insertion(+) create mode 100644 src/components/ReportActionItem/TripDetailsView.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 566d5179f86a..48e33f0b0f5d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -50,6 +50,7 @@ const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; const chatTypes = { POLICY_ANNOUNCE: 'policyAnnounce', POLICY_ADMINS: 'policyAdmins', + POLICY_TRIP_ROOM: 'policyTripRoom', GROUP: 'group', DOMAIN_ALL: 'domainAll', POLICY_ROOM: 'policyRoom', diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx new file mode 100644 index 000000000000..e69de29bb2d1 From 48d51c4984812a3271253eafa0c8798f3a05bf9c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 5 May 2024 18:38:58 +0530 Subject: [PATCH 009/219] add isTripRoom --- src/libs/ReportUtils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8dd04d168717..d29f66a9ffde 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -879,6 +879,13 @@ function isInvoiceRoom(report: OnyxEntry): boolean { return getChatType(report) === CONST.REPORT.CHAT_TYPE.INVOICE; } +/** + * Checks if a report is a completed task report. + */ +function isTripRoom(report: OnyxEntry): boolean { + return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_TRIP_ROOM; +} + function isCurrentUserInvoiceReceiver(report: OnyxEntry): boolean { if (report?.invoiceReceiver?.type === CONST.REPORT.INVOICE_RECEIVER_TYPE.INDIVIDUAL) { return currentUserAccountID === report.invoiceReceiver.accountID; @@ -6619,6 +6626,7 @@ export { isCanceledTaskReport, isChatReport, isChatRoom, + isTripRoom, isChatThread, isChildReport, isClosedExpenseReportWithNoExpenses, From 37b5a9d17e9d059b0058f114ccec7c15908502ff Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 5 May 2024 18:40:02 +0530 Subject: [PATCH 010/219] add isTripRoom --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d29f66a9ffde..eeba76346478 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -883,7 +883,7 @@ function isInvoiceRoom(report: OnyxEntry): boolean { * Checks if a report is a completed task report. */ function isTripRoom(report: OnyxEntry): boolean { - return getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_TRIP_ROOM; + return isChatReport(report) && getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_TRIP_ROOM; } function isCurrentUserInvoiceReceiver(report: OnyxEntry): boolean { From dc2aba95e4e8e159fcdf0da401cb081cba8c6d56 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 5 May 2024 20:17:34 +0530 Subject: [PATCH 011/219] show trip details in report action item --- src/components/ReportActionItem/TripDetailsView.tsx | 7 +++++++ src/pages/home/report/ReportActionItem.tsx | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index e69de29bb2d1..9533436082e4 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -0,0 +1,7 @@ +import {View} from 'react-native'; + +function TripDetailsView() { + return ; +} + +export default TripDetailsView; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 5697807ca825..8c7170fcc616 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -28,6 +28,7 @@ import ReportPreview from '@components/ReportActionItem/ReportPreview'; import TaskAction from '@components/ReportActionItem/TaskAction'; import TaskPreview from '@components/ReportActionItem/TaskPreview'; import TaskView from '@components/ReportActionItem/TaskView'; +import TripDetailsView from '@components/ReportActionItem/TripDetailsView'; import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import SpacerView from '@components/SpacerView'; import Text from '@components/Text'; @@ -834,6 +835,15 @@ function ReportActionItem({ ); } + + if (ReportUtils.isTripRoom(report)) { + return ( + + + + ); + } + if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) { return ( From 942c06848ca5f39be2c0be55ba4c20f7f2cc7666 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 5 May 2024 20:28:13 +0530 Subject: [PATCH 012/219] pass chatreport id to trip details --- .../ReportActionItem/TripDetailsView.tsx | 19 +++++++++++++++++-- src/pages/home/report/ReportActionItem.tsx | 5 ++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index 9533436082e4..9b560a0a037c 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -1,7 +1,22 @@ import {View} from 'react-native'; +import Text from '@components/Text'; -function TripDetailsView() { - return ; +type TripDetailsViewProps = { + /** The active IOUReport, used for Onyx subscription */ + iouReportID?: string; + + /** Whether we should display the horizontal rule below the component */ + shouldShowHorizontalRule: boolean; +}; + +function TripDetailsView({iouReportID, shouldShowHorizontalRule}: TripDetailsViewProps) { + return ( + + + Hello world ${iouReportID} ${shouldShowHorizontalRule} + + + ); } export default TripDetailsView; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 8c7170fcc616..e4f3b06fd182 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -839,7 +839,10 @@ function ReportActionItem({ if (ReportUtils.isTripRoom(report)) { return ( - + ); } From 770d39e92f4b2677851f9a4f99556d94088094e5 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Sun, 5 May 2024 20:49:20 +0530 Subject: [PATCH 013/219] add styles and copy trip details view --- src/CONST.ts | 8 + .../ReportActionItem/TripDetailsView.tsx | 211 +++++++++++++++++- src/styles/index.ts | 30 +++ 3 files changed, 245 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 48e33f0b0f5d..07102e422981 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -4682,6 +4682,14 @@ const CONST = { INITIAL_URL: 'INITIAL_URL', }, + RESERVATION_TYPE: { + CAR: 'car', + HOTEL: 'hotel', + FLIGHT: 'flight', + RAIL: 'rail', + MISC: 'misc', + }, + DOT_SEPARATOR: '•', DEFAULT_TAX: { diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index 9b560a0a037c..804dc2e731fc 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -1,5 +1,94 @@ +import React from 'react'; import {View} from 'react-native'; +import Icon from '@components/Icon'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import SpacerView from '@components/SpacerView'; import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import DateUtils from '@libs/DateUtils'; +import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; +import variables from '@styles/variables'; +import * as Expensicons from '@src/components/Icon/Expensicons'; +import CONST from '@src/CONST'; +import * as ReportUtils from '@src/libs/ReportUtils'; +import * as TripReservationUtils from '@src/libs/TripReservationUtils'; +import type {Reservation, ReservationTimeDetails} from '@src/types/onyx/Transaction'; + +// TODO: to be removed once backend is ready +const testReservationsList: Reservation[] = [ + { + company: { + longName: 'American Airlines', + shortName: 'AA', + }, + confirmations: [ + { + name: 'Confirmation Number', + value: 'DDPNOF', + }, + ], + start: { + address: 'AA Address', + date: '2022-08-21 21:36', + longName: 'Philadelphia', + shortName: 'PHL', + timezoneOffset: -360, + }, + end: { + address: 'BB Address', + date: '2022-11-10 12:36', + longName: 'San Francisco', + shortName: 'SFO', + timezoneOffset: -360, + }, + numPassengers: 2, + route: { + class: '', + number: '2579', + }, + type: CONST.RESERVATION_TYPE.FLIGHT, + }, + { + company: { + longName: 'W San Francisco', + }, + confirmations: [ + { + name: 'Booking Number', + value: 'SUDMBE', + }, + { + name: 'Confirmation Number', + value: 'GGGGGGG-HHHHHH-IIIIII', + }, + ], + start: { + address: '181 3rd St, San Francisco, CA 94103', + date: '2023-01-22 21:40', + longName: 'SFO123', + shortName: 'SFO', + timezoneOffset: -420, + }, + end: { + address: 'DD Address', + date: '2023-02-10 12:00', + longName: 'Denver-Denver Intl', + shortName: 'DEN', + timezoneOffset: -420, + }, + numberOfRooms: 3, + route: { + class: '', + number: '46564', + }, + type: CONST.RESERVATION_TYPE.HOTEL, + }, +]; type TripDetailsViewProps = { /** The active IOUReport, used for Onyx subscription */ @@ -9,14 +98,128 @@ type TripDetailsViewProps = { shouldShowHorizontalRule: boolean; }; +type ReservationViewProps = { + reservation: Reservation; +}; + +function ReservationView({reservation}: ReservationViewProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + + const reservationIcon = TripReservationUtils.getTripReservationIcon(reservation.type); + + const formatAirportInfo = (reservationTimeDetails: ReservationTimeDetails) => `${reservationTimeDetails.longName} (${reservationTimeDetails.shortName})`; + + const getFormattedDate = () => { + switch (reservation.type) { + case CONST.RESERVATION_TYPE.FLIGHT: + case CONST.RESERVATION_TYPE.RAIL: + return DateUtils.getFormattedTransportDate(new Date(reservation.start.date)); + case CONST.RESERVATION_TYPE.HOTEL: + return DateUtils.getFormattedReservationRangeDate(new Date(reservation.start.date), new Date(reservation.end.date)); + default: + return DateUtils.formatToLongDateWithWeekday(new Date(reservation.start.date)); + } + }; + + const formattedDate = getFormattedDate(); + + const bottomDescription = `${reservation.confirmations.length > 0 ? `${reservation.confirmations[0].value} • ` : ''}${ + reservation.type === CONST.RESERVATION_TYPE.FLIGHT + ? `${reservation.company.longName} • ${reservation.company.shortName ?? ''} ${reservation.route.number}` + : reservation.start.address + }`; + + const titleComponent = ( + + {reservation.type === CONST.RESERVATION_TYPE.FLIGHT ? ( + + {formatAirportInfo(reservation.start)} + + {formatAirportInfo(reservation.end)} + + ) : ( + + {reservation.company.longName} + + )} + {bottomDescription} + + ); + + return ( + {}} + iconHeight={20} + iconWidth={20} + iconStyles={[styles.tripReservationIconContainer(true), styles.mr2]} + secondaryIconFill={theme.icon} + /> + ); +} + function TripDetailsView({iouReportID, shouldShowHorizontalRule}: TripDetailsViewProps) { + const StyleUtils = useStyleUtils(); + const {isSmallScreenWidth} = useWindowDimensions(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + // TODO: once backend is ready uncomment lines below and remove test data + const reservations = testReservationsList; + // const tripTransactions = ReportUtils.getTripTransactions(iouReportID); + + // const reservations: Reservation[] = TripReservationUtils.getReservationsFromTripTransactions(tripTransactions); + return ( - - - Hello world ${iouReportID} ${shouldShowHorizontalRule} - + + + + + + + {translate('travel.tripSummary')} + + + + <> + {reservations.map((reservation) => ( + + + + ))} + + + ); } +TripDetailsView.displayName = 'TripDetailsView'; + export default TripDetailsView; diff --git a/src/styles/index.ts b/src/styles/index.ts index 1b1587d81bce..076bb0b562fb 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -412,6 +412,11 @@ const styles = (theme: ThemeColors) => fontSize: variables.fontSizeExtraSmall, }, + textNormalBold: { + fontSize: variables.fontSizeNormal, + fontWeight: 'bold', + }, + textNormal: { fontSize: variables.fontSizeNormal, }, @@ -1327,6 +1332,18 @@ const styles = (theme: ThemeColors) => lineHeight: variables.lineHeightNormal, }, + textSupportingSmallSize: { + fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, + fontSize: variables.fontSizeSmall, + color: theme.textSupporting, + }, + + textSupportingNormalSize: { + fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, + fontSize: variables.fontSizeNormal, + color: theme.textSupporting, + }, + textLabelSupporting: { fontFamily: FontUtils.fontFamily.platform.EXP_NEUE, fontSize: variables.fontSizeLabel, @@ -1373,6 +1390,10 @@ const styles = (theme: ThemeColors) => color: theme.textSupporting, }, + lh14: { + lineHeight: 14, + }, + lh16: { lineHeight: 16, }, @@ -4876,6 +4897,15 @@ const styles = (theme: ThemeColors) => fontSize: variables.fontSizeXLarge, }, + tripReservationIconContainer: (isBiggerIcon: boolean) => ({ + width: isBiggerIcon ? 40 : 32, + height: isBiggerIcon ? 40 : 32, + backgroundColor: theme.overlay, + borderRadius: isBiggerIcon ? 40 : 32, + alignItems: 'center', + justifyContent: 'center', + }), + textLineThrough: { textDecorationLine: 'line-through', }, From 0079ccc62e71bbc634946ab2ef588aabcad87e38 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 6 May 2024 05:43:18 +0530 Subject: [PATCH 014/219] add dateutils and lang --- src/languages/en.ts | 10 ++++++ src/languages/es.ts | 10 ++++++ src/libs/DateUtils.ts | 72 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index aadd6ac03f58..9fa4953e5e1c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -224,6 +224,7 @@ export default { tomorrowAt: 'Tomorrow at', yesterdayAt: 'Yesterday at', conjunctionAt: 'at', + conjunctionTo: 'to', genericErrorMessage: 'Oops... something went wrong and your request could not be completed. Please try again later.', error: { invalidAmount: 'Invalid amount.', @@ -1860,6 +1861,15 @@ export default { agree: 'I agree to the travel ', error: 'You must accept the Terms & Conditions for travel to continue', }, + flight: 'Flight', + hotel: 'Hotel', + car: 'Car', + misc: 'Miscellaneous', + rail: 'Rail', + viewTrip: 'View trip', + trip: 'Trip', + tripSummary: 'Trip summary', + departs: 'Departs', }, workspace: { common: { diff --git a/src/languages/es.ts b/src/languages/es.ts index d4efe11a51b0..629270d0f4dc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -214,6 +214,7 @@ export default { tomorrowAt: 'Mañana a las', yesterdayAt: 'Ayer a las', conjunctionAt: 'a', + conjunctionTo: 'a', genericErrorMessage: 'Ups... algo no ha ido bien y la acción no se ha podido completar. Por favor, inténtalo más tarde.', error: { invalidAmount: 'Importe no válido.', @@ -1884,6 +1885,15 @@ export default { agree: 'Acepto los ', error: 'Debes aceptar los Términos y condiciones para que el viaje continúe', }, + flight: 'Vuelo', + hotel: 'Hotel', + car: 'Auto', + misc: 'Misceláneo', + rail: 'Carril', + viewTrip: 'Ver viaje', + trip: 'Viaje', + tripSummary: 'Resumen del viaje', + departs: 'Sale', }, workspace: { common: { diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 22c5a7d907cc..6b2cd3c4bf98 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -15,8 +15,10 @@ import { isAfter, isBefore, isSameDay, + isSameMonth, isSameSecond, isSameYear, + isThisYear, isValid, parse, set, @@ -764,6 +766,73 @@ function getLastBusinessDayOfMonth(inputDate: Date): number { return getDate(currentDate); } +/** + * Returns a formatted date range from date 1 to date 2. + * Dates are formatted as follows: + * 1. When both dates refer to the same day: Mar 17 + * 2. When both dates refer to the same month: Mar 17-20 + * 3. When both dates refer to the same year: Feb 28 to Mar 1 + * 4. When the dates are from different years: Dec 28, 2023 to Jan 5, 2024 + */ +function getFormattedDateRange(date1: Date, date2: Date): string { + const {translateLocal} = Localize; + + if (isSameDay(date1, date2)) { + // Dates are from the same day + return format(date1, 'MMM d'); + } + if (isSameMonth(date1, date2)) { + // Dates in the same month and year, differ by days + return `${format(date1, 'MMM d')}-${format(date2, 'd')}`; + } + if (isSameYear(date1, date2)) { + // Dates are in the same year, differ by months + return `${format(date1, 'MMM d')} ${translateLocal('common.to').toLowerCase()} ${format(date2, 'MMM d')}`; + } + // Dates differ by years, months, days + return `${format(date1, 'MMM d, yyyy')} ${translateLocal('common.to').toLowerCase()} ${format(date2, 'MMM d, yyyy')}`; +} + +/** + * Returns a formatted date range from date 1 to date 2 of a reservation. + * Dates are formatted as follows: + * 1. When both dates refer to the same day and the current year: Sunday, Mar 17 + * 2. When both dates refer to the same day but not the current year: Wednesday, Mar 17, 2023 + * 3. When both dates refer to the current year: Sunday, Mar 17 to Wednesday, Mar 20 + * 4. When the dates are from different years or from a year which is not current: Wednesday, Mar 17, 2023 to Saturday, Jan 20, 2024 + */ +function getFormattedReservationRangeDate(date1: Date, date2: Date): string { + const {translateLocal} = Localize; + if (isSameDay(date1, date2) && isThisYear(date1)) { + // Dates are from the same day + return format(date1, 'EEEE, MMM d, yyyy'); + } + if (isSameDay(date1, date2)) { + // Dates are from the same day + return format(date1, 'EEEE, MMM d'); + } + if (isSameYear(date1, date2) && isThisYear(date1)) { + // Dates are in the current year, differ by months + return `${format(date1, 'EEEE, MMM d')} ${translateLocal('common.conjunctionTo')} ${format(date2, 'EEEE, MMM d')}`; + } + // Dates differ by years, months, days or only by months but the year is not current + return `${format(date1, 'EEEE, MMM d, yyyy')} ${translateLocal('common.conjunctionTo')} ${format(date2, 'EEEE, MMM d, yyyy')}`; +} + +/** + * Returns a formatted date of a transport mean departure. + * Dates are formatted as follows: + * 1. When the date reffers to the current day: Departs on Sunday, Mar 17 at 8:00 + * 2. When the date reffers not to the current day: Departs on Wednesday, Mar 17, 2023 at 8:00 + */ +function getFormattedTransportDate(date: Date): string { + const {translateLocal} = Localize; + if (isThisYear(date)) { + return `${translateLocal('travel.departs')} ${format(date, 'EEEE, MMM d')} ${translateLocal('common.conjunctionAt')} ${format(date, 'HH:MM')}`; + } + return `${translateLocal('travel.departs')} ${format(date, 'EEEE, MMM d, yyyy')} ${translateLocal('common.conjunctionAt')} ${format(date, 'HH:MM')}`; +} + const DateUtils = { formatToDayOfWeek, formatToLongDateWithWeekday, @@ -811,6 +880,9 @@ const DateUtils = { formatToSupportedTimezone, enrichMoneyRequestTimestamp, getLastBusinessDayOfMonth, + getFormattedDateRange, + getFormattedReservationRangeDate, + getFormattedTransportDate, }; export default DateUtils; From 0d7db3783da45b37e1648d8dff1d5afcf4b5c6f7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 6 May 2024 05:48:50 +0530 Subject: [PATCH 015/219] add reservation type --- src/libs/TripReservationUtils.ts | 31 +++++++++++++++ src/types/onyx/Transaction.ts | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/libs/TripReservationUtils.ts diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts new file mode 100644 index 000000000000..c0b1724d4835 --- /dev/null +++ b/src/libs/TripReservationUtils.ts @@ -0,0 +1,31 @@ +import * as Expensicons from '@src/components/Icon/Expensicons'; +import CONST from '@src/CONST'; +import type {Reservation, ReservationType} from '@src/types/onyx/Transaction'; +import type Transaction from '@src/types/onyx/Transaction'; +import type IconAsset from '@src/types/utils/IconAsset'; + +function getTripReservationIcon(reservationType: ReservationType): IconAsset { + switch (reservationType) { + case CONST.RESERVATION_TYPE.FLIGHT: + return Expensicons.Plane; + case CONST.RESERVATION_TYPE.HOTEL: + return Expensicons.Bed; + case CONST.RESERVATION_TYPE.CAR: + return Expensicons.CarWithKey; + case CONST.RESERVATION_TYPE.MISC: + return Expensicons.Luggage; + case CONST.RESERVATION_TYPE.RAIL: + return Expensicons.Train; + default: + return Expensicons.CarWithKey; + } +} + +function getReservationsFromTripTransactions(transactions: Transaction[]): Reservation[] { + return transactions + .map((item) => item?.reservationList ?? []) + .filter((item) => item.length > 0) + .flat(); +} + +export {getTripReservationIcon, getReservationsFromTripTransactions}; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 7894876fdcdf..065ddba4b847 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -107,6 +107,64 @@ type TaxRate = { data?: TaxRateData; }; +type Fare = { + amount: number; + convertedAmount: number; + convertedCurrency: string; + currencyCode: string; +}; + +type SpotnanaPayload = { + tripId: string; + pnrId: string; + bookingStatus: string; + documents: unknown[]; + carPnr?: unknown; + airPnr?: unknown; + totalFare: Fare; + totalFareAmount: { + base: Fare; + tax: Fare; + }; + version: number; +}; + +type Reservation = { + reservationID?: string; + start: ReservationTimeDetails; + end: ReservationTimeDetails; + type: ReservationType; + company?: Company; + confirmations?: ReservationConfirmation[]; + numPassengers?: number; + numberOfRooms?: number; + route?: { + class: string; + number: string; + }; +}; + +type ReservationTimeDetails = { + date: string; + address?: string; + longName?: string; + shortName?: string; + timezoneOffset?: number; +}; + +type Company = { + longName: string; + shortName?: string; + phone?: string; +}; + +type ReservationConfirmation = { + name: string; + value: string; +}; + +type ReservationType = ValueOf; + type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< { /** The original transaction amount */ @@ -225,6 +283,11 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates transaction loading */ isLoading?: boolean; + /** Travel reserviation list */ + reservationList?: Reservation[]; + + originalSpotnanaPayload?: SpotnanaPayload; + /** The actionable report action ID associated with the transaction */ actionableWhisperReportActionID?: string; @@ -264,6 +327,9 @@ export type { TransactionPendingFieldsKey, TransactionChanges, TaxRate, + Reservation, + ReservationTimeDetails, + ReservationType, ReceiptSource, TransactionCollectionDataSet, }; From 26c0c880225456b1c908d770d80b00e0a946138c Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 6 May 2024 05:49:42 +0530 Subject: [PATCH 016/219] add assets --- assets/images/bed.svg | 1 + assets/images/car-with-key.svg | 1 + assets/images/plane.svg | 1 + assets/images/train.svg | 1 + 4 files changed, 4 insertions(+) create mode 100644 assets/images/bed.svg create mode 100644 assets/images/car-with-key.svg create mode 100644 assets/images/plane.svg create mode 100644 assets/images/train.svg diff --git a/assets/images/bed.svg b/assets/images/bed.svg new file mode 100644 index 000000000000..fd654c036a7c --- /dev/null +++ b/assets/images/bed.svg @@ -0,0 +1 @@ + diff --git a/assets/images/car-with-key.svg b/assets/images/car-with-key.svg new file mode 100644 index 000000000000..1586c0dfecfa --- /dev/null +++ b/assets/images/car-with-key.svg @@ -0,0 +1 @@ + diff --git a/assets/images/plane.svg b/assets/images/plane.svg new file mode 100644 index 000000000000..bf4d56875239 --- /dev/null +++ b/assets/images/plane.svg @@ -0,0 +1 @@ + diff --git a/assets/images/train.svg b/assets/images/train.svg new file mode 100644 index 000000000000..9b838708ecdf --- /dev/null +++ b/assets/images/train.svg @@ -0,0 +1 @@ + From 5497a13eb45112c175b86a3b6748b10dc650fa49 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 6 May 2024 05:51:29 +0530 Subject: [PATCH 017/219] add assets --- src/components/Icon/Expensicons.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 74dbf8622a24..a4848eb5ec83 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -17,6 +17,7 @@ import NotificationsAvatar from '@assets/images/avatars/notifications-avatar.svg import ActiveRoomAvatar from '@assets/images/avatars/room.svg'; import BackArrow from '@assets/images/back-left.svg'; import Bank from '@assets/images/bank.svg'; +import Bed from '@assets/images/bed.svg'; import Bell from '@assets/images/bell.svg'; import BellSlash from '@assets/images/bellSlash.svg'; import Bill from '@assets/images/bill.svg'; @@ -26,6 +27,7 @@ import Bug from '@assets/images/bug.svg'; import Building from '@assets/images/building.svg'; import Calendar from '@assets/images/calendar.svg'; import Camera from '@assets/images/camera.svg'; +import CarWithKey from '@assets/images/car-with-key.svg'; import Car from '@assets/images/car.svg'; import CardsAndDomains from '@assets/images/cards-and-domains.svg'; import Cash from '@assets/images/cash.svg'; @@ -120,6 +122,7 @@ import Paycheck from '@assets/images/paycheck.svg'; import Pencil from '@assets/images/pencil.svg'; import Phone from '@assets/images/phone.svg'; import Pin from '@assets/images/pin.svg'; +import Plane from '@assets/images/plane.svg'; import Play from '@assets/images/play.svg'; import Plus from '@assets/images/plus.svg'; import Printer from '@assets/images/printer.svg'; @@ -151,6 +154,7 @@ import Task from '@assets/images/task.svg'; import Thread from '@assets/images/thread.svg'; import ThreeDots from '@assets/images/three-dots.svg'; import ThumbsUp from '@assets/images/thumbs-up.svg'; +import Train from '@assets/images/train.svg'; import Transfer from '@assets/images/transfer.svg'; import Trashcan from '@assets/images/trashcan.svg'; import Unlock from '@assets/images/unlock.svg'; @@ -334,6 +338,10 @@ export { ChatBubbleUnread, ChatBubbleReply, Lightbulb, + Plane, + Bed, + CarWithKey, DocumentPlus, + Train, Clear, }; From 3adc30918ce4786d5badbab93b6feecd6eb1e99f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 6 May 2024 06:00:40 +0530 Subject: [PATCH 018/219] add title style to menu item --- src/components/MenuItem.tsx | 22 +++++++++++++++++-- .../ReportActionItem/TripDetailsView.tsx | 8 +++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index bf35d65340fc..bd949bf8910d 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -105,6 +105,8 @@ type MenuItemBaseProps = { /** The fill color to pass into the secondary icon. */ secondaryIconFill?: string; + isSecondaryIconHoverable?: boolean; + /** Icon Width */ iconWidth?: number; @@ -170,6 +172,12 @@ type MenuItemBaseProps = { /** Text to display for the item */ title?: string; + /** Component to display as the title */ + titleComponent?: ReactElement; + + /** Any additional styles to apply to the container for title components */ + titleContainerStyle?: StyleProp; + /** A right-aligned subtitle for this menu option */ subtitle?: string | number; @@ -285,6 +293,7 @@ function MenuItem( secondaryIcon, secondaryIconFill, iconType = CONST.ICON_TYPE_ICON, + isSecondaryIconHoverable = false, iconWidth, iconHeight, iconStyles, @@ -302,6 +311,8 @@ function MenuItem( focused = false, disabled = false, title, + titleComponent, + titleContainerStyle, subtitle, shouldShowBasicTitle, label, @@ -528,7 +539,13 @@ function MenuItem( )} {secondaryIcon && ( - + )} - + {!!description && shouldShowDescriptionOnTop && ( )} + {titleComponent} diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index 804dc2e731fc..685b015ecf82 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -124,9 +124,9 @@ function ReservationView({reservation}: ReservationViewProps) { const formattedDate = getFormattedDate(); - const bottomDescription = `${reservation.confirmations.length > 0 ? `${reservation.confirmations[0].value} • ` : ''}${ + const bottomDescription = `${reservation.confirmations?.length > 0 ? `${reservation.confirmations[0].value} • ` : ''}${ reservation.type === CONST.RESERVATION_TYPE.FLIGHT - ? `${reservation.company.longName} • ${reservation.company.shortName ?? ''} ${reservation.route.number}` + ? `${reservation.company?.longName} • ${reservation?.company?.shortName ?? ''} ${reservation.route?.number}` : reservation.start.address }`; @@ -148,7 +148,7 @@ function ReservationView({reservation}: ReservationViewProps) { numberOfLines={1} style={[styles.textNormalBold, styles.lh20]} > - {reservation.company.longName} + {reservation.company?.longName} )} {bottomDescription} @@ -158,7 +158,7 @@ function ReservationView({reservation}: ReservationViewProps) { return ( Date: Wed, 8 May 2024 13:02:29 +0200 Subject: [PATCH 019/219] moving shouldKeepUserInput to the MoneyRequestAmountInput component --- src/components/MoneyRequestAmountInput.tsx | 12 ++++++++---- src/pages/iou/MoneyRequestAmountForm.tsx | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index fcc156db4a96..2a47d147e05a 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -62,6 +62,9 @@ type MoneyRequestAmountInputProps = { /** Style for the touchable input wrapper */ touchableInputWrapperStyle?: StyleProp; + + /** Whether the user input should be kept or not */ + shouldKeepUserInput?: boolean; }; type Selection = { @@ -88,6 +91,7 @@ function MoneyRequestAmountInput( hideCurrencySymbol = false, shouldUpdateSelection = true, moneyRequestAmountInputRef, + shouldKeepUserInput = false, ...props }: MoneyRequestAmountInputProps, forwardedRef: ForwardedRef, @@ -97,7 +101,7 @@ function MoneyRequestAmountInput( const textInput = useRef(null); const decimals = CurrencyUtils.getCurrencyDecimals(currency); - const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmountAsString(amount).toString() : ''; const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); @@ -160,10 +164,10 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (!currency || typeof amount !== 'number') { + if (!currency || typeof amount !== 'number' || shouldKeepUserInput) { return; } - const frontendAmount = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + const frontendAmount = amount ? CurrencyUtils.convertToFrontendAmountAsString(amount).toString() : ''; setCurrentAmount(frontendAmount); setSelection({ start: frontendAmount.length, @@ -171,7 +175,7 @@ function MoneyRequestAmountInput( }); // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [amount]); + }, [amount, shouldKeepUserInput]); // Modifies the amount to match the decimals for changed currency. useEffect(() => { diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 291cf458d498..4de70ec96fdf 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -157,13 +157,13 @@ function MoneyRequestAmountForm( }, []); useEffect(() => { - if (!currency || typeof amount !== 'number' || shouldKeepUserInput) { + if (!currency || typeof amount !== 'number') { return; } initializeAmount(amount); // we want to re-initialize the state only when the selected tab // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTab, shouldKeepUserInput]); + }, [selectedTab]); /** * Update amount with number or Backspace pressed for BigNumberPad. @@ -286,6 +286,7 @@ function MoneyRequestAmountForm( } textInput.current = ref; }} + shouldKeepUserInput={shouldKeepUserInput} moneyRequestAmountInputRef={moneyRequestAmountInput} inputStyle={[styles.iouAmountTextInput]} containerStyle={[styles.iouAmountTextInputContainer]} From 2a6f9ef52af48c73f9afbb7ad6e8fdbd1f79916c Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 8 May 2024 13:09:56 +0200 Subject: [PATCH 020/219] Minor edit --- src/libs/CurrencyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 920d49c8ceb0..d3660c5f16f4 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -141,7 +141,7 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE * Acts the same as `convertAmountToDisplayString` but the result string does not contain currency */ function convertToDisplayStringWithoutCurrency(amountInCents: number, currency: string = CONST.CURRENCY.USD) { - const convertedAmount = convertToFrontendAmount(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); return NumberFormatUtils.formatToParts(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, From f258c08c6fe2f9609341f27c585396212c3f7f03 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 8 May 2024 17:42:55 +0530 Subject: [PATCH 021/219] add ReceiptSlash icon for corrupt pdf's. Signed-off-by: Krishna Gupta --- src/components/Icon/Expensicons.ts | 2 ++ src/components/PDFThumbnail/index.native.tsx | 29 +++++++++++------ src/components/PDFThumbnail/index.tsx | 33 +++++++++++++++----- src/components/PDFThumbnail/types.ts | 5 +-- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 74dbf8622a24..d052dead7366 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -128,6 +128,7 @@ import QrCode from '@assets/images/qrcode.svg'; import QuestionMark from '@assets/images/question-mark-circle.svg'; import ReceiptScan from '@assets/images/receipt-scan.svg'; import ReceiptSearch from '@assets/images/receipt-search.svg'; +import ReceiptSlash from '@assets/images/receipt-slash.svg'; import Receipt from '@assets/images/receipt.svg'; import RemoveMembers from '@assets/images/remove-members.svg'; import Rotate from '@assets/images/rotate-image.svg'; @@ -296,6 +297,7 @@ export { QuestionMark, Receipt, ReceiptScan, + ReceiptSlash, RemoveMembers, ReceiptSearch, Rotate, diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index c008d6823a00..7498fb4597b5 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -2,22 +2,24 @@ import React, {useState} from 'react'; import {View} from 'react-native'; import Pdf from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import type PDFThumbnailProps from './types'; -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, errorLabelStyles}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { const styles = useThemeStyles(); + const theme = useTheme(); const sizeStyles = [styles.w100, styles.h100]; - const {translate} = useLocalize(); - const [isCorrupted, setIsCorrupted] = useState(false); + + const [hasError, setHasError] = useState(false); return ( - - {enabled && !isCorrupted && ( + + {enabled && !hasError && ( { if ('message' in error && typeof error.message === 'string' && error.message.match(/corrupted/i)) { - setIsCorrupted(true); + setHasError(true); } if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) { return; @@ -39,7 +41,16 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena }} /> )} - {isCorrupted && {translate('attachmentView.failedToLoadPDF')}} + {hasError && ( + + + + )} ); diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index 8663357fb508..41098a8b77b9 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -1,11 +1,12 @@ // @ts-expect-error - This line imports a module from 'pdfjs-dist' package which lacks TypeScript typings. import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {Document, pdfjs, Thumbnail} from 'react-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import type PDFThumbnailProps from './types'; @@ -14,9 +15,10 @@ if (!pdfjs.GlobalWorkerOptions.workerSrc) { pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(new Blob([pdfWorkerSource], {type: 'text/javascript'})); } -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, errorLabelStyles}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const theme = useTheme(); + const [hasError, setHasError] = useState(false); const thumbnail = useMemo( () => ( @@ -29,19 +31,34 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena }} externalLinkTarget="_blank" onPassword={onPassword} - error={{translate('attachmentView.failedToLoadPDF')}} + onLoad={() => { + setHasError(false); + }} + onLoadError={() => { + setHasError(true); + }} + error={ + + + + } > ), - [isAuthTokenRequired, previewSourceURL, onPassword, errorLabelStyles, translate, styles.textLabel], + [isAuthTokenRequired, previewSourceURL, onPassword, styles, theme], ); return ( - {enabled && thumbnail} + {enabled && thumbnail} ); } diff --git a/src/components/PDFThumbnail/types.ts b/src/components/PDFThumbnail/types.ts index 5891ac133da1..11253e462aca 100644 --- a/src/components/PDFThumbnail/types.ts +++ b/src/components/PDFThumbnail/types.ts @@ -1,4 +1,4 @@ -import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; type PDFThumbnailProps = { /** Source URL for the preview PDF */ @@ -15,9 +15,6 @@ type PDFThumbnailProps = { /** Callback to call if PDF is password protected */ onPassword?: () => void; - - /** Styles for the error label */ - errorLabelStyles?: StyleProp; }; export default PDFThumbnailProps; From 4ce6e266af438c302e286c0802e4d1616d10b026 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Wed, 8 May 2024 17:24:28 +0200 Subject: [PATCH 022/219] cleaning --- src/pages/iou/MoneyRequestAmountForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 4de70ec96fdf..b3b00ab37bf6 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -224,7 +224,7 @@ function MoneyRequestAmountForm( onSubmitButtonPress({amount: currentAmount, currency, paymentMethod: iouPaymentType}); }, - [taxAmount, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount], + [taxAmount, onSubmitButtonPress, currency, formattedTaxAmount], ); const buttonText: string = useMemo(() => { From d6a166b50a036de297c7740b479e65b9a68e5019 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 9 May 2024 09:04:55 +0530 Subject: [PATCH 023/219] receipt slash icon size update. Signed-off-by: Krishna Gupta --- assets/images/receipt-slash.svg | 12 ++++++++++++ src/components/PDFThumbnail/index.native.tsx | 5 +++-- src/components/PDFThumbnail/index.tsx | 5 +++-- src/styles/variables.ts | 2 ++ 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 assets/images/receipt-slash.svg diff --git a/assets/images/receipt-slash.svg b/assets/images/receipt-slash.svg new file mode 100644 index 000000000000..2af3fcbc60e6 --- /dev/null +++ b/assets/images/receipt-slash.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index 7498fb4597b5..b5e2cb887507 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -7,6 +7,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; +import variables from '@styles/variables'; import type PDFThumbnailProps from './types'; function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { @@ -45,8 +46,8 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index 41098a8b77b9..8cd84934655f 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -9,6 +9,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; +import variables from '@styles/variables'; import type PDFThumbnailProps from './types'; if (!pdfjs.GlobalWorkerOptions.workerSrc) { @@ -41,8 +42,8 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 7ab469af9533..381de09b48a1 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -187,6 +187,8 @@ export default { eReceiptBGHeight: 540, eReceiptBGHWidth: 335, eReceiptTextContainerWidth: 263, + receiptPlaceholderIconWidth: 80, + receiptPlaceholderIconHeight: 80, reportPreviewMaxWidth: 335, reportActionImagesSingleImageHeight: 147, reportActionImagesDoubleImageHeight: 138, From 8745bc8d651741c11ae5d1b5ce3d634f097bb856 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 9 May 2024 12:12:38 +0530 Subject: [PATCH 024/219] fix stylings. Signed-off-by: Krishna Gupta --- src/components/PDFThumbnail/index.native.tsx | 5 ++-- src/components/PDFThumbnail/index.tsx | 29 +++++++++++--------- src/styles/index.ts | 11 ++++++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index b5e2cb887507..fb28689a6c7b 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -14,7 +14,6 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena const styles = useThemeStyles(); const theme = useTheme(); const sizeStyles = [styles.w100, styles.h100]; - const [hasError, setHasError] = useState(false); return ( @@ -29,7 +28,7 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena singlePage style={sizeStyles} onError={(error) => { - if ('message' in error && typeof error.message === 'string' && error.message.match(/corrupted/i)) { + if ('message' in error && typeof error.message === 'string' && error.message.match(/Load pdf failed/i)) { setHasError(true); } if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) { @@ -43,7 +42,7 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena /> )} {hasError && ( - + { setHasError(true); }} - error={ - - - - } + error={() => null} > ), - [isAuthTokenRequired, previewSourceURL, onPassword, styles, theme], + [isAuthTokenRequired, previewSourceURL, onPassword], ); return ( - - {enabled && thumbnail} + + + {enabled && thumbnail} + {hasError && ( + + + + )} + ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 060fb1c5ba90..c5cc1e920bdf 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4215,6 +4215,7 @@ const styles = (theme: ThemeColors) => borderRadius: 16, margin: 20, overflow: 'hidden', + justifyContent: 'center', }, reportPreviewBox: { @@ -4373,6 +4374,16 @@ const styles = (theme: ThemeColors) => maxWidth: 400, }, + pdfErrorPlaceholder: { + overflow: 'hidden', + borderWidth: 2, + borderColor: theme.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + maxWidth: 400, + height: '100%', + backgroundColor: theme.highlightBG, + }, + moneyRequestAttachReceipt: { backgroundColor: theme.highlightBG, borderColor: theme.border, From bbcea3c4a39be48b9a709b36fe9f3b8bf3769d48 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 9 May 2024 12:57:53 +0530 Subject: [PATCH 025/219] feat: prevent uploading corrupt pdf's. Signed-off-by: Krishna Gupta --- src/components/AttachmentPicker/index.native.tsx | 2 +- src/components/MoneyRequestConfirmationList.tsx | 15 ++++++++++++--- src/components/PDFThumbnail/index.native.tsx | 14 ++++++-------- src/components/PDFThumbnail/index.tsx | 7 +++++-- src/components/PDFThumbnail/types.ts | 3 +++ src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- .../iou/request/step/IOURequestStepScan/index.tsx | 2 +- 8 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index ad4cf023c096..431698b3567c 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -212,7 +212,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * An attachment error dialog when user selected malformed images */ const showImageCorruptionAlert = useCallback(() => { - Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedImage')); + Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); }, [translate]); /** diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 2c592c20f4c6..9639d8a3f919 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -332,6 +332,7 @@ function MoneyRequestConfirmationList({ const [didConfirmSplit, setDidConfirmSplit] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); + const [invalidAttachmentPromt, setInvalidAttachmentPromt] = useState(translate('attachmentPicker.protectedPDFNotSupported')); const navigateBack = () => { Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); @@ -1050,7 +1051,14 @@ function MoneyRequestConfirmationList({ previewSourceURL={resolvedReceiptImage as string} // We don't support scaning password protected PDF receipt enabled={!isAttachmentInvalid} - onPassword={() => setIsAttachmentInvalid(true)} + onPassword={() => { + setIsAttachmentInvalid(true); + setInvalidAttachmentPromt(translate('attachmentPicker.protectedPDFNotSupported')); + }} + onLoadError={() => { + setInvalidAttachmentPromt(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); + setIsAttachmentInvalid(true); + }} /> ) : ( diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index fb28689a6c7b..7c4709e45435 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -10,7 +10,7 @@ import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import variables from '@styles/variables'; import type PDFThumbnailProps from './types'; -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError}: PDFThumbnailProps) { const styles = useThemeStyles(); const theme = useTheme(); const sizeStyles = [styles.w100, styles.h100]; @@ -28,16 +28,14 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena singlePage style={sizeStyles} onError={(error) => { - if ('message' in error && typeof error.message === 'string' && error.message.match(/Load pdf failed/i)) { - setHasError(true); + if (onLoadError) { + onLoadError(); } - if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) { + if ('message' in error && typeof error.message === 'string' && error.message.match(/password/i) && onPassword) { + onPassword(); return; } - if (!onPassword) { - return; - } - onPassword(); + setHasError(true); }} /> )} diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index 27b950f5394c..f06158275ade 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -16,7 +16,7 @@ if (!pdfjs.GlobalWorkerOptions.workerSrc) { pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(new Blob([pdfWorkerSource], {type: 'text/javascript'})); } -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError}: PDFThumbnailProps) { const styles = useThemeStyles(); const theme = useTheme(); const [hasError, setHasError] = useState(false); @@ -36,6 +36,9 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena setHasError(false); }} onLoadError={() => { + if (onLoadError) { + onLoadError(); + } setHasError(true); }} error={() => null} @@ -45,7 +48,7 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena ), - [isAuthTokenRequired, previewSourceURL, onPassword], + [isAuthTokenRequired, previewSourceURL, onPassword, onLoadError], ); return ( diff --git a/src/components/PDFThumbnail/types.ts b/src/components/PDFThumbnail/types.ts index 11253e462aca..349669ecc33e 100644 --- a/src/components/PDFThumbnail/types.ts +++ b/src/components/PDFThumbnail/types.ts @@ -15,6 +15,9 @@ type PDFThumbnailProps = { /** Callback to call if PDF is password protected */ onPassword?: () => void; + + /** Callback to call if PDF can't be loaded(corrupted) */ + onLoadError?: () => void; }; export default PDFThumbnailProps; diff --git a/src/languages/en.ts b/src/languages/en.ts index a822e1c5c3c8..98b186fccbf7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -348,7 +348,7 @@ export default { expensifyDoesntHaveAccessToCamera: "Expensify can't take photos without access to your camera. Tap Settings to update permissions.", attachmentError: 'Attachment error', errorWhileSelectingAttachment: 'An error occurred while selecting an attachment, please try again.', - errorWhileSelectingCorruptedImage: 'An error occurred while selecting a corrupted attachment, please try another file.', + errorWhileSelectingCorruptedAttachment: 'An error occurred while selecting a corrupted attachment, please try another file.', takePhoto: 'Take photo', chooseFromGallery: 'Choose from gallery', chooseDocument: 'Choose document', diff --git a/src/languages/es.ts b/src/languages/es.ts index b2f02708bbe2..3ebf4324f9b0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -342,7 +342,7 @@ export default { expensifyDoesntHaveAccessToCamera: 'Expensify no puede tomar fotos sin acceso a la cámara. Haz click en Configuración para actualizar los permisos.', attachmentError: 'Error al adjuntar archivo', errorWhileSelectingAttachment: 'Ha ocurrido un error al seleccionar un archivo adjunto. Por favor, inténtalo de nuevo.', - errorWhileSelectingCorruptedImage: 'Ha ocurrido un error al seleccionar un archivo adjunto corrupto. Por favor, inténtalo con otro archivo.', + errorWhileSelectingCorruptedAttachment: 'Ha ocurrido un error al seleccionar un archivo adjunto corrupto. Por favor, inténtalo con otro archivo.', takePhoto: 'Hacer una foto', chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir documento', diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 44b32754113a..53b68ed5307e 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -212,7 +212,7 @@ function IOURequestStepScan({ return true; }) .catch(() => { - setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedImage'); + setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment'); return false; }); } From 71e3c653b78a2228a4b451666cb41c1d6ca02b51 Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Thu, 9 May 2024 13:13:15 -0500 Subject: [PATCH 026/219] DOCS: Create Set-up-your-wallet.md New article --- .../expenses/Set-up-your-wallet.md | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 docs/articles/new-expensify/expenses/Set-up-your-wallet.md diff --git a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md new file mode 100644 index 000000000000..93658eb484c8 --- /dev/null +++ b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md @@ -0,0 +1,50 @@ +--- +title: Set up your wallet +description: Send and receive payments by adding your payment account +--- +
+To send and receive money using Expensify, you’ll first need to set up your Expensify Wallet by adding your payment account. + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click your profile image or icon in the bottom left menu. +2. Click **Wallet** in the left menu. +3. Click **Enable wallet**. +4. If you haven’t already added your bank account, click **Continue** and follow the prompts to add your bank account details with Plaid. If you have already connected your bank account, you’ll skip to the next step. + +{% include info.html %} +Plaid is an encrypted third-party financial data platform that Expensify uses to securely verify your banking information. +{% include end-info.html %} + +5. Enter your personal details (including your name, address, date of birth, phone number, and the last 4 digits of your social security number). +6. Click **Save & continue**. +7. Review the Onfido terms and click **Accept**. +8. Use the prompts to continue the next steps on your mobile device where you will select which option you want to use to verify your device: a QR code, a link, or a text message. +9. Follow the prompts on your mobile device to submit your ID with Onfido. + +When your ID is uploaded successfully, Onfido closes automatically. You can return to your Expensify Wallet to verify that it is now enabled. Once enabled, you are ready to send and receive payments. +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap your profile image or icon in the bottom menu. +2. Tap **Wallet**. +3. Tap **Enable wallet**. +4. If you haven’t already added your bank account, tap **Continue** and follow the prompts to add your bank account details with Plaid. If you have already connected your bank account, you’ll skip to the next step. + +{% include info.html %} +Plaid is an encrypted third-party financial data platform that Expensify uses to securely verify your banking information. +{% include end-info.html %} + +5. Enter your personal details (including your name, address, date of birth, phone number, and the last 4 digits of your social security number). +6. Tap **Save & continue**. +7. Review the Onfido terms and tap **Accept**. +8. Follow the prompts to submit your ID with Onfido. When your ID is uploaded successfully, Onfido closes automatically. +9. Tap **Enable wallet** again to enable payments for the wallet. + +Once enabled, you are ready to send and receive payments. +{% include end-option.html %} + +{% include end-selector.html %} + +
From 5c93faafb6d979e0f504e9f7a4824e4db25f4bdf Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 13 May 2024 08:43:24 +0530 Subject: [PATCH 027/219] fix margin and gap for trip summary --- src/components/ReportActionItem/TripDetailsView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index 685b015ecf82..9c6544439d23 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -131,7 +131,7 @@ function ReservationView({reservation}: ReservationViewProps) { }`; const titleComponent = ( - + {reservation.type === CONST.RESERVATION_TYPE.FLIGHT ? ( {formatAirportInfo(reservation.start)} @@ -172,7 +172,7 @@ function ReservationView({reservation}: ReservationViewProps) { onSecondaryInteraction={() => {}} iconHeight={20} iconWidth={20} - iconStyles={[styles.tripReservationIconContainer(true), styles.mr2]} + iconStyles={[styles.tripReservationIconContainer(true), styles.mr3]} secondaryIconFill={theme.icon} /> ); From 724c980aef1402de173b34d853c1e309f3942ec8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 13 May 2024 22:01:45 +0530 Subject: [PATCH 028/219] fix empty div when no title present --- src/components/MenuItem.tsx | 50 +++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index 6599edde0022..f562614a7532 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -574,30 +574,32 @@ function MenuItem( {description} )} - - {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( - - - - )} - {!shouldRenderAsHTML && !shouldParseTitle && !!title && ( - - {renderTitleContent()} - - )} - {shouldShowTitleIcon && titleIcon && ( - - - - )} - + {(!!title || !!shouldShowTitleIcon) && ( + + {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( + + + + )} + {!shouldRenderAsHTML && !shouldParseTitle && !!title && ( + + {renderTitleContent()} + + )} + {shouldShowTitleIcon && titleIcon && ( + + + + )} + + )} {!!description && !shouldShowDescriptionOnTop && ( Date: Mon, 13 May 2024 22:02:00 +0530 Subject: [PATCH 029/219] add 4px gap --- src/components/ReportActionItem/TripDetailsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index 9c6544439d23..8309f0ac3feb 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -160,7 +160,7 @@ function ReservationView({reservation}: ReservationViewProps) { description={formattedDate} descriptionTextStyle={[styles.textLabelSupporting, styles.lh16]} titleComponent={titleComponent} - titleContainerStyle={styles.justifyContentStart} + titleContainerStyle={[styles.justifyContentStart, styles.gap1]} secondaryIcon={reservationIcon} isSecondaryIconHoverable shouldShowRightIcon From 000ebb39b546c59c0d6af2622827100717d96093 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 14 May 2024 16:38:29 +0700 Subject: [PATCH 030/219] Hiden contact option if we don't include P2P option --- src/libs/OptionsListUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index f1bdac4a2494..461b25bad1c8 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1827,7 +1827,7 @@ function getOptions( return option; }); - const havingLoginPersonalDetails = options.personalDetails.filter((detail) => !!detail?.login && !!detail.accountID && !detail?.isOptimisticPersonalDetail); + const havingLoginPersonalDetails = includeP2P ? options.personalDetails.filter((detail) => !!detail?.login && !!detail.accountID && !detail?.isOptimisticPersonalDetail) : []; let allPersonalDetailsOptions = havingLoginPersonalDetails; if (sortPersonalDetailsByAlphaAsc) { From eb014587bf2ed5385749f860ebe9cdf50e7c0c03 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 14 May 2024 17:58:34 +0530 Subject: [PATCH 031/219] remove changes from MoneyRequestConfirmationList. Signed-off-by: Krishna Gupta --- src/components/MoneyRequestConfirmationList.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9639d8a3f919..2c592c20f4c6 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -332,7 +332,6 @@ function MoneyRequestConfirmationList({ const [didConfirmSplit, setDidConfirmSplit] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); - const [invalidAttachmentPromt, setInvalidAttachmentPromt] = useState(translate('attachmentPicker.protectedPDFNotSupported')); const navigateBack = () => { Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); @@ -1051,14 +1050,7 @@ function MoneyRequestConfirmationList({ previewSourceURL={resolvedReceiptImage as string} // We don't support scaning password protected PDF receipt enabled={!isAttachmentInvalid} - onPassword={() => { - setIsAttachmentInvalid(true); - setInvalidAttachmentPromt(translate('attachmentPicker.protectedPDFNotSupported')); - }} - onLoadError={() => { - setInvalidAttachmentPromt(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); - setIsAttachmentInvalid(true); - }} + onPassword={() => setIsAttachmentInvalid(true)} /> ) : ( From 31caba2c668877e26dc40a8d91c17d369df24403 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 14 May 2024 18:01:03 +0530 Subject: [PATCH 032/219] update MoneyRequestConfirmationList. Signed-off-by: Krishna Gupta --- src/components/MoneyRequestConfirmationList.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 086580d60e57..f94bba4031f2 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -325,6 +325,7 @@ function MoneyRequestConfirmationList({ const [didConfirmSplit, setDidConfirmSplit] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); + const [invalidAttachmentPromt, setInvalidAttachmentPromt] = useState(translate('attachmentPicker.protectedPDFNotSupported')); const navigateBack = useCallback( () => Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)), @@ -1096,7 +1097,14 @@ function MoneyRequestConfirmationList({ previewSourceURL={resolvedReceiptImage as string} // We don't support scanning password protected PDF receipt enabled={!isAttachmentInvalid} - onPassword={() => setIsAttachmentInvalid(true)} + onPassword={() => { + setIsAttachmentInvalid(true); + setInvalidAttachmentPromt(translate('attachmentPicker.protectedPDFNotSupported')); + }} + onLoadError={() => { + setInvalidAttachmentPromt(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); + setIsAttachmentInvalid(true); + }} /> ) : ( {shouldShowAllFields && supplementaryFields} @@ -1220,6 +1229,7 @@ function MoneyRequestConfirmationList({ transaction, transactionID, translate, + invalidAttachmentPromt, ], ); From 3a3dde8386617b517f8734dc143bea698cb4c806 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 15 May 2024 13:01:41 +0530 Subject: [PATCH 033/219] fix: input gets blurred when all chats are selected. Signed-off-by: Krishna Gupta --- src/pages/WorkspaceSwitcherPage/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/WorkspaceSwitcherPage/index.tsx b/src/pages/WorkspaceSwitcherPage/index.tsx index d84ed7b4add4..be9518962dde 100644 --- a/src/pages/WorkspaceSwitcherPage/index.tsx +++ b/src/pages/WorkspaceSwitcherPage/index.tsx @@ -188,6 +188,7 @@ function WorkspaceSwitcherPage() { showTooltip={false} onSelectRow={() => selectPolicy(defaultPolicy)} pressableStyle={styles.flexRow} + shouldSyncFocus={false} /> From b3c8abbfc22e77b738607e8ccded303cf4b5ea0d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 22:22:38 +0530 Subject: [PATCH 034/219] update const chat type --- src/CONST.ts | 2 +- src/libs/ReportUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index f9db6f5aeb16..647e4d3c6710 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -50,7 +50,7 @@ const KEYBOARD_SHORTCUT_NAVIGATION_TYPE = 'NAVIGATION_SHORTCUT'; const chatTypes = { POLICY_ANNOUNCE: 'policyAnnounce', POLICY_ADMINS: 'policyAdmins', - POLICY_TRIP_ROOM: 'policyTripRoom', + TRIP_ROOM: 'tripRoom', GROUP: 'group', DOMAIN_ALL: 'domainAll', POLICY_ROOM: 'policyRoom', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0bbe14119726..b1505a48d5b8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -912,7 +912,7 @@ function isInvoiceRoom(report: OnyxEntry): boolean { * Checks if a report is a completed task report. */ function isTripRoom(report: OnyxEntry): boolean { - return isChatReport(report) && getChatType(report) === CONST.REPORT.CHAT_TYPE.POLICY_TRIP_ROOM; + return isChatReport(report) && getChatType(report) === CONST.REPORT.CHAT_TYPE.TRIP_ROOM; } function isCurrentUserInvoiceReceiver(report: OnyxEntry): boolean { From bc7a0f896ae7a83f7bb2d84622e5307b16999a5b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 22:25:56 +0530 Subject: [PATCH 035/219] merge main --- src/components/MenuItem.tsx | 366 ++++++++++++++++++------------------ 1 file changed, 180 insertions(+), 186 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index f562614a7532..ad06f9fcb78c 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -105,8 +105,6 @@ type MenuItemBaseProps = { /** The fill color to pass into the secondary icon. */ secondaryIconFill?: string; - isSecondaryIconHoverable?: boolean; - /** Icon Width */ iconWidth?: number; @@ -152,12 +150,12 @@ type MenuItemBaseProps = { /** Should the description be shown above the title (instead of the other way around) */ shouldShowDescriptionOnTop?: boolean; - /** Error to display below the title */ - error?: string; - /** Error to display at the bottom of the component */ errorText?: MaybePhraseKey; + /** Hint to display at the bottom of the component */ + hintText?: MaybePhraseKey; + /** A boolean flag that gives the icon a green fill if true */ success?: boolean; @@ -178,12 +176,6 @@ type MenuItemBaseProps = { /** Text to display for the item */ title?: string; - /** Component to display as the title */ - titleComponent?: ReactElement; - - /** Any additional styles to apply to the container for title components */ - titleContainerStyle?: StyleProp; - /** A right-aligned subtitle for this menu option */ subtitle?: string | number; @@ -273,6 +265,9 @@ type MenuItemBaseProps = { /** Handles what to do when the item is focused */ onFocus?: () => void; + + /** Optional account id if it's user avatar or policy id if it's workspace avatar */ + avatarID?: number | string; }; type MenuItemProps = (IconProps | AvatarProps | NoIcon) & MenuItemBaseProps; @@ -299,7 +294,6 @@ function MenuItem( secondaryIcon, secondaryIconFill, iconType = CONST.ICON_TYPE_ICON, - isSecondaryIconHoverable = false, iconWidth, iconHeight, iconStyles, @@ -313,14 +307,12 @@ function MenuItem( description, helperText, helperTextStyle, - error, errorText, + hintText, success = false, focused = false, disabled = false, title, - titleComponent, - titleContainerStyle, subtitle, shouldShowBasicTitle, label, @@ -353,6 +345,7 @@ function MenuItem( isPaneMenu = false, shouldPutLeftPaddingWhenNoIcon = false, onFocus, + avatarID, }: MenuItemProps, ref: PressableRef, ) { @@ -466,7 +459,6 @@ function MenuItem( style={({pressed}) => [ containerStyle, - errorText ? styles.pb5 : {}, combinedStyle, !interactive && styles.cursorDefault, StyleUtils.getButtonBackgroundColorStyle(getButtonState(focused || isHovered, pressed, success, disabled, interactive), true), @@ -484,97 +476,97 @@ function MenuItem( onFocus={onFocus} > {({pressed}) => ( - <> - - {!!label && isLabelHoverable && ( - - - {label} - - - )} - - {!!icon && Array.isArray(icon) && ( - + + + + {!!label && isLabelHoverable && ( + + + {label} + + )} - {!icon && shouldPutLeftPaddingWhenNoIcon && } - {icon && !Array.isArray(icon) && ( - - {typeof icon !== 'string' && iconType === CONST.ICON_TYPE_ICON && ( + + {!!icon && Array.isArray(icon) && ( + + )} + {!icon && shouldPutLeftPaddingWhenNoIcon && } + {icon && !Array.isArray(icon) && ( + + {typeof icon !== 'string' && iconType === CONST.ICON_TYPE_ICON && ( + + )} + {icon && iconType === CONST.ICON_TYPE_WORKSPACE && ( + + )} + {iconType === CONST.ICON_TYPE_AVATAR && ( + + )} + + )} + {secondaryIcon && ( + - )} - {icon && iconType === CONST.ICON_TYPE_WORKSPACE && ( - - )} - {iconType === CONST.ICON_TYPE_AVATAR && ( - - )} - - )} - {secondaryIcon && ( - - - - )} - - {!!description && shouldShowDescriptionOnTop && ( - - {description} - + )} - {(!!title || !!shouldShowTitleIcon) && ( + + {!!description && shouldShowDescriptionOnTop && ( + + {description} + + )} {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( @@ -599,100 +591,94 @@ function MenuItem( )} - )} - {!!description && !shouldShowDescriptionOnTop && ( - - {description} - - )} - {!!error && ( - - {error} - - )} - {!!furtherDetails && ( - - {!!furtherDetailsIcon && ( - - )} + {!!description && !shouldShowDescriptionOnTop && ( - {furtherDetails} + {description} - - )} - {titleComponent} + )} + {!!furtherDetails && ( + + {!!furtherDetailsIcon && ( + + )} + + {furtherDetails} + + + )} + - - - {badgeText && ( - - )} - {/* Since subtitle can be of type number, we should allow 0 to be shown */} - {(subtitle === 0 || subtitle) && ( - - {subtitle} - - )} - {floatRightAvatars?.length > 0 && ( - - {shouldShowSubscriptRightAvatar ? ( - + {badgeText && ( + + )} + {/* Since subtitle can be of type number, we should allow 0 to be shown */} + {(subtitle === 0 || subtitle) && ( + + {subtitle} + + )} + {floatRightAvatars?.length > 0 && ( + + {shouldShowSubscriptRightAvatar ? ( + + ) : ( + + )} + + )} + {!!brickRoadIndicator && ( + + - ) : ( - + )} + {!title && !!rightLabel && !errorText && ( + + {rightLabel} + + )} + {shouldShowRightIcon && ( + + - )} - - )} - {!!brickRoadIndicator && ( - - - - )} - {!title && !!rightLabel && ( - - {rightLabel} - - )} - {shouldShowRightIcon && ( - - - - )} - {shouldShowRightComponent && rightComponent} - {shouldShowSelectedState && } + + )} + {shouldShowRightComponent && rightComponent} + {shouldShowSelectedState && } + {!!errorText && ( )} - + {!!hintText && ( + + )} + )} )} From 9318abb836e0485366e961326e9ec9d540b79d05 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 22:31:12 +0530 Subject: [PATCH 036/219] merge main --- src/components/MenuItem.tsx | 76 ++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index ad06f9fcb78c..61f29f44b00c 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,6 +1,6 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import type {ImageContentFit} from 'expo-image'; -import type {ReactNode} from 'react'; +import type {ReactElement, ReactNode} from 'react'; import React, {forwardRef, useContext, useMemo} from 'react'; import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; @@ -105,6 +105,8 @@ type MenuItemBaseProps = { /** The fill color to pass into the secondary icon. */ secondaryIconFill?: string; + isSecondaryIconHoverable?: boolean; + /** Icon Width */ iconWidth?: number; @@ -176,6 +178,12 @@ type MenuItemBaseProps = { /** Text to display for the item */ title?: string; + /** Component to display as the title */ + titleComponent?: ReactElement; + + /** Any additional styles to apply to the container for title components */ + titleContainerStyle?: StyleProp; + /** A right-aligned subtitle for this menu option */ subtitle?: string | number; @@ -294,6 +302,7 @@ function MenuItem( secondaryIcon, secondaryIconFill, iconType = CONST.ICON_TYPE_ICON, + isSecondaryIconHoverable = false, iconWidth, iconHeight, iconStyles, @@ -313,6 +322,8 @@ function MenuItem( focused = false, disabled = false, title, + titleComponent, + titleContainerStyle, subtitle, shouldShowBasicTitle, label, @@ -545,7 +556,13 @@ function MenuItem( )} {secondaryIcon && ( - + )} - + {!!description && shouldShowDescriptionOnTop && ( )} - - {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( - - - - )} - {!shouldRenderAsHTML && !shouldParseTitle && !!title && ( - - {renderTitleContent()} - - )} - {shouldShowTitleIcon && titleIcon && ( - - - - )} - + {(!!title || !!shouldShowTitleIcon) && ( + + {!!title && (shouldRenderAsHTML || (shouldParseTitle && !!html.length)) && ( + + + + )} + {!shouldRenderAsHTML && !shouldParseTitle && !!title && ( + + {renderTitleContent()} + + )} + {shouldShowTitleIcon && titleIcon && ( + + + + )} + + )} {!!description && !shouldShowDescriptionOnTop && ( )} + {titleComponent} From e6b529645c428e4bf4546b611674d16cef29e1d6 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 23:23:49 +0530 Subject: [PATCH 037/219] update trip report type --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 4472c7732195..733e55967190 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -703,6 +703,7 @@ const CONST = { TASK_COMPLETED: 'TASKCOMPLETED', TASK_EDITED: 'TASKEDITED', TASK_REOPENED: 'TASKREOPENED', + TRIPPREVIEW: 'TRIPPREVIEW', UNAPPROVED: 'UNAPPROVED', // OldDot Action UNHOLD: 'UNHOLD', UNSHARE: 'UNSHARE', // OldDot Action From 548f81d20d04a6b29654f4d2eda18242970b251d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 23:24:23 +0530 Subject: [PATCH 038/219] show trip preview and check for trip room --- src/pages/home/report/ReportActionItem.tsx | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 423364f3837c..a3242b3f25a8 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -774,6 +774,21 @@ function ReportActionItem({ return {content}; }; + console.log('action.actionName', action.actionName); + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW) { + console.log('Hello'); + if (ReportUtils.isTripRoom(report)) { + return ( + + + + ); + } + } + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { if (ReportActionsUtils.isTransactionThread(parentReportAction)) { const isReversedTransaction = ReportActionsUtils.isReversedTransaction(parentReportAction); @@ -845,17 +860,6 @@ function ReportActionItem({ ); } - if (ReportUtils.isTripRoom(report)) { - return ( - - - - ); - } - if (ReportUtils.isExpenseReport(report) || ReportUtils.isIOUReport(report) || ReportUtils.isInvoiceReport(report)) { return ( From d78fe79843d5ce33bd6703e779fdb5fc386c018a Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 23:24:43 +0530 Subject: [PATCH 039/219] onyx add trip preview type --- src/types/onyx/OriginalMessage.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index b079a64ebb4b..b9b6f08a8b8e 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -27,6 +27,7 @@ type OriginalMessageActionName = | 'ACTIONABLEMENTIONWHISPER' | 'ACTIONABLEREPORTMENTIONWHISPER' | 'ACTIONABLETRACKEXPENSEWHISPER' + | 'TRIPPREVIEW' | ValueOf; type OriginalMessageApproved = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED; @@ -342,6 +343,13 @@ type OriginalMessageDismissedViolation = { }; }; +type OriginalMessageTripRoomPreview = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTION_TRIPPREVIEW; + originalMessage: { + // @TODO: Add types here + }; +}; + type OriginalMessage = | OriginalMessageApproved | OriginalMessageIOU @@ -366,6 +374,7 @@ type OriginalMessage = | OriginalMessageReimbursementDequeued | OriginalMessageMoved | OriginalMessageMarkedReimbursed + | OriginalMessageTripRoomPreview | OriginalMessageActionableTrackedExpenseWhisper | OriginalMessageMergedWithCashTransaction | OriginalMessageDismissedViolation; From c155df816606fe89290a808e7f7ed45d297612ae Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Wed, 15 May 2024 23:25:26 +0530 Subject: [PATCH 040/219] rm console log --- src/pages/home/report/ReportActionItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a3242b3f25a8..c1c80dc8f5f8 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -774,9 +774,7 @@ function ReportActionItem({ return {content}; }; - console.log('action.actionName', action.actionName); if (action.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW) { - console.log('Hello'); if (ReportUtils.isTripRoom(report)) { return ( From 3e4d438e8df6382d0e5bc08eebcfde3dc41d719f Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Thu, 16 May 2024 08:47:12 +0530 Subject: [PATCH 041/219] add trip header and trip transactins --- src/libs/ReportUtils.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8bfacc78fdb7..6aa592c5b6d2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3187,6 +3187,11 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu if (ReportActionsUtils.isModifiedExpenseAction(parentReportAction)) { return ModifiedExpenseMessage.getForReportAction(report?.reportID, parentReportAction); } + + if (isTripRoom(report)) { + return report?.reportName ?? ''; + } + return parentReportActionMessage; } @@ -6516,6 +6521,12 @@ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry return !existingIOUReport || hasIOUWaitingOnCurrentUserBankAccount(chatReport) || !canAddOrDeleteTransactions(existingIOUReport); } +function getTripTransactions(expenseReportID: string | undefined): Transaction[] { + const transactions = TransactionUtils.getAllReportTransactions(expenseReportID); + console.log('transactions: ', transactions); + return transactions.filter((transaction) => TransactionUtils.hasReservationList(transaction)); +} + /** * Checks if report contains actions with errors */ @@ -6947,6 +6958,7 @@ export { updateOptimisticParentReportAction, updateReportPreview, temporary_getMoneyRequestOptions, + getTripTransactions, buildOptimisticInvoiceReport, getInvoiceChatByParticipants, shouldShowMerchantColumn, From 075868d8d3ac4c16cc9f7ce3689b6ed58d00e87b Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 17 May 2024 12:24:31 +0530 Subject: [PATCH 042/219] minor updates and refactoring. Signed-off-by: Krishna Gupta --- src/components/PDFThumbnail/index.native.tsx | 25 +++++------------- src/components/PDFThumbnail/index.tsx | 27 +++++--------------- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index 7c4709e45435..27d41ede3263 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -2,24 +2,20 @@ import React, {useState} from 'react'; import {View} from 'react-native'; import Pdf from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; -import variables from '@styles/variables'; +import PDFThumbnailError from './PDFThumbnailError'; import type PDFThumbnailProps from './types'; function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError}: PDFThumbnailProps) { const styles = useThemeStyles(); - const theme = useTheme(); const sizeStyles = [styles.w100, styles.h100]; - const [hasError, setHasError] = useState(false); + const [failedToLoad, setFailedToLoad] = useState(false); return ( - - {enabled && !hasError && ( + + {enabled && !failedToLoad && ( )} - {hasError && ( - - - - )} + {failedToLoad && } ); diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index f4dc4727657a..99ae95f34c6b 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -3,12 +3,9 @@ import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {Document, pdfjs, Thumbnail} from 'react-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; -import variables from '@styles/variables'; +import PDFThumbnailError from './PDFThumbnailError'; import type PDFThumbnailProps from './types'; if (!pdfjs.GlobalWorkerOptions.workerSrc) { @@ -17,8 +14,7 @@ if (!pdfjs.GlobalWorkerOptions.workerSrc) { function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError}: PDFThumbnailProps) { const styles = useThemeStyles(); - const theme = useTheme(); - const [hasError, setHasError] = useState(false); + const [failedToLoad, setFailedToLoad] = useState(false); const thumbnail = useMemo( () => ( @@ -32,13 +28,13 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena externalLinkTarget="_blank" onPassword={onPassword} onLoad={() => { - setHasError(false); + setFailedToLoad(false); }} onLoadError={() => { if (onLoadError) { onLoadError(); } - setHasError(true); + setFailedToLoad(true); }} error={() => null} > @@ -52,18 +48,9 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena return ( - - {enabled && thumbnail} - {hasError && ( - - - - )} + + {enabled && failedToLoad && thumbnail} + {failedToLoad && } ); From dc778d7c880028f187b6fc36a8908ecf538989e6 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 17 May 2024 12:25:19 +0530 Subject: [PATCH 043/219] add PDFThumbnailError component. Signed-off-by: Krishna Gupta --- .../PDFThumbnail/PDFThumbnailError.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/components/PDFThumbnail/PDFThumbnailError.tsx diff --git a/src/components/PDFThumbnail/PDFThumbnailError.tsx b/src/components/PDFThumbnail/PDFThumbnailError.tsx new file mode 100644 index 000000000000..0598a995e030 --- /dev/null +++ b/src/components/PDFThumbnail/PDFThumbnailError.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; + +function PDFThumbnailError() { + const styles = useThemeStyles(); + const theme = useTheme(); + + return ( + + + + ); +} + +export default PDFThumbnailError; From 1d151298a1a2ba5d1180151f05bfbd8838d72fba Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Fri, 17 May 2024 21:41:52 +0530 Subject: [PATCH 044/219] working report --- src/components/ReportActionItem/TripDetailsView.tsx | 9 +++++---- src/libs/TransactionUtils.ts | 5 +++++ src/pages/home/report/ReportActionItem.tsx | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index 8309f0ac3feb..bc8328ad22a7 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -185,10 +185,11 @@ function TripDetailsView({iouReportID, shouldShowHorizontalRule}: TripDetailsVie const {translate} = useLocalize(); // TODO: once backend is ready uncomment lines below and remove test data - const reservations = testReservationsList; - // const tripTransactions = ReportUtils.getTripTransactions(iouReportID); - - // const reservations: Reservation[] = TripReservationUtils.getReservationsFromTripTransactions(tripTransactions); + // const reservations = testReservationsList; + const tripTransactions = ReportUtils.getTripTransactions(iouReportID); + console.log('reportId: ', iouReportID); + console.log('**', tripTransactions); + const reservations: Reservation[] = TripReservationUtils.getReservationsFromTripTransactions(tripTransactions); return ( diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index aa9a87404924..1cf03b88cc65 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -647,6 +647,10 @@ function isCustomUnitRateIDForP2P(transaction: OnyxEntry): boolean return transaction?.comment?.customUnit?.customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; } +function hasReservationList(transaction: Transaction | undefined | null): boolean { + return !!transaction?.reservationList && transaction?.reservationList?.length > 0; +} + /** * Get rate ID from the transaction object */ @@ -766,6 +770,7 @@ export { getWaypointIndex, waypointHasValidAddress, getRecentTransactions, + hasReservationList, hasViolation, hasNoticeTypeViolation, isCustomUnitRateIDForP2P, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index c1c80dc8f5f8..993bd5993037 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -774,12 +774,13 @@ function ReportActionItem({ return {content}; }; + console.log(report); if (action.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW) { if (ReportUtils.isTripRoom(report)) { return ( From e202ba916c92d7e52eafc22888e59772d27567a1 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 19 May 2024 19:05:05 +0200 Subject: [PATCH 045/219] Refactoring --- src/components/MoneyRequestAmountInput.tsx | 5 ++--- src/pages/iou/MoneyRequestAmountForm.tsx | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index d7cb10cb2f23..22336f8c3a78 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -108,7 +108,6 @@ function MoneyRequestAmountInput( maxLength, hideFocusedState = true, shouldKeepUserInput = false, - ...props }: MoneyRequestAmountInputProps, forwardedRef: ForwardedRef, @@ -118,7 +117,7 @@ function MoneyRequestAmountInput( const textInput = useRef(null); const decimals = CurrencyUtils.getCurrencyDecimals(currency); - const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmountAsString(amount).toString() : ''; + const selectedAmountAsString = CurrencyUtils.convertToFrontendAmountAsString(amount); const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); @@ -187,7 +186,7 @@ function MoneyRequestAmountInput( if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput) { return; } - const frontendAmount = formatAmountOnBlur ? CurrencyUtils.convertToDisplayStringWithoutCurrency(amount, currency) : CurrencyUtils.convertToFrontendAmountAsString(amount).toString(); + const frontendAmount = formatAmountOnBlur ? CurrencyUtils.convertToDisplayStringWithoutCurrency(amount, currency) : CurrencyUtils.convertToFrontendAmountAsString(amount); setCurrentAmount(frontendAmount); // Only update selection if the amount prop was changed from the outside and is not the same as the current amount we just computed diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index b3b00ab37bf6..5bbc9d22a97f 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -148,7 +148,7 @@ function MoneyRequestAmountForm( }, [isFocused, wasFocused]); const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount).toString() : ''; + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount) : ''; moneyRequestAmountInput.current?.changeAmount(frontendAmount); moneyRequestAmountInput.current?.changeSelection({ start: frontendAmount.length, From da2b2f4a0533353c3f542d2ccdfd8abbb7e2eda7 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:27:04 +0530 Subject: [PATCH 046/219] get trip transactions --- src/libs/ReportUtils.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6aa592c5b6d2..fd62a9b4e7a1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6521,10 +6521,9 @@ function shouldCreateNewMoneyRequestReport(existingIOUReport: OnyxEntry return !existingIOUReport || hasIOUWaitingOnCurrentUserBankAccount(chatReport) || !canAddOrDeleteTransactions(existingIOUReport); } -function getTripTransactions(expenseReportID: string | undefined): Transaction[] { - const transactions = TransactionUtils.getAllReportTransactions(expenseReportID); - console.log('transactions: ', transactions); - return transactions.filter((transaction) => TransactionUtils.hasReservationList(transaction)); +function getTripTransactions(tripRoomReportID: string | undefined): Transaction[] | undefined { + const tripTransactionReportIDs = Object.values(allReports ?? {}).filter((report) => report && report?.parentReportID === tripRoomReportID); + return Object.values(tripTransactionReportIDs ?? {}).map((report) => report && TransactionUtils.getAllReportTransactions(report?.reportID)); } /** From 1812e1b1958a244f7a5b85913dfa5213f48565d8 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:27:22 +0530 Subject: [PATCH 047/219] get trip reservations method --- src/libs/TripReservationUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts index c0b1724d4835..b525ccd520fe 100644 --- a/src/libs/TripReservationUtils.ts +++ b/src/libs/TripReservationUtils.ts @@ -23,7 +23,8 @@ function getTripReservationIcon(reservationType: ReservationType): IconAsset { function getReservationsFromTripTransactions(transactions: Transaction[]): Reservation[] { return transactions - .map((item) => item?.reservationList ?? []) + .flat() + .map((item) => item?.receipt?.reservationList ?? []) .filter((item) => item.length > 0) .flat(); } From 0229a52f71c452337dbe7a43a701adc70d770642 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:27:34 +0530 Subject: [PATCH 048/219] rm console --- src/pages/home/report/ReportActionItem.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 993bd5993037..fb06018c9e74 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -774,13 +774,12 @@ function ReportActionItem({ return {content}; }; - console.log(report); if (action.actionName === CONST.REPORT.ACTIONS.TYPE.TRIPPREVIEW) { if (ReportUtils.isTripRoom(report)) { return ( From 876c40320157983ad1652e561002010ed297315b Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:27:49 +0530 Subject: [PATCH 049/219] add reservation to receipt --- src/types/onyx/Transaction.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 3e3291cab4ea..11b0d9a4fa45 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -79,6 +79,7 @@ type Receipt = { filename?: string; state?: ValueOf; type?: string; + reservationList?: Reservation[]; }; type Route = { @@ -107,7 +108,6 @@ type TaxRate = { data?: TaxRateData; }; - type Fare = { amount: number; convertedAmount: number; @@ -140,7 +140,8 @@ type Reservation = { numPassengers?: number; numberOfRooms?: number; route?: { - class: string; + airlineCode: string; + class?: string; number: string; }; }; @@ -150,7 +151,7 @@ type ReservationTimeDetails = { address?: string; longName?: string; shortName?: string; - timezoneOffset?: number; + timezoneOffset?: string; }; type Company = { @@ -291,10 +292,6 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates transaction loading */ isLoading?: boolean; - - /** Travel reserviation list */ - reservationList?: Reservation[]; - originalSpotnanaPayload?: SpotnanaPayload; /** Holds individual shares of a split keyed by accountID, only used locally */ @@ -303,7 +300,6 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Holds the accountIDs of accounts who paid the split, for now only supports a single payer */ splitPayerAccountIDs?: number[]; - /** The actionable report action ID associated with the transaction */ actionableWhisperReportActionID?: string; From 658ba4b4b9be2c47504c7753681b59ec1d72b82d Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:28:46 +0530 Subject: [PATCH 050/219] add reservation to receipt --- .../ReportActionItem/TripDetailsView.tsx | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index bc8328ad22a7..a7f34be668bf 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import Onyx from 'react-native-onyx'; import Icon from '@components/Icon'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -91,8 +92,8 @@ const testReservationsList: Reservation[] = [ ]; type TripDetailsViewProps = { - /** The active IOUReport, used for Onyx subscription */ - iouReportID?: string; + /** The active tripRoomReportID, used for Onyx subscription */ + tripRoomReportID?: string; /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: boolean; @@ -108,14 +109,15 @@ function ReservationView({reservation}: ReservationViewProps) { const reservationIcon = TripReservationUtils.getTripReservationIcon(reservation.type); - const formatAirportInfo = (reservationTimeDetails: ReservationTimeDetails) => `${reservationTimeDetails.longName} (${reservationTimeDetails.shortName})`; - + const formatAirportInfo = (reservationTimeDetails: ReservationTimeDetails) => + `${reservationTimeDetails?.longName ? `${reservationTimeDetails?.longName} ` : ''}${reservationTimeDetails?.shortName}`; const getFormattedDate = () => { switch (reservation.type) { case CONST.RESERVATION_TYPE.FLIGHT: case CONST.RESERVATION_TYPE.RAIL: return DateUtils.getFormattedTransportDate(new Date(reservation.start.date)); case CONST.RESERVATION_TYPE.HOTEL: + case CONST.RESERVATION_TYPE.CAR: return DateUtils.getFormattedReservationRangeDate(new Date(reservation.start.date), new Date(reservation.end.date)); default: return DateUtils.formatToLongDateWithWeekday(new Date(reservation.start.date)); @@ -178,17 +180,41 @@ function ReservationView({reservation}: ReservationViewProps) { ); } -function TripDetailsView({iouReportID, shouldShowHorizontalRule}: TripDetailsViewProps) { +function TripDetailsView({tripRoomReportID, shouldShowHorizontalRule}: TripDetailsViewProps) { const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); const styles = useThemeStyles(); const {translate} = useLocalize(); // TODO: once backend is ready uncomment lines below and remove test data - // const reservations = testReservationsList; - const tripTransactions = ReportUtils.getTripTransactions(iouReportID); - console.log('reportId: ', iouReportID); - console.log('**', tripTransactions); + Onyx.merge('transactions_3369516381858695612', { + receipt: {reservationList: [{end: {date: '2024-05-24T19:00:00'}, reservationID: '1174780760', start: {date: '2024-05-23T19:00:00'}, type: 'car'}]}, + }); + + Onyx.merge('transactions_2415242883159099811', { + receipt: { + reservationList: [ + { + company: {longName: 'Gulf Air', phone: '', shortName: 'GF'}, + confirmations: [{name: 'Confirmation Number', value: 'QXHLBH'}], + end: {date: '2024-05-23T07:30:00', shortName: 'BAH', timezoneOffset: ''}, + route: {airlineCode: 'GF57', number: '57'}, + start: {date: '2024-05-23T06:30:00', shortName: 'BOM', timezoneOffset: ''}, + type: 'flight', + }, + { + company: {longName: 'Gulf Air', phone: '', shortName: 'GF'}, + confirmations: [{name: 'Confirmation Number', value: 'QXHLBH'}], + end: {date: '2024-05-23T20:55:00', shortName: 'BOM', timezoneOffset: ''}, + route: {airlineCode: 'GF64', number: '64'}, + start: {date: '2024-05-23T14:30:00', shortName: 'BAH', timezoneOffset: ''}, + type: 'flight', + }, + ], + }, + }); + + const tripTransactions = ReportUtils.getTripTransactions(tripRoomReportID); const reservations: Reservation[] = TripReservationUtils.getReservationsFromTripTransactions(tripTransactions); return ( From 854ac5b1c36cf2b53c5a54aa351922cc8623bf26 Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:29:15 +0530 Subject: [PATCH 051/219] add reservation details --- .../ReportActionItem/TripDetailsView.tsx | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx index a7f34be668bf..31d93c43be44 100644 --- a/src/components/ReportActionItem/TripDetailsView.tsx +++ b/src/components/ReportActionItem/TripDetailsView.tsx @@ -20,77 +20,6 @@ import * as ReportUtils from '@src/libs/ReportUtils'; import * as TripReservationUtils from '@src/libs/TripReservationUtils'; import type {Reservation, ReservationTimeDetails} from '@src/types/onyx/Transaction'; -// TODO: to be removed once backend is ready -const testReservationsList: Reservation[] = [ - { - company: { - longName: 'American Airlines', - shortName: 'AA', - }, - confirmations: [ - { - name: 'Confirmation Number', - value: 'DDPNOF', - }, - ], - start: { - address: 'AA Address', - date: '2022-08-21 21:36', - longName: 'Philadelphia', - shortName: 'PHL', - timezoneOffset: -360, - }, - end: { - address: 'BB Address', - date: '2022-11-10 12:36', - longName: 'San Francisco', - shortName: 'SFO', - timezoneOffset: -360, - }, - numPassengers: 2, - route: { - class: '', - number: '2579', - }, - type: CONST.RESERVATION_TYPE.FLIGHT, - }, - { - company: { - longName: 'W San Francisco', - }, - confirmations: [ - { - name: 'Booking Number', - value: 'SUDMBE', - }, - { - name: 'Confirmation Number', - value: 'GGGGGGG-HHHHHH-IIIIII', - }, - ], - start: { - address: '181 3rd St, San Francisco, CA 94103', - date: '2023-01-22 21:40', - longName: 'SFO123', - shortName: 'SFO', - timezoneOffset: -420, - }, - end: { - address: 'DD Address', - date: '2023-02-10 12:00', - longName: 'Denver-Denver Intl', - shortName: 'DEN', - timezoneOffset: -420, - }, - numberOfRooms: 3, - route: { - class: '', - number: '46564', - }, - type: CONST.RESERVATION_TYPE.HOTEL, - }, -]; - type TripDetailsViewProps = { /** The active tripRoomReportID, used for Onyx subscription */ tripRoomReportID?: string; From 97b1d61605d1b8938539d0d7649180883bfd336e Mon Sep 17 00:00:00 2001 From: Rushat Gabhane Date: Mon, 20 May 2024 12:31:33 +0530 Subject: [PATCH 052/219] hide horizontal rule --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index fb06018c9e74..21c87f25155f 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -780,7 +780,7 @@ function ReportActionItem({ ); From 1f93a1ee1db81f5b41e6dd55b4df586d8c7db432 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 20 May 2024 13:20:32 +0530 Subject: [PATCH 053/219] fix styling issues. Signed-off-by: Krishna Gupta --- src/components/PDFThumbnail/index.tsx | 4 ++-- src/styles/index.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index 99ae95f34c6b..8e79c027cf03 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -47,9 +47,9 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena ); return ( - + - {enabled && failedToLoad && thumbnail} + {enabled && !failedToLoad && thumbnail} {failedToLoad && } diff --git a/src/styles/index.ts b/src/styles/index.ts index 97d491790c96..6c69e12f34d0 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4225,7 +4225,6 @@ const styles = (theme: ThemeColors) => borderRadius: 16, margin: 20, overflow: 'hidden', - justifyContent: 'center', }, reportPreviewBox: { From 5495b7a3f5101230d2bd744b196f005a981d2112 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 20 May 2024 13:36:21 +0530 Subject: [PATCH 054/219] update padding horizontal of workspace section header. Signed-off-by: Krishna Gupta --- src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx | 2 +- src/pages/WorkspaceSwitcherPage/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx b/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx index 044ded08e35d..36e24036d365 100644 --- a/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx +++ b/src/pages/WorkspaceSwitcherPage/WorkspacesSectionHeader.tsx @@ -19,7 +19,7 @@ function WorkspacesSectionHeader() { const {translate} = useLocalize(); return ( - + - + Date: Tue, 21 May 2024 11:11:12 +0700 Subject: [PATCH 055/219] remove includePersonalDetails --- src/libs/OptionsListUtils.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 223a7fa1a832..eb6233d004f5 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -141,7 +141,6 @@ type GetOptionsConfig = { maxRecentReportsToShow?: number; excludeLogins?: string[]; includeMultipleParticipantReports?: boolean; - includePersonalDetails?: boolean; includeRecentReports?: boolean; includeSelfDM?: boolean; sortByReportTypeInSearch?: boolean; @@ -1648,7 +1647,6 @@ function getOptions( maxRecentReportsToShow = 0, excludeLogins = [], includeMultipleParticipantReports = false, - includePersonalDetails = false, includeRecentReports = false, // When sortByReportTypeInSearch flag is true, recentReports will include the personalDetails options as well. sortByReportTypeInSearch = false, @@ -1917,7 +1915,7 @@ function getOptions( } } - if (includePersonalDetails) { + if (includeP2P) { const personalDetailsOptionsToExclude = [...optionsToExclude, {login: currentUserLogin}]; // Next loop over all personal details removing any that are selectedUsers or recentChats allPersonalDetailsOptions.forEach((personalDetailOption) => { @@ -1990,7 +1988,7 @@ function getSearchOptions(options: OptionList, searchValue = '', betas: Beta[] = maxRecentReportsToShow: 0, // Unlimited sortByReportTypeInSearch: true, showChatPreviewLine: true, - includePersonalDetails: true, + includeP2P: true, forcePolicyNamePreview: true, includeOwnedWorkspaceChats: true, includeThreads: true, @@ -2011,7 +2009,7 @@ function getShareLogOptions(options: OptionList, searchValue = '', betas: Beta[] includeRecentReports: true, includeMultipleParticipantReports: true, sortByReportTypeInSearch: true, - includePersonalDetails: true, + includeP2P: true, forcePolicyNamePreview: true, includeOwnedWorkspaceChats: true, includeSelfDM: true, @@ -2068,7 +2066,6 @@ function getFilteredOptions( includePolicyReportFieldOptions = false, policyReportFieldOptions: string[] = [], recentlyUsedPolicyReportFieldOptions: string[] = [], - includePersonalDetails = true, maxRecentReportsToShow = 5, ) { return getOptions( @@ -2078,7 +2075,6 @@ function getFilteredOptions( searchInputValue: searchValue.trim(), selectedOptions, includeRecentReports: true, - includePersonalDetails, maxRecentReportsToShow, excludeLogins, includeOwnedWorkspaceChats, @@ -2124,7 +2120,7 @@ function getShareDestinationOptions( maxRecentReportsToShow: 0, // Unlimited includeRecentReports: true, includeMultipleParticipantReports: true, - includePersonalDetails: false, + includeP2P: false, showChatPreviewLine: true, forcePolicyNamePreview: true, includeThreads: true, @@ -2181,7 +2177,7 @@ function getMemberInviteOptions( { betas, searchInputValue: searchValue.trim(), - includePersonalDetails: true, + includeP2P: true, excludeLogins, sortPersonalDetailsByAlphaAsc: true, includeSelectedOptions, From 096c21201cc9d5be6a78419b020e64817bb55da2 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 21 May 2024 11:16:02 +0700 Subject: [PATCH 056/219] fix ts --- src/libs/OptionsListUtils.ts | 1 - src/pages/iou/request/MoneyRequestParticipantsSelector.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index eb6233d004f5..0fa0ed1782f9 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -2120,7 +2120,6 @@ function getShareDestinationOptions( maxRecentReportsToShow: 0, // Unlimited includeRecentReports: true, includeMultipleParticipantReports: true, - includeP2P: false, showChatPreviewLine: true, forcePolicyNamePreview: true, includeThreads: true, diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index b525a2c1e3dd..2f75c34b3e74 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -115,7 +115,6 @@ function MoneyRequestParticipantsSelector({participants = [], onFinish, onPartic undefined, undefined, undefined, - !isCategorizeOrShareAction, isCategorizeOrShareAction ? 0 : undefined, ); From 0ff20cac2eaf7c781d1cb01e7fe0506af051441c Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 21 May 2024 11:20:01 +0700 Subject: [PATCH 057/219] fix jest test --- tests/unit/OptionsListUtilsTest.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index ae8a93efda96..297ec8b00f4f 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -476,6 +476,7 @@ describe('OptionsListUtils', () => { undefined, undefined, undefined, + false, undefined, undefined, undefined, @@ -490,8 +491,6 @@ describe('OptionsListUtils', () => { undefined, undefined, undefined, - undefined, - false, ); // Then no personal detail options will be returned From da3c4683dd0cb913f29590b9b2da6140015106f7 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 22 May 2024 10:05:21 +0700 Subject: [PATCH 058/219] fix: Error message only changes after clicking Split expense button for the second time --- .../MoneyRequestConfirmationList.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 33f1258e60c7..f40998410962 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -708,16 +708,7 @@ function MoneyRequestConfirmationList({ return; } - if (iouType === CONST.IOU.TYPE.PAY) { - if (!paymentMethod) { - return; - } - - setDidConfirm(true); - - Log.info(`[IOU] Sending money via: ${paymentMethod}`); - onSendMoney?.(paymentMethod); - } else { + if (iouType !== CONST.IOU.TYPE.PAY) { // validate the amount for distance expenses const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); if (isDistanceRequest && !isDistanceRequestWithPendingRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { @@ -730,7 +721,18 @@ function MoneyRequestConfirmationList({ setFormError('iou.error.genericSmartscanFailureMessage'); return; } + } + if (iouType === CONST.IOU.TYPE.PAY) { + if (!paymentMethod) { + return; + } + + setDidConfirm(true); + + Log.info(`[IOU] Sending money via: ${paymentMethod}`); + onSendMoney?.(paymentMethod); + } else { playSound(SOUNDS.DONE); setDidConfirm(true); onConfirm?.(selectedParticipants); From 556066827a66f0149d37e6f098d23eafeb81d2d2 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 22 May 2024 10:35:01 +0700 Subject: [PATCH 059/219] fix logic show money request form error --- .../MoneyRequestConfirmationList.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index f40998410962..0c5eb26c48e7 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -346,14 +346,15 @@ function MoneyRequestConfirmationList({ const isCategoryRequired = !!policy?.requiresCategory; useEffect(() => { - if (shouldDisplayFieldError && hasSmartScanFailed) { - setFormError('iou.receiptScanningFailed'); - return; - } if (shouldDisplayFieldError && didConfirmSplit) { setFormError('iou.error.genericSmartscanFailureMessage'); return; } + + if (shouldDisplayFieldError && hasSmartScanFailed) { + setFormError('iou.receiptScanningFailed'); + return; + } // reset the form error whenever the screen gains or loses focus setFormError(''); @@ -703,11 +704,6 @@ function MoneyRequestConfirmationList({ setFormError('iou.error.invalidCategoryLength'); return; } - - if (formError) { - return; - } - if (iouType !== CONST.IOU.TYPE.PAY) { // validate the amount for distance expenses const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); @@ -721,6 +717,14 @@ function MoneyRequestConfirmationList({ setFormError('iou.error.genericSmartscanFailureMessage'); return; } + + playSound(SOUNDS.DONE); + setDidConfirm(true); + onConfirm?.(selectedParticipants); + } + + if (formError) { + return; } if (iouType === CONST.IOU.TYPE.PAY) { @@ -732,10 +736,6 @@ function MoneyRequestConfirmationList({ Log.info(`[IOU] Sending money via: ${paymentMethod}`); onSendMoney?.(paymentMethod); - } else { - playSound(SOUNDS.DONE); - setDidConfirm(true); - onConfirm?.(selectedParticipants); } }, [ From ecf84585593b826c875897eee4f10ca617778b30 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 22 May 2024 17:11:35 +0700 Subject: [PATCH 060/219] remove unnecessary condition --- src/libs/OptionsListUtils.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 0fa0ed1782f9..d59c60848c5a 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1915,22 +1915,20 @@ function getOptions( } } - if (includeP2P) { - const personalDetailsOptionsToExclude = [...optionsToExclude, {login: currentUserLogin}]; - // Next loop over all personal details removing any that are selectedUsers or recentChats - allPersonalDetailsOptions.forEach((personalDetailOption) => { - if (personalDetailsOptionsToExclude.some((optionToExclude) => optionToExclude.login === personalDetailOption.login)) { - return; - } - const {searchText, participantsList, isChatRoom} = personalDetailOption; - const participantNames = getParticipantNames(participantsList); - if (searchValue && !isSearchStringMatch(searchValue, searchText, participantNames, isChatRoom)) { - return; - } + const personalDetailsOptionsToExclude = [...optionsToExclude, {login: currentUserLogin}]; + // Next loop over all personal details removing any that are selectedUsers or recentChats + allPersonalDetailsOptions.forEach((personalDetailOption) => { + if (personalDetailsOptionsToExclude.some((optionToExclude) => optionToExclude.login === personalDetailOption.login)) { + return; + } + const {searchText, participantsList, isChatRoom} = personalDetailOption; + const participantNames = getParticipantNames(participantsList); + if (searchValue && !isSearchStringMatch(searchValue, searchText, participantNames, isChatRoom)) { + return; + } - personalDetailsOptions.push(personalDetailOption); - }); - } + personalDetailsOptions.push(personalDetailOption); + }); let currentUserOption = allPersonalDetailsOptions.find((personalDetailsOption) => personalDetailsOption.login === currentUserLogin); if (searchValue && currentUserOption && !isSearchStringMatch(searchValue, currentUserOption.searchText)) { From 1e4fda541df3585d960f2b3775d0f4f73752e13d Mon Sep 17 00:00:00 2001 From: Ren Jones <153645623+ren-jones@users.noreply.github.com> Date: Wed, 22 May 2024 10:54:20 -0500 Subject: [PATCH 061/219] Update docs/articles/new-expensify/expenses/Set-up-your-wallet.md Fixing numbering Co-authored-by: Rushat Gabhane --- docs/articles/new-expensify/expenses/Set-up-your-wallet.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md index 93658eb484c8..aa584a9c4eab 100644 --- a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md +++ b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md @@ -36,6 +36,7 @@ When your ID is uploaded successfully, Onfido closes automatically. You can retu Plaid is an encrypted third-party financial data platform that Expensify uses to securely verify your banking information. {% include end-info.html %} +{:start="5"} 5. Enter your personal details (including your name, address, date of birth, phone number, and the last 4 digits of your social security number). 6. Tap **Save & continue**. 7. Review the Onfido terms and tap **Accept**. From 2d80900dddd46540d807d8138f1be571ee31ac37 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 22 May 2024 16:31:27 -0700 Subject: [PATCH 062/219] Hook to track when a value changes based on a deep comparison --- src/hooks/useDeepCompare.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/hooks/useDeepCompare.ts diff --git a/src/hooks/useDeepCompare.ts b/src/hooks/useDeepCompare.ts new file mode 100644 index 000000000000..27b7155079ef --- /dev/null +++ b/src/hooks/useDeepCompare.ts @@ -0,0 +1,11 @@ +// useDeepCompare.ts +import { useRef } from 'react'; +import isEqual from 'lodash/isEqual'; + +export default function useDeepCompare(value: T): T | undefined { + const ref = useRef(); + if (!isEqual(value, ref.current)) { + ref.current = value; + } + return ref.current; +} \ No newline at end of file From c4b3d7633c12f3c07281c2b24c943d21231f4fd5 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 22 May 2024 16:32:04 -0700 Subject: [PATCH 063/219] Update the report memo when the permissions array changes --- src/pages/home/ReportScreen.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 8620d8d4866e..23e16d3bb1dd 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -52,6 +52,7 @@ import ReportActionsView from './report/ReportActionsView'; import ReportFooter from './report/ReportFooter'; import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext'; import {ActionListContext, ReactionListContext} from './ReportScreenContext'; +import useDeepCompare from '@hooks/useDeepCompare'; type ReportScreenOnyxPropsWithoutParentReportAction = { /** Get modal status */ @@ -168,6 +169,8 @@ function ReportScreen({ const isReportOpenInRHP = useIsReportOpenInRHP(); const {isSmallScreenWidth} = useWindowDimensions(); const shouldUseNarrowLayout = isSmallScreenWidth || isReportOpenInRHP; + const permissions = useDeepCompare(reportProp?.permissions); + /** * Create a lightweight Report so as to keep the re-rendering as light as possible by * passing in only the required props. @@ -215,7 +218,7 @@ function ReportScreen({ isOptimisticReport: reportProp?.isOptimisticReport, lastMentionedTime: reportProp?.lastMentionedTime, avatarUrl: reportProp?.avatarUrl, - permissions: reportProp?.permissions, + permissions, invoiceReceiver: reportProp?.invoiceReceiver, }), [ @@ -256,7 +259,7 @@ function ReportScreen({ reportProp?.isOptimisticReport, reportProp?.lastMentionedTime, reportProp?.avatarUrl, - reportProp?.permissions, + permissions, reportProp?.invoiceReceiver, ], ); From c8f678b66d4ae5e39489a0eb6f044d0072dd6c46 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 22 May 2024 16:36:47 -0700 Subject: [PATCH 064/219] Add a helpful comment --- src/hooks/useDeepCompare.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/hooks/useDeepCompare.ts b/src/hooks/useDeepCompare.ts index 27b7155079ef..a342636eee82 100644 --- a/src/hooks/useDeepCompare.ts +++ b/src/hooks/useDeepCompare.ts @@ -1,7 +1,20 @@ -// useDeepCompare.ts import { useRef } from 'react'; import isEqual from 'lodash/isEqual'; +/** + * This hook returns a reference to the provided value, + * but only updates that reference if a deep comparison indicates that the value has changed. + * + * This is useful when working with objects or arrays as dependencies to other hooks like `useEffect` or `useMemo`, + * where you want the hook to trigger not just on reference changes, but also when the contents of the object or array change. + * + * @example + * const myArray = // some array + * const deepComparedArray = useDeepCompare(myArray); + * useEffect(() => { + * // This will run not just when myArray is a new array, but also when its contents change. + * }, [deepComparedArray]); + */ export default function useDeepCompare(value: T): T | undefined { const ref = useRef(); if (!isEqual(value, ref.current)) { From 4f772dd7e2bc957ee0e4a7557931ecb5ecd553c4 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 22 May 2024 16:38:40 -0700 Subject: [PATCH 065/219] More clear name since hook returns a ref --- src/hooks/{useDeepCompare.ts => useDeepCompareRef.ts} | 4 ++-- src/pages/home/ReportScreen.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/hooks/{useDeepCompare.ts => useDeepCompareRef.ts} (85%) diff --git a/src/hooks/useDeepCompare.ts b/src/hooks/useDeepCompareRef.ts similarity index 85% rename from src/hooks/useDeepCompare.ts rename to src/hooks/useDeepCompareRef.ts index a342636eee82..4869e461d919 100644 --- a/src/hooks/useDeepCompare.ts +++ b/src/hooks/useDeepCompareRef.ts @@ -10,12 +10,12 @@ import isEqual from 'lodash/isEqual'; * * @example * const myArray = // some array - * const deepComparedArray = useDeepCompare(myArray); + * const deepComparedArray = useDeepCompareRef(myArray); * useEffect(() => { * // This will run not just when myArray is a new array, but also when its contents change. * }, [deepComparedArray]); */ -export default function useDeepCompare(value: T): T | undefined { +export default function useDeepCompareRef(value: T): T | undefined { const ref = useRef(); if (!isEqual(value, ref.current)) { ref.current = value; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 23e16d3bb1dd..06bee0a6a4ee 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -52,7 +52,7 @@ import ReportActionsView from './report/ReportActionsView'; import ReportFooter from './report/ReportFooter'; import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext'; import {ActionListContext, ReactionListContext} from './ReportScreenContext'; -import useDeepCompare from '@hooks/useDeepCompare'; +import useDeepCompareRef from '@hooks/useDeepCompareRef'; type ReportScreenOnyxPropsWithoutParentReportAction = { /** Get modal status */ @@ -169,7 +169,7 @@ function ReportScreen({ const isReportOpenInRHP = useIsReportOpenInRHP(); const {isSmallScreenWidth} = useWindowDimensions(); const shouldUseNarrowLayout = isSmallScreenWidth || isReportOpenInRHP; - const permissions = useDeepCompare(reportProp?.permissions); + const permissions = useDeepCompareRef(reportProp?.permissions); /** * Create a lightweight Report so as to keep the re-rendering as light as possible by From f11e7eb4cd06ccb35b4b89f137fc3d825e49039d Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Thu, 23 May 2024 10:37:31 +0700 Subject: [PATCH 066/219] update default rate --- src/libs/DistanceRequestUtils.ts | 93 ++++++++++++------- src/libs/Permissions.ts | 2 +- .../step/IOURequestStepDistanceRate.tsx | 2 +- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 9cb48534214e..4104fd64259f 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -38,6 +38,48 @@ Onyx.connect({ const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter +/** + * Retrieves the mileage rates for given policy. + * + * @param policy - The policy from which to extract the mileage rates. + * + * @returns An array of mileage rates or an empty array if not found. + */ +function getMileageRates(policy: OnyxEntry, includeDisableRate: boolean = true): Record { + const mileageRates: Record = {}; + + if (!policy || !policy?.customUnits) { + return mileageRates; + } + + const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + if (!distanceUnit?.rates) { + return mileageRates; + } + + Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { + if (includeDisableRate) { + mileageRates[rateID] = { + rate: rate.rate, + currency: rate.currency, + unit: distanceUnit.attributes.unit, + name: rate.name, + customUnitRateID: rate.customUnitRateID, + } + } else if(rate.enabled) { + mileageRates[rateID] = { + rate: rate.rate, + currency: rate.currency, + unit: distanceUnit.attributes.unit, + name: rate.name, + customUnitRateID: rate.customUnitRateID, + } + } + }); + + return mileageRates; +} + /** * Retrieves the default mileage rate based on a given policy. * @@ -57,8 +99,9 @@ function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): Mileage if (!distanceUnit?.rates) { return null; } - - const distanceRate = Object.values(distanceUnit.rates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE) ?? Object.values(distanceUnit.rates)[0]; + const mileageRates = getMileageRates(policy, false) + + const distanceRate = Object.values(mileageRates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE) ?? Object.values(mileageRates)[0]; return { customUnitRateID: distanceRate.customUnitRateID, @@ -179,38 +222,6 @@ function getDistanceMerchant( return `${distanceInUnits} @ ${ratePerUnit}`; } -/** - * Retrieves the mileage rates for given policy. - * - * @param policy - The policy from which to extract the mileage rates. - * - * @returns An array of mileage rates or an empty array if not found. - */ -function getMileageRates(policy: OnyxEntry): Record { - const mileageRates: Record = {}; - - if (!policy || !policy?.customUnits) { - return mileageRates; - } - - const distanceUnit = Object.values(policy.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - if (!distanceUnit?.rates) { - return mileageRates; - } - - Object.entries(distanceUnit.rates).forEach(([rateID, rate]) => { - mileageRates[rateID] = { - rate: rate.rate, - currency: rate.currency, - unit: distanceUnit.attributes.unit, - name: rate.name, - customUnitRateID: rate.customUnitRateID, - }; - }); - - return mileageRates; -} - /** * Retrieves the rate and unit for a P2P distance expense for a given currency. * @@ -259,8 +270,20 @@ function getCustomUnitRateID(reportID: string) { let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID; + if (!policy?.customUnits) { + return null; + } + + const distanceUnit = Object.values(policy?.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); + const lastSelectedDistanceRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? '' + const lastSelectedDistanceRate = distanceUnit?.rates[lastSelectedDistanceRateID] ?? {} + if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { - customUnitRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + if (lastSelectedDistanceRate.enabled && lastSelectedDistanceRateID) { + customUnitRateID = lastSelectedDistanceRateID + } else { + customUnitRateID = getDefaultMileageRate(policy)?.customUnitRateID ?? ''; + } } return customUnitRateID; diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 45551fe1cad9..f2b34b7efa9b 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL); + return true } function canUseChronos(betas: OnyxEntry): boolean { diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 1b9ccf1876cf..c5f2212d30f2 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -100,7 +100,7 @@ const IOURequestStepDistanceRateWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '0'}`, - selector: DistanceRequestUtils.getMileageRates, + selector: (policy: OnyxEntry) => DistanceRequestUtils.getMileageRates(policy, false) }, })(IOURequestStepDistanceRate); From b3de1796726c106401b8066ae4245fa161b46b74 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Thu, 23 May 2024 10:39:01 +0700 Subject: [PATCH 067/219] add comma --- src/libs/DistanceRequestUtils.ts | 18 +++++++++--------- src/libs/Permissions.ts | 2 +- .../step/IOURequestStepDistanceRate.tsx | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 4104fd64259f..fadaeaaeb06f 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -65,18 +65,18 @@ function getMileageRates(policy: OnyxEntry, includeDisableRate: boolean unit: distanceUnit.attributes.unit, name: rate.name, customUnitRateID: rate.customUnitRateID, - } - } else if(rate.enabled) { + }; + } else if (rate.enabled) { mileageRates[rateID] = { rate: rate.rate, currency: rate.currency, unit: distanceUnit.attributes.unit, name: rate.name, customUnitRateID: rate.customUnitRateID, - } + }; } }); - + return mileageRates; } @@ -99,8 +99,8 @@ function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): Mileage if (!distanceUnit?.rates) { return null; } - const mileageRates = getMileageRates(policy, false) - + const mileageRates = getMileageRates(policy, false); + const distanceRate = Object.values(mileageRates).find((rate) => rate.name === CONST.CUSTOM_UNITS.DEFAULT_RATE) ?? Object.values(mileageRates)[0]; return { @@ -275,12 +275,12 @@ function getCustomUnitRateID(reportID: string) { } const distanceUnit = Object.values(policy?.customUnits).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE); - const lastSelectedDistanceRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? '' - const lastSelectedDistanceRate = distanceUnit?.rates[lastSelectedDistanceRateID] ?? {} + const lastSelectedDistanceRateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? ''; + const lastSelectedDistanceRate = distanceUnit?.rates[lastSelectedDistanceRateID] ?? {}; if (ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isPolicyExpenseChat(parentReport)) { if (lastSelectedDistanceRate.enabled && lastSelectedDistanceRateID) { - customUnitRateID = lastSelectedDistanceRateID + customUnitRateID = lastSelectedDistanceRateID; } else { customUnitRateID = getDefaultMileageRate(policy)?.customUnitRateID ?? ''; } diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index f2b34b7efa9b..e24d03ec62ff 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true + return true; } function canUseChronos(betas: OnyxEntry): boolean { diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index c5f2212d30f2..4f54faf69aee 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -100,7 +100,7 @@ const IOURequestStepDistanceRateWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '0'}`, - selector: (policy: OnyxEntry) => DistanceRequestUtils.getMileageRates(policy, false) + selector: (policy: OnyxEntry) => DistanceRequestUtils.getMileageRates(policy, false), }, })(IOURequestStepDistanceRate); From 7124d210436f5af17e49fbc6db9cc568b5234629 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Thu, 23 May 2024 10:56:48 +0700 Subject: [PATCH 068/219] address lint error --- src/libs/DistanceRequestUtils.ts | 7 ++++++- src/libs/Permissions.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index fadaeaaeb06f..d00e4e8b8818 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,3 +1,4 @@ +import {isEmpty} from 'lodash'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; @@ -45,7 +46,7 @@ const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 mile * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policy: OnyxEntry, includeDisableRate: boolean = true): Record { +function getMileageRates(policy: OnyxEntry | EmptyObject, includeDisableRate: boolean = true): Record { const mileageRates: Record = {}; if (!policy || !policy?.customUnits) { @@ -91,6 +92,10 @@ function getMileageRates(policy: OnyxEntry, includeDisableRate: boolean * @returns [unit] - The unit of measurement for the distance. */ function getDefaultMileageRate(policy: OnyxEntry | EmptyObject): MileageRate | null { + if (isEmpty(policy)) { + return null; + } + if (!policy?.customUnits) { return null; } diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index e24d03ec62ff..45551fe1cad9 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true; + return !!betas?.includes(CONST.BETAS.ALL); } function canUseChronos(betas: OnyxEntry): boolean { From 01c92b36e8f33f128de26528049dd7dcb0869d25 Mon Sep 17 00:00:00 2001 From: cretadn22 Date: Thu, 23 May 2024 11:04:56 +0700 Subject: [PATCH 069/219] address lint error --- src/libs/DistanceRequestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index d00e4e8b8818..07536a3f7fb7 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -46,7 +46,7 @@ const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 mile * * @returns An array of mileage rates or an empty array if not found. */ -function getMileageRates(policy: OnyxEntry | EmptyObject, includeDisableRate: boolean = true): Record { +function getMileageRates(policy: OnyxEntry, includeDisableRate = true): Record { const mileageRates: Record = {}; if (!policy || !policy?.customUnits) { From 9ae36e5fac1aff27f0cc3be7cec8f7a3ab4e9a30 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 23 May 2024 17:43:05 +0700 Subject: [PATCH 070/219] fix user is not prompted with a warning message when leaving group --- src/components/ChatDetailsQuickActionsBar.tsx | 3 ++- src/libs/ReportUtils.ts | 11 ++++++++--- src/libs/actions/Report.ts | 9 +++++++-- src/pages/InviteReportParticipantsPage.tsx | 5 ++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/ChatDetailsQuickActionsBar.tsx b/src/components/ChatDetailsQuickActionsBar.tsx index f15fc31aec45..8c5ef8ed520c 100644 --- a/src/components/ChatDetailsQuickActionsBar.tsx +++ b/src/components/ChatDetailsQuickActionsBar.tsx @@ -2,6 +2,7 @@ import React, {useState} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as ReportUtils from '@libs/ReportUtils'; import * as Report from '@userActions/Report'; import type {Report as OnyxReportType} from '@src/types/onyx'; import Button from './Button'; @@ -35,7 +36,7 @@ function ChatDetailsQuickActionsBar({report}: ChatDetailsQuickActionsBarProps) { />