From df364c5fe9c13b3e8c39c51b2f85d6f798ad54b3 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 18 Apr 2024 17:50:35 +0530 Subject: [PATCH 001/163] 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 002/163] 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 003/163] 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 d5e45aab90bac223ac8fa9716363c019dfccfc97 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 2 May 2024 21:55:32 +0700 Subject: [PATCH 004/163] RBR transaction thread is disappearing from the LHN when navigating to another chat --- src/libs/SidebarUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index c0d0c9020a64..e837130e12b0 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -96,6 +96,10 @@ function getOrderedReportIDs( const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const hasErrorsOtherThanFailedReceipt = doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); const shouldOverrideHidden = hasErrorsOtherThanFailedReceipt || isFocused || report.isPinned; + if (hasErrorsOtherThanFailedReceipt) { + return true; + } + if (isHidden && !shouldOverrideHidden) { return false; } From b9808e7f4ac9582d9b3a57a448db6b774cb7f8fb Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 2 May 2024 22:19:40 +0700 Subject: [PATCH 005/163] lint fix --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index e837130e12b0..40c032bb3e55 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -99,7 +99,7 @@ function getOrderedReportIDs( if (hasErrorsOtherThanFailedReceipt) { return true; } - + if (isHidden && !shouldOverrideHidden) { return false; } From f258c08c6fe2f9609341f27c585396212c3f7f03 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Wed, 8 May 2024 17:42:55 +0530 Subject: [PATCH 006/163] 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 d6a166b50a036de297c7740b479e65b9a68e5019 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Thu, 9 May 2024 09:04:55 +0530 Subject: [PATCH 007/163] 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 008/163] 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 009/163] 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 d71d7fcb1e7a215fa6058e7813aa21911c71f6d9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 14 May 2024 09:38:25 +0700 Subject: [PATCH 010/163] feat: add track tax --- src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 4 + .../PolicyDistanceRateDetailsPage.tsx | 17 +++ ...cyDistanceRateTaxReclaimableOnEditPage.tsx | 107 ++++++++++++++++++ .../PolicyDistanceRatesSettingsPage.tsx | 29 ++++- 9 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 39a090635655..16b06baafc7c 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -757,6 +757,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/distance-rates/:rateID/edit', getRoute: (policyID: string, rateID: string) => `settings/workspaces/${policyID}/distance-rates/${rateID}/edit` as const, }, + WORKSPACE_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT: { + route: 'settings/workspaces/:policyID/distance-rates/:rateID/tax-reclaimable/edit', + getRoute: (policyID: string, rateID: string) => `settings/workspaces/${policyID}/distance-rates/${rateID}/tax-reclaimable/edit` as const, + }, // Referral program promotion REFERRAL_DETAILS_MODAL: { route: 'referral/:contentType', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c43247623508..0fbd4243caea 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -305,6 +305,7 @@ const SCREENS = { DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings', DISTANCE_RATE_DETAILS: 'Distance_Rate_Details', DISTANCE_RATE_EDIT: 'Distance_Rate_Edit', + DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT: 'Distance_Rate_Tax_Reclaimable_On_Edit', }, EDIT_REQUEST: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 4373c2b63823..de0b8487cf9a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -247,6 +247,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRateDetailsPage').default as React.ComponentType, [SCREENS.WORKSPACE.DISTANCE_RATE_EDIT]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRateEditPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_SETTINGS]: () => require('../../../../pages/workspace/tags/TagSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_LIST_VIEW]: () => require('../../../../pages/workspace/tags/WorkspaceViewTagsPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index d7c92328ee04..982685130e20 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -75,6 +75,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.CREATE_DISTANCE_RATE, SCREENS.WORKSPACE.DISTANCE_RATES_SETTINGS, SCREENS.WORKSPACE.DISTANCE_RATE_EDIT, + SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT, SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS, ], }; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index da1f7b2fddfc..5a8cb77c0055 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -412,6 +412,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.DISTANCE_RATE_EDIT]: { path: ROUTES.WORKSPACE_DISTANCE_RATE_EDIT.route, }, + [SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT]: { + path: ROUTES.WORKSPACE_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT.route, + }, [SCREENS.WORKSPACE.TAGS_SETTINGS]: { path: ROUTES.WORKSPACE_TAGS_SETTINGS.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index df707c5a7c7d..b5f996273392 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -210,6 +210,10 @@ type SettingsNavigatorParamList = { policyID: string; rateID: string; }; + [SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT]: { + policyID: string; + rateID: string; + }; [SCREENS.WORKSPACE.TAGS_SETTINGS]: { policyID: string; }; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index a75fc74c33cc..405de59eabfe 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -54,6 +54,9 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const editRateValue = () => { Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATE_EDIT.getRoute(policyID, rateID)); }; + const editTaxReclaimableOnValue = () => { + Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT.getRoute(policyID, rateID)); + }; const toggleRate = () => { if (!rate.enabled || canDisableRate) { @@ -138,6 +141,20 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail onPress={editRateValue} /> + clearErrorFields('rate')} + > + + setIsWarningModalVisible(false)} isVisible={isWarningModalVisible} diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx new file mode 100644 index 000000000000..364073c4c9fd --- /dev/null +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx @@ -0,0 +1,107 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback} from 'react'; +import {Keyboard} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import AmountForm from '@components/AmountForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapperWithRef from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import {validateRateValue} from '@libs/PolicyDistanceRatesUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/PolicyDistanceRateEditForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type PolicyDistanceRateEditPageOnyxProps = { + /** Policy details */ + policy: OnyxEntry; +}; + +type PolicyDistanceRateEditPageProps = PolicyDistanceRateEditPageOnyxProps & StackScreenProps; + +function PolicyDistanceRateTaxReclaimableOnEditPage({policy, route}: PolicyDistanceRateEditPageProps) { + const styles = useThemeStyles(); + const {translate, toLocaleDigit} = useLocalize(); + const {inputCallbackRef} = useAutoFocusInput(); + + const policyID = route.params.policyID; + const rateID = route.params.rateID; + const customUnits = policy?.customUnits ?? {}; + const customUnit = customUnits[Object.keys(customUnits)[0]]; + const rate = customUnit.rates[rateID]; + const currency = rate.currency ?? CONST.CURRENCY.USD; + const currentRateValue = (rate.rate ?? 0).toString(); + + const submitRate = (values: FormOnyxValues) => { + Policy.updatePolicyDistanceRateValue(policyID, customUnit, [{...rate, rate: Number(values.rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET}]); + Keyboard.dismiss(); + Navigation.goBack(); + }; + + const validate = useCallback( + (values: FormOnyxValues) => validateRateValue(values, currency, toLocaleDigit), + [currency, toLocaleDigit], + ); + + return ( + + + Navigation.goBack()} + /> + + + + + + ); +} + +PolicyDistanceRateTaxReclaimableOnEditPage.displayName = 'PolicyDistanceRateTaxReclaimableOnEditPage'; + +export default withOnyx({ + policy: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, + }, +})(PolicyDistanceRateTaxReclaimableOnEditPage); diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx index d756084e5e49..d31b2b4ff8a5 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, { useCallback } from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -20,6 +20,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {CustomUnit} from '@src/types/onyx/Policy'; +import Switch from '@components/Switch'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; import CategorySelector from './CategorySelector'; import UnitSelector from './UnitSelector'; @@ -36,7 +39,6 @@ type PolicyDistanceRatesSettingsPageProps = PolicyDistanceRatesSettingsPageOnyxP function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: PolicyDistanceRatesSettingsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const policyID = route.params.policyID; const customUnits = policy?.customUnits ?? {}; const customUnit = customUnits[Object.keys(customUnits)[0]]; @@ -65,6 +67,10 @@ function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: Poli Policy.clearPolicyDistanceRatesErrorFields(policyID, customUnitID, {...errorFields, [fieldName]: null}); }; + const onToggleTrackTax = useCallback(()=>{ + + },[]) + return ( )} + + + + Track Tax + + + + {!policy?.tax?.trackingEnabled && + Taxes must be enabled on the workspace to use this feature. Head over to {}}>More features to make that change. + } + + From 8d3eaf7738227185bddcc03859107cee66af4429 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 14 May 2024 16:43:35 +0700 Subject: [PATCH 011/163] feat: add tax rate --- .../PolicyDistanceRateDetailsPage.tsx | 33 ++++++++- ...olicyDistanceRateTaxRateSelectionModal.tsx | 71 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index 405de59eabfe..0b57ced69d5d 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useState} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -26,6 +26,8 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {Rate} from '@src/types/onyx/Policy'; +import type { ListItemType } from './PolicyDistanceRateTaxRateSelectionModal'; +import PolicyDistanceRateTaxRateSelectionModal from './PolicyDistanceRateTaxRateSelectionModal'; type PolicyDistanceRateDetailsPageOnyxProps = { /** Policy details */ @@ -40,6 +42,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const {windowWidth} = useWindowDimensions(); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [isTaxRateSelectionModalVisible, setIsTaxRateSelectionModalVisible] = useState(false); const policyID = route.params.policyID; const rateID = route.params.rateID; @@ -50,6 +53,19 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const canDeleteRate = Object.values(customUnit.rates).filter((distanceRate) => distanceRate.enabled).length > 1 || !rate.enabled; const canDisableRate = Object.values(customUnit.rates).filter((distanceRate) => distanceRate.enabled).length > 1; const errorFields = rate.errorFields; + const defaultTaxRateID = policy?.taxRates?.defaultExternalID ?? ''; + const taxRate = `${policy?.taxRates?.taxes[defaultTaxRateID].name} (${policy?.taxRates?.taxes[defaultTaxRateID].value})` + const taxRateItems: ListItemType[] = useMemo(()=>{ + const taxes = policy?.taxRates?.taxes; + const result = Object.entries(taxes ?? {}).map(([key, value]) => ({ + value: value.value, + text: `${value.name} (${value.value})`, + isSelected: defaultTaxRateID === key, + keyForList: key + })); + return result + },[policy, defaultTaxRateID]) + const editRateValue = () => { Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATE_EDIT.getRoute(policyID, rateID)); @@ -141,6 +157,21 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail onPress={editRateValue} /> + + setIsTaxRateSelectionModalVisible(true)} + /> + {}} + onClose={()=> setIsTaxRateSelectionModalVisible(false)} + /> + void; + + /** Function to call when the user closes the role selection modal */ + onClose: () => void; +}; + +function PolicyDistanceRateTaxRateSelectionModal({isVisible, items, onTaxRateChange, onClose}: WorkspaceMemberDetailsPageProps) { + // const {translate} = useLocalize(); + const styles = useThemeStyles(); + + return ( + onClose?.()} + onModalHide={onClose} + hideModalContentWhileAnimating + useNativeDriver + > + + + + item.isSelected)?.keyForList} + /> + + + + ); +} + +PolicyDistanceRateTaxRateSelectionModal.displayName = 'PolicyDistanceRateTaxRateSelectionModal'; + +export type {ListItemType}; + +export default PolicyDistanceRateTaxRateSelectionModal; From eb014587bf2ed5385749f860ebe9cdf50e7c0c03 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Tue, 14 May 2024 17:58:34 +0530 Subject: [PATCH 012/163] 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 013/163] 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 0cf9a2c2096e4627ff007691b344eb5e3972821b Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 15 May 2024 15:29:18 +0700 Subject: [PATCH 014/163] fix: add EnableDistanceRequestTax api --- src/libs/API/types.ts | 1 + .../ModalStackNavigators/index.tsx | 3 +- src/libs/actions/Policy.ts | 54 +++++++++++++ .../PolicyDistanceRateDetailsPage.tsx | 76 ++++++++++--------- ...olicyDistanceRateTaxRateSelectionModal.tsx | 2 +- ...cyDistanceRateTaxReclaimableOnEditPage.tsx | 2 +- .../PolicyDistanceRatesSettingsPage.tsx | 64 +++++++++------- src/types/onyx/Policy.ts | 1 + 8 files changed, 138 insertions(+), 65 deletions(-) diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 8fe704ff220e..7400f561359e 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -206,6 +206,7 @@ const WRITE_COMMANDS = { ADD_BILLING_CARD_AND_REQUEST_WORKSPACE_OWNER_CHANGE: 'AddBillingCardAndRequestPolicyOwnerChange', SET_POLICY_DISTANCE_RATES_UNIT: 'SetPolicyDistanceRatesUnit', SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY: 'SetPolicyDistanceRatesDefaultCategory', + ENABLE_DISTANCE_REQUEST_TAX: 'EnableDistanceRequestTax', UPDATE_POLICY_DISTANCE_RATE_VALUE: 'UpdatePolicyDistanceRateValue', SET_POLICY_DISTANCE_RATES_ENABLED: 'SetPolicyDistanceRatesEnabled', DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index de0b8487cf9a..b3174b72e277 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -247,7 +247,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRateDetailsPage').default as React.ComponentType, [SCREENS.WORKSPACE.DISTANCE_RATE_EDIT]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRateEditPage').default as React.ComponentType, - [SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT]: () => + require('../../../../pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_SETTINGS]: () => require('../../../../pages/workspace/tags/TagSettingsPage').default as React.ComponentType, [SCREENS.WORKSPACE.TAG_LIST_VIEW]: () => require('../../../../pages/workspace/tags/WorkspaceViewTagsPage').default as React.ComponentType, diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 3332932f97c7..583ca5f5b753 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -4461,6 +4461,59 @@ function enablePolicyWorkflows(policyID: string, enabled: boolean) { } } +function enableDistanceRequestTax(policyID: string, customUnitName: string, customUnitID: string, attributes: Attributes) { + const policy = getPolicy(policyID); + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + attributes, + }, + }, + pendingFields: { + customUnits: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: { + customUnits: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnitID]: { + attributes: policy.customUnits ? policy.customUnits[customUnitID].attributes : null, + }, + }, + }, + }, + ], + }; + + const params = { + customUnitName, + customUnitID, + attributes, + }; + API.write(WRITE_COMMANDS.ENABLE_DISTANCE_REQUEST_TAX, params, onyxData); +} + function renamePolicyTaglist(policyID: string, policyTagListName: {oldName: string; newName: string}, policyTags: OnyxEntry) { const newName = policyTagListName.newName; const oldName = policyTagListName.oldName; @@ -5264,6 +5317,7 @@ export { enablePolicyTags, enablePolicyTaxes, enablePolicyWorkflows, + enableDistanceRequestTax, openPolicyDistanceRatesPage, openPolicyMoreFeaturesPage, generateCustomUnitID, diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index 0b57ced69d5d..238ecea64dfa 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -26,7 +26,7 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {Rate} from '@src/types/onyx/Policy'; -import type { ListItemType } from './PolicyDistanceRateTaxRateSelectionModal'; +import type {ListItemType} from './PolicyDistanceRateTaxRateSelectionModal'; import PolicyDistanceRateTaxRateSelectionModal from './PolicyDistanceRateTaxRateSelectionModal'; type PolicyDistanceRateDetailsPageOnyxProps = { @@ -48,24 +48,24 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const rateID = route.params.rateID; const customUnits = policy?.customUnits ?? {}; const customUnit = customUnits[Object.keys(customUnits)[0]]; + const isTrackTaxEnabled = customUnit.attributes.taxEnabled; const rate = customUnit.rates[rateID]; const currency = rate.currency ?? CONST.CURRENCY.USD; const canDeleteRate = Object.values(customUnit.rates).filter((distanceRate) => distanceRate.enabled).length > 1 || !rate.enabled; const canDisableRate = Object.values(customUnit.rates).filter((distanceRate) => distanceRate.enabled).length > 1; const errorFields = rate.errorFields; const defaultTaxRateID = policy?.taxRates?.defaultExternalID ?? ''; - const taxRate = `${policy?.taxRates?.taxes[defaultTaxRateID].name} (${policy?.taxRates?.taxes[defaultTaxRateID].value})` - const taxRateItems: ListItemType[] = useMemo(()=>{ + const taxRate = `${policy?.taxRates?.taxes[defaultTaxRateID].name} (${policy?.taxRates?.taxes[defaultTaxRateID].value})`; + const taxRateItems: ListItemType[] = useMemo(() => { const taxes = policy?.taxRates?.taxes; const result = Object.entries(taxes ?? {}).map(([key, value]) => ({ value: value.value, text: `${value.name} (${value.value})`, isSelected: defaultTaxRateID === key, - keyForList: key + keyForList: key, })); - return result - },[policy, defaultTaxRateID]) - + return result; + }, [policy, defaultTaxRateID]); const editRateValue = () => { Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATE_EDIT.getRoute(policyID, rateID)); @@ -157,35 +157,39 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail onPress={editRateValue} /> - - setIsTaxRateSelectionModalVisible(true)} - /> - {}} - onClose={()=> setIsTaxRateSelectionModalVisible(false)} - /> - - clearErrorFields('rate')} - > - - + {isTrackTaxEnabled && ( + + setIsTaxRateSelectionModalVisible(true)} + /> + {}} + onClose={() => setIsTaxRateSelectionModalVisible(false)} + /> + + )} + {isTrackTaxEnabled && ( + clearErrorFields('rate')} + > + + + )} setIsWarningModalVisible(false)} isVisible={isWarningModalVisible} diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx index 06c897f7e2ae..1a7df8b437fc 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx @@ -48,7 +48,7 @@ function PolicyDistanceRateTaxRateSelectionModal({isVisible, items, onTaxRateCha includePaddingTop={false} > diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx index 364073c4c9fd..67c497281236 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx @@ -67,7 +67,7 @@ function PolicyDistanceRateTaxReclaimableOnEditPage({policy, route}: PolicyDista shouldEnableMaxHeight > Navigation.goBack()} /> diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx index d31b2b4ff8a5..847136cf7a59 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, { useCallback } from 'react'; +import React from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -7,6 +7,9 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import type {ListItem} from '@components/SelectionList/types'; +import Switch from '@components/Switch'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; import type {UnitItemType} from '@components/UnitPicker'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -20,9 +23,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {CustomUnit} from '@src/types/onyx/Policy'; -import Switch from '@components/Switch'; -import Text from '@components/Text'; -import TextLink from '@components/TextLink'; import CategorySelector from './CategorySelector'; import UnitSelector from './UnitSelector'; @@ -43,6 +43,7 @@ function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: Poli const customUnits = policy?.customUnits ?? {}; const customUnit = customUnits[Object.keys(customUnits)[0]]; const customUnitID = customUnit?.customUnitID ?? ''; + const isTrackTaxEnabled = customUnit.attributes.taxEnabled; const defaultCategory = customUnits[customUnitID].defaultCategory; const defaultUnit = customUnits[customUnitID].attributes.unit; @@ -67,10 +68,10 @@ function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: Poli Policy.clearPolicyDistanceRatesErrorFields(policyID, customUnitID, {...errorFields, [fieldName]: null}); }; - const onToggleTrackTax = useCallback(()=>{ - - },[]) - + const onToggleTrackTax = (isOn: boolean) => { + const attributes = {...customUnits[customUnitID].attributes, taxEnabled: isOn}; + Policy.enableDistanceRequestTax(policyID, customUnit.name, customUnitID, attributes); + }; return ( )} - - - - Track Tax - - + + + + Track Tax + - {!policy?.tax?.trackingEnabled && - Taxes must be enabled on the workspace to use this feature. Head over to {}}>More features to make that change. - } - - + + {!policy?.tax?.trackingEnabled && ( + + + Taxes must be enabled on the workspace to use this feature. Head over to{' '} + {}} + > + More features + {' '} + to make that change. + + + )} + diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 53c76286985d..be318342eeff 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -19,6 +19,7 @@ type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ type Attributes = { unit: Unit; + taxEnabled?: boolean; }; type CustomUnit = OnyxCommon.OnyxValueWithOfflineFeedback<{ From 7b2ca00ad936104f4835b5edf3f6020864f97b07 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 May 2024 12:09:39 +0800 Subject: [PATCH 015/163] just go back --- src/pages/ReportDetailsPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index ca66a0e97bb5..8be0e40305c9 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -268,7 +268,6 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD From 613e3aecaf80bc9a33eba803ae6cecf924694fb6 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 May 2024 12:41:24 +0800 Subject: [PATCH 016/163] remove unused variable --- src/pages/ReportDetailsPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 8be0e40305c9..a5f800c702a6 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -64,7 +64,6 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD const {translate} = useLocalize(); const {isOffline} = useNetwork(); const styles = useThemeStyles(); - const route = useRoute(); const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`], [policies, report?.policyID]); const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy ?? null), [policy]); const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID ?? '', policies), [report?.policyID, policies]); From 4f84ec1f4b344dd98cac24ce70707520c265f728 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 16 May 2024 12:53:55 +0800 Subject: [PATCH 017/163] remove unused import --- src/pages/ReportDetailsPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index a5f800c702a6..793684af4ecd 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -1,4 +1,3 @@ -import {useRoute} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; From 075868d8d3ac4c16cc9f7ce3689b6ed58d00e87b Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Fri, 17 May 2024 12:24:31 +0530 Subject: [PATCH 018/163] 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 019/163] 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 1f93a1ee1db81f5b41e6dd55b4df586d8c7db432 Mon Sep 17 00:00:00 2001 From: Krishna Gupta Date: Mon, 20 May 2024 13:20:32 +0530 Subject: [PATCH 020/163] 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 7634bcc13adffaa32dd31b57f72154d4f6b3e0e8 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 20 May 2024 14:58:50 +0700 Subject: [PATCH 021/163] feat: hold expense in one transaction view --- src/components/MoneyReportHeader.tsx | 149 +++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 18 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 4eb3be871a8d..d7ead41b6a8c 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -8,6 +8,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as HeaderUtils from '@libs/HeaderUtils'; +import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -19,20 +20,26 @@ import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; +import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; +import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu'; import SettlementButton from './SettlementButton'; type MoneyReportHeaderOnyxProps = { /** The chat report this report is linked to */ chatReport: OnyxEntry; + /** All the data for the transaction in one transaction view */ + transaction: OnyxEntry; + /** The next step for the report */ nextStep: OnyxEntry; @@ -41,6 +48,9 @@ type MoneyReportHeaderOnyxProps = { /** The transaction thread report associated with the current report, if any */ transactionThreadReport: OnyxEntry; + + /** Whether we should show the Hold Interstitial explaining the feature */ + shownHoldUseExplanation: OnyxEntry; }; type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { @@ -68,31 +78,40 @@ function MoneyReportHeader({ session, policy, chatReport, + transaction, nextStep, report: moneyRequestReport, transactionThreadReport, reportActions, shouldUseNarrowLayout = false, + shownHoldUseExplanation = false, onBackButtonPress, }: MoneyReportHeaderProps) { const styles = useThemeStyles(); const theme = useTheme(); const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false); + const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); const {translate} = useLocalize(); const {windowWidth} = useWindowDimensions(); const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport); const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); + const isApproved = ReportUtils.isReportApproved(moneyRequestReport); + const isOnHold = TransactionUtils.isOnHold(transaction); const requestParentReportAction = useMemo(() => { if (!reportActions || !transactionThreadReport?.parentReportActionID) { return null; } return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID); }, [reportActions, transactionThreadReport?.parentReportActionID]); + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction as OnyxTypes.ReportAction); + const canHoldOrUnholdRequest = !isEmptyObject(transaction) && !isSettled && !isApproved && !isDeletedParentAction; // Only the requestor can delete the request, admins can only edit it. const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; + const isPolicyAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; + const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); @@ -104,7 +123,7 @@ function MoneyReportHeader({ const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); - const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID); + const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((t) => t.transactionID); const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs); const cancelPayment = useCallback(() => { @@ -126,6 +145,7 @@ function MoneyReportHeader({ const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; + const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID); const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); @@ -169,6 +189,40 @@ function MoneyReportHeader({ setIsDeleteRequestModalVisible(false); }, [moneyRequestReport?.reportID, requestParentReportAction, setIsDeleteRequestModalVisible]); + const changeMoneyRequestStatus = () => { + if (!transactionThreadReport) { + return; + } + const iouTransactionID = requestParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + + if (isOnHold) { + IOU.unholdRequest(iouTransactionID, transactionThreadReport.reportID); + } else { + const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(policy?.type ?? CONST.POLICY.TYPE.PERSONAL, iouTransactionID, transactionThreadReport.reportID, activeRoute)); + } + }; + + const getStatusIcon: (src: IconAsset) => React.ReactNode = (src) => ( + + ); + + const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => { + if (isOnHold) { + return {title: translate('iou.hold'), description: translate('iou.expenseOnHold'), danger: true, shouldShowBorderBottom: true}; + } + if (allHavePendingRTERViolation) { + return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true}; + } + }; + + const statusBarProps = getStatusBarProps(); + // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( () => chatReport?.isOwnPolicyExpenseChat && !policy?.harvesting?.enabled, @@ -176,6 +230,49 @@ function MoneyReportHeader({ ); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; + if (canHoldOrUnholdRequest) { + const isRequestIOU = chatReport?.type === 'iou'; + const isHoldCreator = ReportUtils.isHoldCreator(transaction, moneyRequestReport?.reportID) && isRequestIOU; + const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(moneyRequestReport); + const canModifyStatus = !isTrackExpenseReport && (isPolicyAdmin || isActionOwner || isApprover); + if (isOnHold && (isHoldCreator || (!isRequestIOU && canModifyStatus))) { + threeDotsMenuItems.push({ + icon: Expensicons.Stopwatch, + text: translate('iou.unholdExpense'), + onSelected: () => changeMoneyRequestStatus(), + }); + } + if (!isOnHold && (isRequestIOU || canModifyStatus) && !isScanning) { + threeDotsMenuItems.push({ + icon: Expensicons.Stopwatch, + text: translate('iou.hold'), + onSelected: () => changeMoneyRequestStatus(), + }); + } + } + + useEffect(() => { + setShouldShowHoldMenu(isOnHold && !shownHoldUseExplanation); + }, [isOnHold, shownHoldUseExplanation]); + + useEffect(() => { + if (!shouldShowHoldMenu) { + return; + } + + if (shouldUseNarrowLayout) { + if (Navigation.getActiveRoute().slice(1) === ROUTES.PROCESS_MONEY_REQUEST_HOLD) { + Navigation.goBack(); + } + } else { + Navigation.navigate(ROUTES.PROCESS_MONEY_REQUEST_HOLD); + } + }, [shouldUseNarrowLayout, shouldShowHoldMenu]); + + const handleHoldRequestClose = () => { + IOU.setShownHoldUseExplanation(); + }; + if (isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport)) { threeDotsMenuItems.push({ icon: Expensicons.Trashcan, @@ -232,7 +329,7 @@ function MoneyReportHeader({ shouldShowApproveButton={shouldShowApproveButton} shouldDisableApproveButton={shouldDisableApproveButton} style={[styles.pv2]} - formattedAmount={!ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID) ? displayedAmount : ''} + formattedAmount={!hasOnlyHeldExpenses ? displayedAmount : ''} isDisabled={!canAllowSettlement} /> @@ -250,18 +347,12 @@ function MoneyReportHeader({ )} - {allHavePendingRTERViolation && ( + {statusBarProps && ( - } - description={translate('iou.pendingMatchWithCreditCardDescription')} - shouldShowBorderBottom + title={statusBarProps.title} + description={statusBarProps.description} + danger={statusBarProps.danger} + shouldShowBorderBottom={statusBarProps.shouldShowBorderBottom} /> )} @@ -278,7 +369,7 @@ function MoneyReportHeader({ addBankAccountRoute={bankAccountRoute} shouldHidePaymentOptions={!shouldShowPayButton} shouldShowApproveButton={shouldShowApproveButton} - formattedAmount={!ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID) ? displayedAmount : ''} + formattedAmount={!hasOnlyHeldExpenses ? displayedAmount : ''} shouldDisableApproveButton={shouldDisableApproveButton} isDisabled={!canAllowSettlement} /> @@ -297,14 +388,14 @@ function MoneyReportHeader({ )} {shouldShowNextStep && ( - + )} {isHoldMenuVisible && requestType !== undefined && ( + {shouldUseNarrowLayout && shouldShowHoldMenu && ( + + )} ); } MoneyReportHeader.displayName = 'MoneyReportHeader'; -export default withOnyx({ +const MoneyReportHeaderWithTransaction = withOnyx>({ + transaction: { + key: ({transactionThreadReport, reportActions}) => { + const requestParentReportAction = ( + transactionThreadReport?.parentReportActionID && reportActions ? reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID) : {} + ) as OnyxTypes.ReportAction & OnyxTypes.OriginalMessageIOU; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${requestParentReportAction?.originalMessage?.IOUTransactionID ?? 0}`; + }, + }, + shownHoldUseExplanation: { + key: ONYXKEYS.NVP_HOLD_USE_EXPLAINED, + initWithStoredValues: true, + }, +})(MoneyReportHeader); + +export default withOnyx, Omit>({ chatReport: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`, }, @@ -354,4 +467,4 @@ export default withOnyx({ session: { key: ONYXKEYS.SESSION, }, -})(MoneyReportHeader); +})(MoneyReportHeaderWithTransaction); From 013863e7bb76fd05370f5021de54c6abaf548853 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 20 May 2024 15:00:36 +0700 Subject: [PATCH 022/163] show rbr transaction thread --- src/libs/OptionsListUtils.ts | 19 +++++++----- src/libs/SidebarUtils.ts | 56 ++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 544ced533e82..481d1a0431f9 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1636,6 +1636,16 @@ function getUserToInviteOption({ return userToInvite; } +/** + * check whether report has violations + */ +function checkReportHasViolations(report: Report, betas: OnyxEntry, transactionViolations: OnyxCollection) { + const {parentReportID, parentReportActionID} = report ?? {}; + const canGetParentReport = parentReportID && parentReportActionID && allReportActions; + const parentReportAction = canGetParentReport ? allReportActions[parentReportID]?.[parentReportActionID] ?? null : null; + return (Permissions.canUseViolations(betas) && !!parentReportAction && ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction)) ?? false; +} + /** * filter options based on specific conditions */ @@ -1743,13 +1753,7 @@ function getOptions( // Filter out all the reports that shouldn't be displayed const filteredReportOptions = options.reports.filter((option) => { const report = option.item; - - const {parentReportID, parentReportActionID} = report ?? {}; - const canGetParentReport = parentReportID && parentReportActionID && allReportActions; - const parentReportAction = canGetParentReport ? allReportActions[parentReportID]?.[parentReportActionID] ?? null : null; - const doesReportHaveViolations = - (betas?.includes(CONST.BETAS.VIOLATIONS) && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction)) ?? false; - + const doesReportHaveViolations = checkReportHasViolations(report, betas, transactionViolations); return ReportUtils.shouldReportBeInOptionList({ report, currentReportId: topmostReportId, @@ -2430,6 +2434,7 @@ export { getTaxRatesSection, getFirstKeyForList, getUserToInviteOption, + checkReportHasViolations, }; export type {MemberForList, CategorySection, CategoryTreeSection, Options, OptionList, SearchOption, PayeePersonalDetails, Category, Tax, TaxRatesOption, Option, OptionTree}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index fd7dbc2eb348..4cc29861c47d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -81,43 +81,46 @@ function getOrderedReportIDs( const allReportsDictValues = Object.values(allReports ?? {}); // Filter out all the reports that shouldn't be displayed - let reportsToDisplay = allReportsDictValues.filter((report) => { + let reportsToDisplay: Array = []; + + allReportsDictValues.forEach((report) => { if (!report) { - return false; + return; } - const parentReportActionsKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`; - const parentReportActions = allReportActions?.[parentReportActionsKey]; const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; - const parentReportAction = parentReportActions?.find((action) => action && action?.reportActionID === report.parentReportActionID); - const doesReportHaveViolations = !!( - betas?.includes(CONST.BETAS.VIOLATIONS) && - !!parentReportAction && - ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction as OnyxEntry) - ); + const doesReportHaveViolations = OptionsListUtils.checkReportHasViolations(report, betas, transactionViolations); const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; const hasErrorsOtherThanFailedReceipt = doesReportHaveViolations || Object.values(allReportErrors).some((error) => error?.[0] !== 'report.genericSmartscanFailureMessage'); const shouldOverrideHidden = hasErrorsOtherThanFailedReceipt || isFocused || report.isPinned; if (hasErrorsOtherThanFailedReceipt) { - return true; + reportsToDisplay.push({ + ...report, + hasErrorsOtherThanFailedReceipt: true, + }); + return; } if (isHidden && !shouldOverrideHidden) { - return false; + return; } - return ReportUtils.shouldReportBeInOptionList({ - report, - currentReportId: currentReportId ?? '', - isInFocusMode, - betas, - policies: policies as OnyxCollection, - excludeEmptyChats: true, - doesReportHaveViolations, - includeSelfDM: true, - }); + if ( + ReportUtils.shouldReportBeInOptionList({ + report, + currentReportId: currentReportId ?? '', + isInFocusMode, + betas, + policies: policies as OnyxCollection, + excludeEmptyChats: true, + doesReportHaveViolations, + includeSelfDM: true, + }) + ) { + reportsToDisplay.push(report); + } }); // The LHN is split into four distinct groups, and each group is sorted a little differently. The groups will ALWAYS be in this order: @@ -133,6 +136,7 @@ function getOrderedReportIDs( const draftReports: Array> = []; const nonArchivedReports: Array> = []; const archivedReports: Array> = []; + const errorReports: Array> = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { reportsToDisplay = reportsToDisplay.filter( @@ -141,7 +145,7 @@ function getOrderedReportIDs( } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((reportToDisplay) => { - let report = reportToDisplay as OnyxEntry; + let report = reportToDisplay; if (report) { report = { ...report, @@ -157,6 +161,8 @@ function getOrderedReportIDs( draftReports.push(report); } else if (ReportUtils.isArchivedRoom(report)) { archivedReports.push(report); + } else if (report?.hasErrorsOtherThanFailedReceipt) { + errorReports.push(report); } else { nonArchivedReports.push(report); } @@ -164,6 +170,8 @@ function getOrderedReportIDs( // Sort each group of reports accordingly pinnedAndGBRReports.sort((a, b) => (a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0)); + errorReports.sort((a, b) => (a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0)); + draftReports.sort((a, b) => (a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0)); if (isInDefaultMode) { @@ -184,7 +192,7 @@ function getOrderedReportIDs( // Now that we have all the reports grouped and sorted, they must be flattened into an array and only return the reportID. // The order the arrays are concatenated in matters and will determine the order that the groups are displayed in the sidebar. - const LHNReports = [...pinnedAndGBRReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); + const LHNReports = [...pinnedAndGBRReports, ...errorReports, ...draftReports, ...nonArchivedReports, ...archivedReports].map((report) => report?.reportID ?? ''); return LHNReports; } From 5a7e1493dff7352d046ce0aabfd143679214985c Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 20 May 2024 17:02:33 +0800 Subject: [PATCH 023/163] use the default props --- src/pages/ReportDetailsPage.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 793684af4ecd..783b78ef51d9 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -263,10 +263,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD return ( - + From b1229a47f0742ff7b9d7334e1c55427c204d466b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 20 May 2024 17:03:02 +0800 Subject: [PATCH 024/163] fix going back from flag comment removes the linked action --- src/pages/FlagCommentPage.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/pages/FlagCommentPage.tsx b/src/pages/FlagCommentPage.tsx index 53aac0ce2a8b..e30ac2a319d6 100644 --- a/src/pages/FlagCommentPage.tsx +++ b/src/pages/FlagCommentPage.tsx @@ -160,14 +160,7 @@ function FlagCommentPage({parentReportAction, route, report, parentReport, repor > {({safeAreaPaddingBottomStyle}) => ( - { - Navigation.goBack(); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report?.reportID ?? '')); - }} - /> + Date: Mon, 20 May 2024 17:10:27 +0800 Subject: [PATCH 025/163] remove unused import --- src/pages/FlagCommentPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/FlagCommentPage.tsx b/src/pages/FlagCommentPage.tsx index e30ac2a319d6..98fe3973ff9d 100644 --- a/src/pages/FlagCommentPage.tsx +++ b/src/pages/FlagCommentPage.tsx @@ -19,7 +19,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import withReportAndReportActionOrNotFound from './home/report/withReportAndReportActionOrNotFound'; From d3997ed49108c262b803ffd96bc42091ecb7bcd8 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 May 2024 11:21:31 +0200 Subject: [PATCH 026/163] Use new version of expensify-common --- .../AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx | 2 +- src/components/AttachmentModal.tsx | 2 +- src/components/AttachmentPicker/index.native.tsx | 2 +- src/components/Attachments/AttachmentView/index.tsx | 2 +- src/components/AutoEmailLink.tsx | 6 +++--- src/components/CurrencySelectionList/index.tsx | 2 +- src/components/DatePicker/CalendarPicker/index.tsx | 2 +- src/components/DeeplinkWrapper/index.website.tsx | 2 +- src/components/DragAndDrop/Provider/index.tsx | 2 +- .../HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx | 2 +- .../HTMLRenderers/MentionUserRenderer.tsx | 2 +- src/components/LHNOptionsList/OptionRowLHN.tsx | 2 +- src/components/MenuItem.tsx | 2 +- src/components/MoneyRequestConfirmationList.tsx | 2 +- src/components/ReportActionItem/MoneyReportView.tsx | 2 +- src/components/ReportActionItem/ReportActionItemImage.tsx | 2 +- src/components/ReportActionItem/TaskPreview.tsx | 2 +- src/components/SelectionList/InviteMemberListItem.tsx | 2 +- src/components/SelectionList/UserListItem.tsx | 2 +- src/components/StateSelector.tsx | 2 +- src/components/TextInput/BaseTextInput/index.native.tsx | 2 +- src/components/TextInput/BaseTextInput/index.tsx | 2 +- src/components/UnitPicker.tsx | 2 +- .../UserDetailsTooltip/BaseUserDetailsTooltip/index.tsx | 2 +- src/components/createOnyxContext.tsx | 2 +- src/hooks/useCopySelectionHelper.ts | 2 +- src/hooks/useGeographicalStateFromRoute.ts | 2 +- src/hooks/useHtmlPaste/index.ts | 2 +- src/languages/en.ts | 3 +-- src/languages/es.ts | 2 +- src/libs/ActiveClientManager/index.ts | 2 +- src/libs/BankAccountUtils.ts | 2 +- src/libs/EmojiUtils.ts | 2 +- src/libs/GetStyledTextArray.ts | 2 +- src/libs/KeyboardShortcut/index.ts | 2 +- src/libs/LocalePhoneNumber.ts | 2 +- src/libs/Log.ts | 2 +- src/libs/LoginUtils.ts | 3 +-- src/libs/NextStepUtils.ts | 2 +- .../Notification/LocalNotification/BrowserNotifications.ts | 2 +- src/libs/OptionsListUtils.ts | 2 +- src/libs/PersonalDetailsUtils.ts | 2 +- src/libs/PhoneNumber.ts | 2 +- src/libs/PolicyUtils.ts | 2 +- src/libs/ReceiptUtils.ts | 2 +- src/libs/ReportActionsUtils.ts | 2 +- src/libs/ReportUtils.ts | 3 +-- src/libs/SelectionScraper/index.ts | 2 +- src/libs/SidebarUtils.ts | 2 +- src/libs/UserUtils.ts | 2 +- src/libs/ValidationUtils.ts | 5 ++--- src/libs/actions/App.ts | 2 +- src/libs/actions/Device/generateDeviceID/index.android.ts | 2 +- src/libs/actions/Device/generateDeviceID/index.ts | 2 +- .../Device/getDeviceInfo/getOSAndName/index.native.ts | 2 +- src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts | 5 ++--- src/libs/actions/IOU.ts | 3 +-- src/libs/actions/Policy.ts | 4 +--- src/libs/actions/Report.ts | 3 +-- src/libs/fileDownload/FileUtils.ts | 2 +- src/libs/isReportMessageAttachment.ts | 2 +- src/libs/models/BankAccount.ts | 2 +- src/pages/DetailsPage.tsx | 2 +- src/pages/EditReportFieldPage.tsx | 2 +- src/pages/PrivateNotes/PrivateNotesEditPage.tsx | 3 +-- src/pages/ProfilePage.tsx | 2 +- src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx | 2 +- .../BusinessInfo/substeps/ConfirmationBusiness.tsx | 2 +- .../components/BankAccountValidationForm.tsx | 2 +- src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx | 2 +- src/pages/RoomDescriptionPage.tsx | 2 +- src/pages/RoomInvitePage.tsx | 2 +- src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx | 2 +- src/pages/TeachersUnite/KnowATeacherPage.tsx | 2 +- src/pages/home/report/ContextMenu/ContextMenuActions.tsx | 3 +-- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- .../ComposerWithSuggestions/ComposerWithSuggestions.tsx | 2 +- .../home/report/ReportActionCompose/SuggestionMention.tsx | 2 +- src/pages/home/report/ReportActionItemBasicMessage.tsx | 2 +- src/pages/home/report/ReportActionItemMessageEdit.tsx | 2 +- src/pages/home/report/comment/TextCommentFragment.tsx | 2 +- .../home/report/comment/shouldRenderAsText/index.native.ts | 2 +- src/pages/home/report/comment/shouldRenderAsText/index.ts | 2 +- .../settings/Profile/Contacts/ContactMethodDetailsPage.tsx | 2 +- src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx | 2 +- .../settings/Profile/Contacts/NewContactMethodPage.tsx | 2 +- .../settings/Profile/PersonalDetails/StateSelectionPage.tsx | 2 +- src/pages/settings/Security/CloseAccountPage.tsx | 2 +- src/pages/signin/EmailDeliveryFailurePage.tsx | 2 +- src/pages/signin/LoginForm/BaseLoginForm.tsx | 2 +- src/pages/signin/SignInPage.tsx | 2 +- src/pages/signin/UnlinkLoginForm.tsx | 2 +- src/pages/tasks/NewTaskDescriptionPage.tsx | 2 +- src/pages/tasks/NewTaskDetailsPage.tsx | 2 +- src/pages/tasks/TaskDescriptionPage.tsx | 2 +- src/pages/wallet/WalletStatementPage.tsx | 2 +- src/pages/workspace/WorkspaceInviteMessagePage.tsx | 2 +- src/pages/workspace/WorkspaceProfileDescriptionPage.tsx | 2 +- src/pages/workspace/WorkspaceProfilePage.tsx | 2 +- src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx | 2 +- src/pages/workspace/distanceRates/UnitSelector/index.tsx | 2 +- .../reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx | 2 +- src/pages/workspace/taxes/NamePage.tsx | 2 +- src/stories/Composer.stories.tsx | 2 +- tests/utils/TestHelper.ts | 2 +- 105 files changed, 109 insertions(+), 120 deletions(-) diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx index 99a0ee3bf683..d07cf1b474a9 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect, useRef} from 'react'; // eslint-disable-next-line no-restricted-imports import type {Text as RNText} from 'react-native'; diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index fb6a8e911e87..f3954eff4683 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {memo, useCallback, useEffect, useMemo, useState} from 'react'; import {Animated, Keyboard, View} from 'react-native'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index ad4cf023c096..9249eb79d368 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Alert, View} from 'react-native'; import RNFetchBlob from 'react-native-blob-util'; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index cee2264894a7..a7409e57f846 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {memo, useEffect, useState} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; diff --git a/src/components/AutoEmailLink.tsx b/src/components/AutoEmailLink.tsx index e1a9bdd2794b..d64c665a020f 100644 --- a/src/components/AutoEmailLink.tsx +++ b/src/components/AutoEmailLink.tsx @@ -1,4 +1,4 @@ -import {CONST} from 'expensify-common/lib/CONST'; +import {CONST as COMMON_CONST} from 'expensify-common'; import React from 'react'; import type {StyleProp, TextStyle} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -20,8 +20,8 @@ function AutoEmailLink({text, style}: AutoEmailLinkProps) { const styles = useThemeStyles(); return ( - {text.split(CONST.REG_EXP.EXTRACT_EMAIL).map((str, index) => { - if (CONST.REG_EXP.EMAIL.test(str)) { + {text.split(COMMON_CONST.REG_EXP.EXTRACT_EMAIL).map((str, index) => { + if (COMMON_CONST.REG_EXP.EMAIL.test(str)) { return ( >; type State = keyof typeof COMMON_CONST.STATES; diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 925a3db518ae..a6d8993888cc 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -1,5 +1,5 @@ import {useNavigation} from '@react-navigation/native'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import {useCallback, useEffect} from 'react'; import type UseHtmlPaste from './types'; diff --git a/src/languages/en.ts b/src/languages/en.ts index f8d122f3b69a..fa65aadc1a43 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1,5 +1,4 @@ -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import Str from 'expensify-common/lib/str'; +import {CONST as COMMON_CONST, Str} from 'expensify-common'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; import type {ConnectionName, PolicyConnectionSyncStage} from '@src/types/onyx/Policy'; diff --git a/src/languages/es.ts b/src/languages/es.ts index 536a6182f393..f3d7ea521424 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import CONST from '@src/CONST'; import type {ConnectionName, PolicyConnectionSyncStage} from '@src/types/onyx/Policy'; import type { diff --git a/src/libs/ActiveClientManager/index.ts b/src/libs/ActiveClientManager/index.ts index 69bd3a848e0b..e93cdb07d084 100644 --- a/src/libs/ActiveClientManager/index.ts +++ b/src/libs/ActiveClientManager/index.ts @@ -3,7 +3,7 @@ * only one tab is processing those saved requests or we would be duplicating data (or creating errors). * This file ensures exactly that by tracking all the clientIDs connected, storing the most recent one last and it considers that last clientID the "leader". */ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; import * as ActiveClients from '@userActions/ActiveClients'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/BankAccountUtils.ts b/src/libs/BankAccountUtils.ts index a7fbc5f3bd4e..d3c5bb4998a8 100644 --- a/src/libs/BankAccountUtils.ts +++ b/src/libs/BankAccountUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {OnyxEntry} from 'react-native-onyx'; import type * as OnyxTypes from '@src/types/onyx'; diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 3b189dbb084f..18bc5f5fb576 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -1,5 +1,5 @@ import {getUnixTime} from 'date-fns'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import memoize from 'lodash/memoize'; import Onyx from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/libs/GetStyledTextArray.ts b/src/libs/GetStyledTextArray.ts index 9bc6ccc33b4c..04094c1ee61f 100644 --- a/src/libs/GetStyledTextArray.ts +++ b/src/libs/GetStyledTextArray.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; type StyledText = { text: string; diff --git a/src/libs/KeyboardShortcut/index.ts b/src/libs/KeyboardShortcut/index.ts index b349c43b5715..4f89d44da959 100644 --- a/src/libs/KeyboardShortcut/index.ts +++ b/src/libs/KeyboardShortcut/index.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import * as KeyCommand from 'react-native-key-command'; import getOperatingSystem from '@libs/getOperatingSystem'; import localeCompare from '@libs/LocaleCompare'; diff --git a/src/libs/LocalePhoneNumber.ts b/src/libs/LocalePhoneNumber.ts index 460d5fc0fe9f..7249912e6f58 100644 --- a/src/libs/LocalePhoneNumber.ts +++ b/src/libs/LocalePhoneNumber.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/Log.ts b/src/libs/Log.ts index 101996870e1d..0612dcff9223 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -2,7 +2,7 @@ // action would likely cause confusion about which one to use. But most other API methods should happen inside an action file. /* eslint-disable rulesdir/no-api-in-views */ -import Logger from 'expensify-common/lib/Logger'; +import {Logger} from 'expensify-common'; import Onyx from 'react-native-onyx'; import type {Merge} from 'type-fest'; import CONST from '@src/CONST'; diff --git a/src/libs/LoginUtils.ts b/src/libs/LoginUtils.ts index 2d49cb992717..ded60ab3e800 100644 --- a/src/libs/LoginUtils.ts +++ b/src/libs/LoginUtils.ts @@ -1,5 +1,4 @@ -import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; -import Str from 'expensify-common/lib/str'; +import {PUBLIC_DOMAINS, Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 66f63ddfd27f..e7dd903f9502 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,5 +1,5 @@ import {format, lastDayOfMonth, setDate} from 'date-fns'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; diff --git a/src/libs/Notification/LocalNotification/BrowserNotifications.ts b/src/libs/Notification/LocalNotification/BrowserNotifications.ts index ee6d00ee2208..9980a1d0e1b9 100644 --- a/src/libs/Notification/LocalNotification/BrowserNotifications.ts +++ b/src/libs/Notification/LocalNotification/BrowserNotifications.ts @@ -1,5 +1,5 @@ // Web and desktop implementation only. Do not import for direct use. Use LocalNotification. -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {ImageSourcePropType} from 'react-native'; import EXPENSIFY_ICON_URL from '@assets/images/expensify-logo-round-clearspace.png'; import * as AppUpdate from '@libs/actions/AppUpdate'; diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 544ced533e82..a5c6f2c7300f 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1,5 +1,5 @@ /* eslint-disable no-continue */ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; // eslint-disable-next-line you-dont-need-lodash-underscore/get import lodashGet from 'lodash/get'; import lodashOrderBy from 'lodash/orderBy'; diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index ffa0605f1eba..26547583f644 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {CurrentUserPersonalDetails} from '@components/withCurrentUserPersonalDetails'; diff --git a/src/libs/PhoneNumber.ts b/src/libs/PhoneNumber.ts index 787b3634030a..84ef35a18489 100644 --- a/src/libs/PhoneNumber.ts +++ b/src/libs/PhoneNumber.ts @@ -1,7 +1,7 @@ // eslint-disable-next-line no-restricted-imports import {parsePhoneNumber as originalParsePhoneNumber} from 'awesome-phonenumber'; import type {ParsedPhoneNumber, ParsedPhoneNumberInvalid, PhoneNumberParseOptions} from 'awesome-phonenumber'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import CONST from '@src/CONST'; /** diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 6a26b1a6cfc2..fca803e03790 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index 849bc50e77b0..b3892d20162f 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import _ from 'lodash'; import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 906a2963baa8..ca45ba79fa23 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1,4 +1,4 @@ -import fastMerge from 'expensify-common/lib/fastMerge'; +import {fastMerge} from 'expensify-common'; import _ from 'lodash'; import lodashFindLast from 'lodash/findLast'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b8e4c448fdc2..ef646a169358 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1,6 +1,5 @@ import {format} from 'date-fns'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import Str from 'expensify-common/lib/str'; +import {ExpensiMark, Str} from 'expensify-common'; import {isEmpty} from 'lodash'; import lodashEscape from 'lodash/escape'; import lodashFindLastIndex from 'lodash/findLastIndex'; diff --git a/src/libs/SelectionScraper/index.ts b/src/libs/SelectionScraper/index.ts index eda38a43507c..88726aa633b6 100644 --- a/src/libs/SelectionScraper/index.ts +++ b/src/libs/SelectionScraper/index.ts @@ -1,7 +1,7 @@ import render from 'dom-serializer'; import type {Node} from 'domhandler'; import {DataNode, Element} from 'domhandler'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import {parseDocument} from 'htmlparser2'; import CONST from '@src/CONST'; import type GetCurrentSelection from './types'; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b4ae942547a1..aae4fc3e729c 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ChatReportSelector, PolicySelector, ReportActionsSelector} from '@hooks/useReportIDs'; diff --git a/src/libs/UserUtils.ts b/src/libs/UserUtils.ts index ce7e4963afc7..cdbbb96e777f 100644 --- a/src/libs/UserUtils.ts +++ b/src/libs/UserUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as defaultAvatars from '@components/Icon/DefaultAvatars'; diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts index d5a87fc07e28..3f95f1aa2b70 100644 --- a/src/libs/ValidationUtils.ts +++ b/src/libs/ValidationUtils.ts @@ -1,6 +1,5 @@ import {addYears, endOfMonth, format, isAfter, isBefore, isSameDay, isValid, isWithinInterval, parse, parseISO, startOfDay, subYears} from 'date-fns'; -import Str from 'expensify-common/lib/str'; -import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url'; +import {Str, Url} from 'expensify-common'; import isDate from 'lodash/isDate'; import isEmpty from 'lodash/isEmpty'; import isObject from 'lodash/isObject'; @@ -241,7 +240,7 @@ function getDatePassedError(inputDate: string): string { */ function isValidWebsite(url: string): boolean { const isLowerCase = url === url.toLowerCase(); - return new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i').test(url) && isLowerCase; + return new RegExp(`^${Url.URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i').test(url) && isLowerCase; } function validateIdentity(identity: Record): Record { diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 2657ea04d9e5..61bd1c2b235c 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -1,5 +1,5 @@ // Issue - https://github.com/Expensify/App/issues/26719 -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {AppStateStatus} from 'react-native'; import {AppState} from 'react-native'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; diff --git a/src/libs/actions/Device/generateDeviceID/index.android.ts b/src/libs/actions/Device/generateDeviceID/index.android.ts index d662967a9f76..17563e039e6a 100644 --- a/src/libs/actions/Device/generateDeviceID/index.android.ts +++ b/src/libs/actions/Device/generateDeviceID/index.android.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import DeviceInfo from 'react-native-device-info'; import type GenerateDeviceID from './types'; diff --git a/src/libs/actions/Device/generateDeviceID/index.ts b/src/libs/actions/Device/generateDeviceID/index.ts index 82ea72ba8180..a88457463f34 100644 --- a/src/libs/actions/Device/generateDeviceID/index.ts +++ b/src/libs/actions/Device/generateDeviceID/index.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type GenerateDeviceID from './types'; const uniqueID = Str.guid(); diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts index 16a48348df71..06c425438053 100644 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.native.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import RNDeviceInfo from 'react-native-device-info'; import type {GetOSAndName} from './types'; diff --git a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts index 29b004412f64..7111cab41ad1 100644 --- a/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts +++ b/src/libs/actions/Device/getDeviceInfo/getOSAndName/index.ts @@ -1,4 +1,3 @@ -// Don't import this file with '* as Device'. It's known to make VSCode IntelliSense crash. -import {getOSAndName} from 'expensify-common/lib/Device'; +import {Device} from 'expensify-common'; -export default getOSAndName; +export default Device.getOSAndName; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 2464dbff7dbd..a9beaa410696 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1,6 +1,5 @@ import {format} from 'date-fns'; -import fastMerge from 'expensify-common/lib/fastMerge'; -import Str from 'expensify-common/lib/str'; +import {fastMerge, Str} from 'expensify-common'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 3e09524e1dd1..09c1bf35a25a 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1,6 +1,4 @@ -import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import Str from 'expensify-common/lib/str'; +import {ExpensiMark, PUBLIC_DOMAINS, Str} from 'expensify-common'; import {escapeRegExp} from 'lodash'; import lodashClone from 'lodash/clone'; import lodashUnion from 'lodash/union'; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 91a0ce5da930..8ff0484946d3 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1,6 +1,5 @@ import {format as timezoneFormat, utcToZonedTime} from 'date-fns-tz'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import Str from 'expensify-common/lib/str'; +import {ExpensiMark, Str} from 'expensify-common'; import isEmpty from 'lodash/isEmpty'; import {DeviceEventEmitter, InteractionManager, Linking} from 'react-native'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; diff --git a/src/libs/fileDownload/FileUtils.ts b/src/libs/fileDownload/FileUtils.ts index 6517a7a28642..f058ce0f80d8 100644 --- a/src/libs/fileDownload/FileUtils.ts +++ b/src/libs/fileDownload/FileUtils.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import {Alert, Linking, Platform} from 'react-native'; import ImageSize from 'react-native-image-size'; import type {FileObject} from '@components/AttachmentModal'; diff --git a/src/libs/isReportMessageAttachment.ts b/src/libs/isReportMessageAttachment.ts index 330ba4470097..9ef4125ee8bf 100644 --- a/src/libs/isReportMessageAttachment.ts +++ b/src/libs/isReportMessageAttachment.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import CONST from '@src/CONST'; import type {Message} from '@src/types/onyx/ReportAction'; diff --git a/src/libs/models/BankAccount.ts b/src/libs/models/BankAccount.ts index 611d77c99927..44b2122d8091 100644 --- a/src/libs/models/BankAccount.ts +++ b/src/libs/models/BankAccount.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {AdditionalData} from '@src/types/onyx/BankAccount'; diff --git a/src/pages/DetailsPage.tsx b/src/pages/DetailsPage.tsx index 49b3e856c65d..1e0cb71f020c 100755 --- a/src/pages/DetailsPage.tsx +++ b/src/pages/DetailsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx index 6cc93d05ebbc..be5ba4f6e486 100644 --- a/src/pages/EditReportFieldPage.tsx +++ b/src/pages/EditReportFieldPage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx index f9560edf7486..83a72bfe7ff8 100644 --- a/src/pages/PrivateNotes/PrivateNotesEditPage.tsx +++ b/src/pages/PrivateNotes/PrivateNotesEditPage.tsx @@ -1,7 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import Str from 'expensify-common/lib/str'; +import {ExpensiMark, Str} from 'expensify-common'; import lodashDebounce from 'lodash/debounce'; import React, {useCallback, useMemo, useRef, useState} from 'react'; import {Keyboard} from 'react-native'; diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index d5e556b25dd0..cd942edc474b 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx b/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx index 10b05e58deb7..07c76542f49f 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx index 6311a63a4059..4c8331a23d8f 100644 --- a/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx +++ b/src/pages/ReimbursementAccount/BusinessInfo/substeps/ConfirmationBusiness.tsx @@ -1,4 +1,4 @@ -import type {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import type {CONST as COMMON_CONST} from 'expensify-common'; import React, {useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx b/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx index a83e18d119c9..9c539cbef6fa 100644 --- a/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx +++ b/src/pages/ReimbursementAccount/ConnectBankAccount/components/BankAccountValidationForm.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx index 8c59e98f7218..dde820368447 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.tsx @@ -1,6 +1,6 @@ import type {RouteProp} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import lodashPick from 'lodash/pick'; import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; diff --git a/src/pages/RoomDescriptionPage.tsx b/src/pages/RoomDescriptionPage.tsx index 3992dff188e2..46a10483fd08 100644 --- a/src/pages/RoomDescriptionPage.tsx +++ b/src/pages/RoomDescriptionPage.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxCollection} from 'react-native-onyx'; diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index c78094594f16..633623fc16f3 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; import {View} from 'react-native'; diff --git a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx index 6e8c3b340f4e..eabd8642c287 100644 --- a/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx +++ b/src/pages/TeachersUnite/IntroSchoolPrincipalPage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/TeachersUnite/KnowATeacherPage.tsx b/src/pages/TeachersUnite/KnowATeacherPage.tsx index cb58009c40f0..aaca679e49b8 100644 --- a/src/pages/TeachersUnite/KnowATeacherPage.tsx +++ b/src/pages/TeachersUnite/KnowATeacherPage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 105eadffd436..69c56f82d4f4 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -1,5 +1,4 @@ -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import Str from 'expensify-common/lib/str'; +import {ExpensiMark, Str} from 'expensify-common'; import type {MutableRefObject} from 'react'; import React from 'react'; import {InteractionManager} from 'react-native'; diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 6f56f8f09632..b90d1561c77c 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React from 'react'; import {FlatList} from 'react-native'; import type {FlatListProps} from 'react-native'; diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx index 29b25828d24b..260f81ca0c3b 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx @@ -1,5 +1,5 @@ import {useIsFocused, useNavigation} from '@react-navigation/native'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef, MutableRefObject, RefAttributes, RefObject} from 'react'; import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 05e1163da200..75c4632c86b1 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import lodashSortBy from 'lodash/sortBy'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; diff --git a/src/pages/home/report/ReportActionItemBasicMessage.tsx b/src/pages/home/report/ReportActionItemBasicMessage.tsx index a28f2af24448..20098933b02e 100644 --- a/src/pages/home/report/ReportActionItemBasicMessage.tsx +++ b/src/pages/home/report/ReportActionItemBasicMessage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 6cb03e8dae05..b972ef270aaa 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -1,4 +1,4 @@ -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import lodashDebounce from 'lodash/debounce'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx index cf1ab6f8aa19..f29acf8d35fc 100644 --- a/src/pages/home/report/comment/TextCommentFragment.tsx +++ b/src/pages/home/report/comment/TextCommentFragment.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import {isEmpty} from 'lodash'; import React, {memo} from 'react'; import type {StyleProp, TextStyle} from 'react-native'; diff --git a/src/pages/home/report/comment/shouldRenderAsText/index.native.ts b/src/pages/home/report/comment/shouldRenderAsText/index.native.ts index 7c5758f8720d..ac68a9e660cf 100644 --- a/src/pages/home/report/comment/shouldRenderAsText/index.native.ts +++ b/src/pages/home/report/comment/shouldRenderAsText/index.native.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; /** * Whether to render the report action as text diff --git a/src/pages/home/report/comment/shouldRenderAsText/index.ts b/src/pages/home/report/comment/shouldRenderAsText/index.ts index f26f43c528eb..7e8200671c1a 100644 --- a/src/pages/home/report/comment/shouldRenderAsText/index.ts +++ b/src/pages/home/report/comment/shouldRenderAsText/index.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; /** * Whether to render the report action as text diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx index 8aaeb7151563..28562c5e8d00 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, Keyboard, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx index 8970df59f856..e38f1a029d51 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx +++ b/src/pages/settings/Profile/Contacts/ContactMethodsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx index 20e12f71664e..52fea78bb69e 100644 --- a/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx +++ b/src/pages/settings/Profile/Contacts/NewContactMethodPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx index bf1bb8ad197e..f16cfcc8f9ff 100644 --- a/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx +++ b/src/pages/settings/Profile/PersonalDetails/StateSelectionPage.tsx @@ -1,5 +1,5 @@ import {useNavigation, useRoute} from '@react-navigation/native'; -import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import {CONST as COMMON_CONST} from 'expensify-common'; import _ from 'lodash'; import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; diff --git a/src/pages/settings/Security/CloseAccountPage.tsx b/src/pages/settings/Security/CloseAccountPage.tsx index 0fc0f5b7790a..c9ad13b17e45 100644 --- a/src/pages/settings/Security/CloseAccountPage.tsx +++ b/src/pages/settings/Security/CloseAccountPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/signin/EmailDeliveryFailurePage.tsx b/src/pages/signin/EmailDeliveryFailurePage.tsx index e2e12dbd0c1c..0a294e3a6b86 100644 --- a/src/pages/signin/EmailDeliveryFailurePage.tsx +++ b/src/pages/signin/EmailDeliveryFailurePage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect, useMemo} from 'react'; import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/signin/LoginForm/BaseLoginForm.tsx b/src/pages/signin/LoginForm/BaseLoginForm.tsx index 4286a2603341..31c77d3db989 100644 --- a/src/pages/signin/LoginForm/BaseLoginForm.tsx +++ b/src/pages/signin/LoginForm/BaseLoginForm.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import type {ForwardedRef} from 'react'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; diff --git a/src/pages/signin/SignInPage.tsx b/src/pages/signin/SignInPage.tsx index 16aeb7053726..1b51911cbc33 100644 --- a/src/pages/signin/SignInPage.tsx +++ b/src/pages/signin/SignInPage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect, useRef, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/signin/UnlinkLoginForm.tsx b/src/pages/signin/UnlinkLoginForm.tsx index c2e62e3b80f3..2fa43fbe61b2 100644 --- a/src/pages/signin/UnlinkLoginForm.tsx +++ b/src/pages/signin/UnlinkLoginForm.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/tasks/NewTaskDescriptionPage.tsx b/src/pages/tasks/NewTaskDescriptionPage.tsx index 93ad5e63f3d7..410dd9c6e137 100644 --- a/src/pages/tasks/NewTaskDescriptionPage.tsx +++ b/src/pages/tasks/NewTaskDescriptionPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 52fdbf5523a2..7cad3d5f844b 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index c48746c81239..35e954bad2f1 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -1,5 +1,5 @@ import {useFocusEffect} from '@react-navigation/native'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; diff --git a/src/pages/wallet/WalletStatementPage.tsx b/src/pages/wallet/WalletStatementPage.tsx index ac1b6d428d52..40f69d809618 100644 --- a/src/pages/wallet/WalletStatementPage.tsx +++ b/src/pages/wallet/WalletStatementPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import {format, getMonth, getYear} from 'date-fns'; -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 2eaa38b865e6..dfa121cdd509 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import lodashDebounce from 'lodash/debounce'; import React, {useEffect, useMemo, useState} from 'react'; import {Keyboard, View} from 'react-native'; diff --git a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx index 00b11bf64697..faa2415713fd 100644 --- a/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx +++ b/src/pages/workspace/WorkspaceProfileDescriptionPage.tsx @@ -1,4 +1,4 @@ -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useState} from 'react'; import {Keyboard, View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 2b826e89e850..fd7aa2f6e398 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -1,4 +1,4 @@ -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useState} from 'react'; import type {ImageStyle, StyleProp} from 'react-native'; import {Image, StyleSheet, View} from 'react-native'; diff --git a/src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx b/src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx index 638ab9d58c31..f30d90f5a063 100644 --- a/src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx +++ b/src/pages/workspace/bills/WorkspaceBillsFirstSection.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/workspace/distanceRates/UnitSelector/index.tsx b/src/pages/workspace/distanceRates/UnitSelector/index.tsx index e6511e7d6418..166d0df786a1 100644 --- a/src/pages/workspace/distanceRates/UnitSelector/index.tsx +++ b/src/pages/workspace/distanceRates/UnitSelector/index.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; diff --git a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx index 684b4be37d44..8027300357f2 100644 --- a/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx +++ b/src/pages/workspace/reimburse/WorkspaceRateAndUnitPage/InitialPage.tsx @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import React, {useEffect} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; diff --git a/src/pages/workspace/taxes/NamePage.tsx b/src/pages/workspace/taxes/NamePage.tsx index dfcad933b7b0..f338d9813350 100644 --- a/src/pages/workspace/taxes/NamePage.tsx +++ b/src/pages/workspace/taxes/NamePage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; diff --git a/src/stories/Composer.stories.tsx b/src/stories/Composer.stories.tsx index b60568003bd2..7244479b4c58 100644 --- a/src/stories/Composer.stories.tsx +++ b/src/stories/Composer.stories.tsx @@ -1,5 +1,5 @@ import type {Meta} from '@storybook/react'; -import ExpensiMark from 'expensify-common/lib/ExpensiMark'; +import {ExpensiMark} from 'expensify-common'; import React, {useState} from 'react'; import {Image, View} from 'react-native'; import Composer from '@components/Composer'; diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index bd107ba6ed56..6bcee9763dde 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,4 +1,4 @@ -import Str from 'expensify-common/lib/str'; +import {Str} from 'expensify-common'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import * as Session from '@src/libs/actions/Session'; From 3073d7c3d10fdadab93f021167d4b3863ba4db44 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 20 May 2024 17:44:42 +0700 Subject: [PATCH 027/163] fix test --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 4cc29861c47d..3ebf5ab5783a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -89,7 +89,7 @@ function getOrderedReportIDs( } const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; - const doesReportHaveViolations = OptionsListUtils.checkReportHasViolations(report, betas, transactionViolations); + const doesReportHaveViolations = OptionsListUtils.checkReportHasViolations(report, betas ?? [], transactionViolations); const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; From d1687e0ce45664638c4a37a506bffb05fdc348ef Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 21 May 2024 11:58:38 +0700 Subject: [PATCH 028/163] show hold banner --- src/components/MoneyReportHeader.tsx | 12 ++++++------ src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index d7ead41b6a8c..155433bdcffb 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -142,11 +142,11 @@ function MoneyReportHeader({ const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation; - const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation; + const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID); + const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID); - const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation; + const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); @@ -213,8 +213,8 @@ function MoneyReportHeader({ ); const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => { - if (isOnHold) { - return {title: translate('iou.hold'), description: translate('iou.expenseOnHold'), danger: true, shouldShowBorderBottom: true}; + if (hasOnlyHeldExpenses) { + return {title: translate('iou.hold'), description: translate('iou.expensesOnHold'), danger: true, shouldShowBorderBottom: true}; } if (allHavePendingRTERViolation) { return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true}; @@ -309,7 +309,7 @@ function MoneyReportHeader({ shouldShowBackButton={shouldUseNarrowLayout} onBackButtonPress={onBackButtonPress} // Shows border if no buttons or next steps are showing below the header - shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation} + shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation && !hasOnlyHeldExpenses} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} diff --git a/src/languages/en.ts b/src/languages/en.ts index f8d122f3b69a..9cc0f7d723f9 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -749,6 +749,7 @@ export default { reason: 'Reason', holdReasonRequired: 'A reason is required when holding.', expenseOnHold: 'This expense was put on hold. Review the comments for next steps.', + expensesOnHold: 'All expenses were put on hold. Review the comments for next steps.', confirmApprove: 'Confirm approval amount', confirmApprovalAmount: "Approve what's not on hold, or approve the entire report.", confirmPay: 'Confirm payment amount', diff --git a/src/languages/es.ts b/src/languages/es.ts index 536a6182f393..072f16880e8f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -743,6 +743,7 @@ export default { reason: 'Razón', holdReasonRequired: 'Se requiere una razón para bloquear.', expenseOnHold: 'Este gasto está bloqueado. Revisa los comentarios para saber como proceder.', + expensesOnHold: 'Todos los gastos quedaron bloqueado. Revisa los comentarios para saber como proceder.', confirmApprove: 'Confirmar importe a aprobar', confirmApprovalAmount: 'Aprueba lo que no está bloqueado, o aprueba todo el informe.', confirmPay: 'Confirmar importe de pago', From 411629b68f1684c759f234fb138e40bd714519d3 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 21 May 2024 14:09:13 +0700 Subject: [PATCH 029/163] run prettier --- src/components/MoneyReportHeader.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 155433bdcffb..7fd0e41ed910 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -146,7 +146,8 @@ function MoneyReportHeader({ const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; + const shouldShowNextStep = + !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); @@ -154,6 +155,9 @@ function MoneyReportHeader({ const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount; const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && shouldUseNarrowLayout); + // Shows border if no buttons or banners are showing below the header + const shouldShowBorderBottom = !(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; + const confirmPayment = (type?: PaymentMethodType | undefined) => { if (!type) { return; @@ -308,8 +312,7 @@ function MoneyReportHeader({ policy={policy} shouldShowBackButton={shouldUseNarrowLayout} onBackButtonPress={onBackButtonPress} - // Shows border if no buttons or next steps are showing below the header - shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation && !hasOnlyHeldExpenses} + shouldShowBorderBottom={shouldShowBorderBottom} shouldShowThreeDotsButton threeDotsMenuItems={threeDotsMenuItems} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)} @@ -388,7 +391,7 @@ function MoneyReportHeader({ )} {shouldShowNextStep && ( - + )} From 2d80900dddd46540d807d8138f1be571ee31ac37 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 22 May 2024 16:31:27 -0700 Subject: [PATCH 030/163] 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 031/163] 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 032/163] 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 033/163] 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 c01469768b88fc43f50466c23cfd85192fe08d4d Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 22 May 2024 16:54:48 -0700 Subject: [PATCH 034/163] Make sure that the connection sync progress times out after 20 minutes using timestamp --- src/CONST.ts | 1 + src/libs/actions/connections/QuickBooksOnline.ts | 1 + src/pages/workspace/accounting/PolicyAccountingPage.tsx | 4 +++- src/types/onyx/Policy.ts | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6517ece4276d..dbb9ec5fcaf7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1806,6 +1806,7 @@ const CONST = { XERO_CHECK_CONNECTION: 'xeroCheckConnection', XERO_SYNC_TITLE: 'xeroSyncTitle', }, + SYNC_STAGE_TIMEOUT: 1_200_000, // 20 minutes (20m * 60s * 1000ms) }, ACCESS_VARIANTS: { PAID: 'paid', diff --git a/src/libs/actions/connections/QuickBooksOnline.ts b/src/libs/actions/connections/QuickBooksOnline.ts index f507758e8d38..e0b11fbdc94c 100644 --- a/src/libs/actions/connections/QuickBooksOnline.ts +++ b/src/libs/actions/connections/QuickBooksOnline.ts @@ -21,6 +21,7 @@ function syncConnection(policyID: string) { value: { stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.STARTING_IMPORT, connectionName: CONST.POLICY.CONNECTIONS.NAME.QBO, + timestamp: Date.now(), }, }, ]; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index d6ef27c7b00d..763a0ee5c7d6 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -112,7 +112,9 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); const threeDotsMenuContainerRef = useRef(null); - const isSyncInProgress = !!connectionSyncProgress?.stageInProgress && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE; + const isSyncInProgress = !!connectionSyncProgress?.stageInProgress + && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE + && (Date.now() - (connectionSyncProgress.timestamp ?? 0)) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT; const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME).filter((name) => !(name === CONST.POLICY.CONNECTIONS.NAME.XERO && !canUseXeroIntegration)); const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]) ?? connectionSyncProgress?.connectionName; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 53c76286985d..1611e059a6fd 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -545,6 +545,7 @@ type PolicyConnectionName = ValueOf; type PolicyConnectionSyncProgress = { stageInProgress: PolicyConnectionSyncStage; connectionName: PolicyConnectionName; + timestamp: number; }; export default Policy; From 3b4af719500e5f236e3620fded7d0efba50db87f Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Wed, 22 May 2024 21:45:03 -0700 Subject: [PATCH 035/163] Prettier --- src/pages/workspace/accounting/PolicyAccountingPage.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 763a0ee5c7d6..6cdb05f3148c 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -112,9 +112,10 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); const threeDotsMenuContainerRef = useRef(null); - const isSyncInProgress = !!connectionSyncProgress?.stageInProgress - && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE - && (Date.now() - (connectionSyncProgress.timestamp ?? 0)) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT; + const isSyncInProgress = + !!connectionSyncProgress?.stageInProgress && + connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE && + Date.now() - (connectionSyncProgress.timestamp ?? 0) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT; const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME).filter((name) => !(name === CONST.POLICY.CONNECTIONS.NAME.XERO && !canUseXeroIntegration)); const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]) ?? connectionSyncProgress?.connectionName; From 496b79140ec23a7282cd81817d6260309f9a4158 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 23 May 2024 16:05:07 +0700 Subject: [PATCH 036/163] fix: enableDistanceRequestTax params --- src/libs/actions/Policy.ts | 9 ++++++--- .../distanceRates/PolicyDistanceRateDetailsPage.tsx | 3 +-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 1ef09f9aab09..ca1214043f46 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -4501,9 +4501,12 @@ function enableDistanceRequestTax(policyID: string, customUnitName: string, cust }; const params = { - customUnitName, - customUnitID, - attributes, + policyID, + customUnit: JSON.stringify({ + customUnitName, + customUnitID, + attributes, + }), }; API.write(WRITE_COMMANDS.ENABLE_DISTANCE_REQUEST_TAX, params, onyxData); } diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index 0feae31a175a..44169b4585c0 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -51,7 +51,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const customUnit = customUnits[Object.keys(customUnits)[0]]; const rate = customUnit?.rates[rateID]; const currency = rate?.currency ?? CONST.CURRENCY.USD; - + const isTrackTaxEnabled = customUnit.attributes.taxEnabled; const defaultTaxRateID = policy?.taxRates?.defaultExternalID ?? ''; const taxRate = `${policy?.taxRates?.taxes[defaultTaxRateID].name} (${policy?.taxRates?.taxes[defaultTaxRateID].value})`; @@ -66,7 +66,6 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail return result; }, [policy, defaultTaxRateID]); - // Rates can be disabled or deleted as long as in the remaining rates there is always at least one enabled rate and there are no pending delete action const canDisableOrDeleteRate = Object.values(customUnit?.rates).some( (distanceRate: Rate) => distanceRate?.enabled && rateID !== distanceRate?.customUnitRateID && distanceRate?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, From b32b35060b79f3461df9c24e9ff0df8a0e2ba6db Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 23 May 2024 16:28:05 +0700 Subject: [PATCH 037/163] fix: add navigation to more features --- .../distanceRates/PolicyDistanceRatesSettingsPage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx index 2190b495981a..9744f5e3cae9 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -14,12 +14,14 @@ import type {UnitItemType} from '@components/UnitPicker'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {CustomUnit} from '@src/types/onyx/Policy'; @@ -135,7 +137,7 @@ function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: Poli Taxes must be enabled on the workspace to use this feature. Head over to{' '} {}} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID))} > More features {' '} From debf40ea95275c403f8978061840a6f4fe5e59ba Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Thu, 23 May 2024 12:23:41 -0700 Subject: [PATCH 038/163] Use ISO timestamp --- src/CONST.ts | 2 +- src/libs/actions/connections/QuickBooksOnline.ts | 2 +- src/pages/workspace/accounting/PolicyAccountingPage.tsx | 4 +++- src/types/onyx/Policy.ts | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index dbb9ec5fcaf7..28e619bbd2bf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1806,7 +1806,7 @@ const CONST = { XERO_CHECK_CONNECTION: 'xeroCheckConnection', XERO_SYNC_TITLE: 'xeroSyncTitle', }, - SYNC_STAGE_TIMEOUT: 1_200_000, // 20 minutes (20m * 60s * 1000ms) + SYNC_STAGE_TIMEOUT_MINUTES: 20, }, ACCESS_VARIANTS: { PAID: 'paid', diff --git a/src/libs/actions/connections/QuickBooksOnline.ts b/src/libs/actions/connections/QuickBooksOnline.ts index e0b11fbdc94c..17920f27033b 100644 --- a/src/libs/actions/connections/QuickBooksOnline.ts +++ b/src/libs/actions/connections/QuickBooksOnline.ts @@ -21,7 +21,7 @@ function syncConnection(policyID: string) { value: { stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.STARTING_IMPORT, connectionName: CONST.POLICY.CONNECTIONS.NAME.QBO, - timestamp: Date.now(), + timestamp: new Date().toISOString(), }, }, ]; diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 6cdb05f3148c..95cd395dd628 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -39,6 +39,7 @@ import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; import type {PolicyConnectionName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; +import {differenceInMinutes, parseISO, isValid} from "date-fns"; type PolicyAccountingPageOnyxProps = { connectionSyncProgress: OnyxEntry; @@ -112,10 +113,11 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); const threeDotsMenuContainerRef = useRef(null); + const lastSyncProgressDate = parseISO(connectionSyncProgress?.timestamp ?? ''); const isSyncInProgress = !!connectionSyncProgress?.stageInProgress && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE && - Date.now() - (connectionSyncProgress.timestamp ?? 0) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT; + (isValid(lastSyncProgressDate) && differenceInMinutes(new Date(), lastSyncProgressDate) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT_MINUTES); const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME).filter((name) => !(name === CONST.POLICY.CONNECTIONS.NAME.XERO && !canUseXeroIntegration)); const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]) ?? connectionSyncProgress?.connectionName; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 1611e059a6fd..e61770aebc27 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -545,7 +545,7 @@ type PolicyConnectionName = ValueOf; type PolicyConnectionSyncProgress = { stageInProgress: PolicyConnectionSyncStage; connectionName: PolicyConnectionName; - timestamp: number; + timestamp: string; }; export default Policy; From daf4da7ce659b89df131ddd6b6d78f3541c92c87 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Thu, 23 May 2024 12:26:01 -0700 Subject: [PATCH 039/163] Prettier --- src/pages/workspace/accounting/PolicyAccountingPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/accounting/PolicyAccountingPage.tsx b/src/pages/workspace/accounting/PolicyAccountingPage.tsx index 95cd395dd628..285de304a26c 100644 --- a/src/pages/workspace/accounting/PolicyAccountingPage.tsx +++ b/src/pages/workspace/accounting/PolicyAccountingPage.tsx @@ -1,3 +1,4 @@ +import {differenceInMinutes, isValid, parseISO} from 'date-fns'; import React, {useMemo, useRef, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -39,7 +40,6 @@ import type {Policy, PolicyConnectionSyncProgress} from '@src/types/onyx'; import type {PolicyConnectionName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; -import {differenceInMinutes, parseISO, isValid} from "date-fns"; type PolicyAccountingPageOnyxProps = { connectionSyncProgress: OnyxEntry; @@ -117,7 +117,8 @@ function PolicyAccountingPage({policy, connectionSyncProgress, isConnectionDataF const isSyncInProgress = !!connectionSyncProgress?.stageInProgress && connectionSyncProgress.stageInProgress !== CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.JOB_DONE && - (isValid(lastSyncProgressDate) && differenceInMinutes(new Date(), lastSyncProgressDate) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT_MINUTES); + isValid(lastSyncProgressDate) && + differenceInMinutes(new Date(), lastSyncProgressDate) < CONST.POLICY.CONNECTIONS.SYNC_STAGE_TIMEOUT_MINUTES; const accountingIntegrations = Object.values(CONST.POLICY.CONNECTIONS.NAME).filter((name) => !(name === CONST.POLICY.CONNECTIONS.NAME.XERO && !canUseXeroIntegration)); const connectedIntegration = accountingIntegrations.find((integration) => !!policy?.connections?.[integration]) ?? connectionSyncProgress?.connectionName; From 960722c3e3abb1db27f4ea366d81a0690192781b Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Fri, 24 May 2024 17:23:32 +0200 Subject: [PATCH 040/163] Adjust Section to accept illustrations in .svg format --- .../emptystate__travel.svg | 575 ++++++++++++++++++ src/components/FeatureList.tsx | 2 +- src/components/Icon/Illustrations.ts | 2 + src/components/Section/index.tsx | 34 +- src/pages/Travel/ManageTrips.tsx | 10 +- 5 files changed, 608 insertions(+), 15 deletions(-) create mode 100644 assets/images/product-illustrations/emptystate__travel.svg diff --git a/assets/images/product-illustrations/emptystate__travel.svg b/assets/images/product-illustrations/emptystate__travel.svg new file mode 100644 index 000000000000..416b27eb5bee --- /dev/null +++ b/assets/images/product-illustrations/emptystate__travel.svg @@ -0,0 +1,575 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index 5e4ab89cf150..2fae184cae6a 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -36,7 +36,7 @@ type FeatureListProps = { menuItems: FeatureListItem[]; /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ - illustration: DotLottieAnimation; + illustration: DotLottieAnimation | IconAsset; /** The style passed to the illustration */ illustrationStyle?: StyleProp; diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 8d3f53be9396..189f5d8778fc 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -6,6 +6,7 @@ import ConciergeBlue from '@assets/images/product-illustrations/concierge--blue. import ConciergeExclamation from '@assets/images/product-illustrations/concierge--exclamation.svg'; import CreditCardsBlue from '@assets/images/product-illustrations/credit-cards--blue.svg'; import EmptyStateExpenses from '@assets/images/product-illustrations/emptystate__expenses.svg'; +import EmptyStateTravel from '@assets/images/product-illustrations/emptystate__travel.svg'; import GpsTrackOrange from '@assets/images/product-illustrations/gps-track--orange.svg'; import Hands from '@assets/images/product-illustrations/home-illustration-hands.svg'; import InvoiceOrange from '@assets/images/product-illustrations/invoice--orange.svg'; @@ -176,4 +177,5 @@ export { Tag, CarIce, Lightbulb, + EmptyStateTravel, }; diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 7f7d759c72aa..20c46976a4cf 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -3,6 +3,7 @@ import type {ReactNode} from 'react'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; +import ImageSVG from '@components/ImageSVG'; import Lottie from '@components/Lottie'; import type DotLottieAnimation from '@components/LottieAnimations/types'; import type {MenuItemWithLink} from '@components/MenuItemList'; @@ -60,7 +61,7 @@ type SectionProps = ChildrenProps & { isCentralPane?: boolean; /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ - illustration?: DotLottieAnimation; + illustration?: DotLottieAnimation | IconAsset; /** The background color to apply in the upper half of the screen. */ illustrationBackgroundColor?: string; @@ -78,6 +79,10 @@ type SectionProps = ChildrenProps & { renderSubtitle?: () => ReactNode; }; +function isIllustrationLottieAnimation(illustration: DotLottieAnimation | IconAsset | undefined): illustration is DotLottieAnimation { + return typeof illustration === 'object'; +} + function Section({ children, childrenStyles, @@ -104,7 +109,11 @@ function Section({ const StyleUtils = useStyleUtils(); const {isSmallScreenWidth} = useWindowDimensions(); - const illustrationContainerStyle: StyleProp = StyleUtils.getBackgroundColorStyle(illustrationBackgroundColor ?? illustration?.backgroundColor ?? theme.appBG); + const isLottie = isIllustrationLottieAnimation(illustration); + + const lottieIllustration = isLottie ? illustration : undefined; + + const illustrationContainerStyle: StyleProp = StyleUtils.getBackgroundColorStyle(illustrationBackgroundColor ?? lottieIllustration?.backgroundColor ?? theme.appBG); return ( @@ -117,13 +126,20 @@ function Section({ {!!illustration && ( - + {isLottie ? ( + + ) : ( + + )} {overlayContent?.()} diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx index 401f06277b5e..f8ad8d619619 100644 --- a/src/pages/Travel/ManageTrips.tsx +++ b/src/pages/Travel/ManageTrips.tsx @@ -3,7 +3,6 @@ import {View} from 'react-native'; import type {FeatureListItem} from '@components/FeatureList'; import FeatureList from '@components/FeatureList'; import * as Illustrations from '@components/Icon/Illustrations'; -import LottieAnimations from '@components/LottieAnimations'; import ScrollView from '@components/ScrollView'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -11,7 +10,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import colors from '@styles/theme/colors'; import ROUTES from '@src/ROUTES'; -import getTripIllustrationStyle from './getTripIllustrationStyle'; const tripsFeatures: FeatureListItem[] = [ { @@ -24,11 +22,13 @@ const tripsFeatures: FeatureListItem[] = [ }, ]; +const ILLUSTRATION_WIDTH = 190; +const ILLUSTRATION_HEIGHT = 172; + function ManageTrips() { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - const illustrationStyle = getTripIllustrationStyle(); return ( @@ -42,8 +42,8 @@ function ManageTrips() { onCtaPress={() => { Navigation.navigate(ROUTES.TRAVEL_TCS); }} - illustration={LottieAnimations.Plane} - illustrationStyle={illustrationStyle} + illustration={Illustrations.EmptyStateTravel} + illustrationStyle={[styles.mv4, {width: ILLUSTRATION_WIDTH, height: ILLUSTRATION_HEIGHT}]} illustrationBackgroundColor={colors.blue600} titleStyles={styles.textHeadlineH1} contentPaddingOnLargeScreens={styles.p5} From 6d28766569f0ecc749cd01fbf1663e4181f17e63 Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 27 May 2024 11:05:11 +0700 Subject: [PATCH 041/163] clean code --- src/libs/OptionsListUtils.ts | 18 ++++++++++++------ src/libs/SidebarUtils.ts | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index ac5799a120bb..bc5665c3d400 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -1650,14 +1650,20 @@ function getUserToInviteOption({ } /** - * check whether report has violations + * Check whether report has violations */ -function checkReportHasViolations(report: Report, betas: OnyxEntry, transactionViolations: OnyxCollection) { +function shouldShowViolations(report: Report, betas: OnyxEntry, transactionViolations: OnyxCollection) { + if (!Permissions.canUseViolations(betas)) { + return false; + } const {parentReportID, parentReportActionID} = report ?? {}; const canGetParentReport = parentReportID && parentReportActionID && allReportActions; + if (!canGetParentReport) { + return false; + } const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {}; - const parentReportAction = canGetParentReport ? parentReportActions[parentReportActionID] ?? null : null; - return (Permissions.canUseViolations(betas) && !!parentReportAction && ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction)) ?? false; + const parentReportAction = parentReportActions[parentReportActionID] ?? null; + return (!!parentReportAction && ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction)) ?? false; } /** @@ -1767,7 +1773,7 @@ function getOptions( // Filter out all the reports that shouldn't be displayed const filteredReportOptions = options.reports.filter((option) => { const report = option.item; - const doesReportHaveViolations = checkReportHasViolations(report, betas, transactionViolations); + const doesReportHaveViolations = shouldShowViolations(report, betas, transactionViolations); return ReportUtils.shouldReportBeInOptionList({ report, @@ -2449,7 +2455,7 @@ export { getTaxRatesSection, getFirstKeyForList, getUserToInviteOption, - checkReportHasViolations, + shouldShowViolations, }; export type {MemberForList, CategorySection, CategoryTreeSection, Options, OptionList, SearchOption, PayeePersonalDetails, Category, Tax, TaxRatesOption, Option, OptionTree}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3ebf5ab5783a..543d6228506d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -89,7 +89,7 @@ function getOrderedReportIDs( } const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; - const doesReportHaveViolations = OptionsListUtils.checkReportHasViolations(report, betas ?? [], transactionViolations); + const doesReportHaveViolations = OptionsListUtils.shouldShowViolations(report, betas ?? [], transactionViolations); const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; From 0b6cdc2587f659100233b1e5a806371d0509d107 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 27 May 2024 10:27:27 +0200 Subject: [PATCH 042/163] Add trip illustration fixes --- src/components/FeatureList.tsx | 2 +- src/components/Section/index.tsx | 10 +++++++--- src/pages/Travel/ManageTrips.tsx | 5 +---- .../Travel/getTripIllustrationStyle/index.native.ts | 8 -------- src/pages/Travel/getTripIllustrationStyle/index.ts | 9 --------- src/styles/index.ts | 5 +++++ 6 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 src/pages/Travel/getTripIllustrationStyle/index.native.ts delete mode 100644 src/pages/Travel/getTripIllustrationStyle/index.ts diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index 2fae184cae6a..132d58dec218 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -35,7 +35,7 @@ type FeatureListProps = { /** A list of menuItems representing the feature list. */ menuItems: FeatureListItem[]; - /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ + /** The illustration to display in the header. Can be a JSON object representing a Lottie animation or an image. */ illustration: DotLottieAnimation | IconAsset; /** The style passed to the illustration */ diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index 20c46976a4cf..be9f7819ee42 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import type {ReactNode} from 'react'; +import React from 'react'; import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; @@ -60,7 +60,7 @@ type SectionProps = ChildrenProps & { /** Whether the section is in the central pane of the layout */ isCentralPane?: boolean; - /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */ + /** The illustration to display in the header. Can be a JSON object representing a Lottie animation or an image. */ illustration?: DotLottieAnimation | IconAsset; /** The background color to apply in the upper half of the screen. */ @@ -80,7 +80,11 @@ type SectionProps = ChildrenProps & { }; function isIllustrationLottieAnimation(illustration: DotLottieAnimation | IconAsset | undefined): illustration is DotLottieAnimation { - return typeof illustration === 'object'; + if (typeof illustration === 'number' || !illustration) { + return false; + } + + return 'file' in illustration && 'w' in illustration && 'h' in illustration; } function Section({ diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx index f8ad8d619619..ba6eea16c8cf 100644 --- a/src/pages/Travel/ManageTrips.tsx +++ b/src/pages/Travel/ManageTrips.tsx @@ -22,9 +22,6 @@ const tripsFeatures: FeatureListItem[] = [ }, ]; -const ILLUSTRATION_WIDTH = 190; -const ILLUSTRATION_HEIGHT = 172; - function ManageTrips() { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -43,7 +40,7 @@ function ManageTrips() { Navigation.navigate(ROUTES.TRAVEL_TCS); }} illustration={Illustrations.EmptyStateTravel} - illustrationStyle={[styles.mv4, {width: ILLUSTRATION_WIDTH, height: ILLUSTRATION_HEIGHT}]} + illustrationStyle={[styles.mv4, styles.tripIllustrationSize]} illustrationBackgroundColor={colors.blue600} titleStyles={styles.textHeadlineH1} contentPaddingOnLargeScreens={styles.p5} diff --git a/src/pages/Travel/getTripIllustrationStyle/index.native.ts b/src/pages/Travel/getTripIllustrationStyle/index.native.ts deleted file mode 100644 index e5b0a1381d7e..000000000000 --- a/src/pages/Travel/getTripIllustrationStyle/index.native.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type {ViewStyle} from 'react-native'; - -// Styling lottie animations for the ManageTrips component requires different margin values depending on the platform. -export default function getTripIllustrationStyle(): ViewStyle { - return { - marginVertical: 20, - }; -} diff --git a/src/pages/Travel/getTripIllustrationStyle/index.ts b/src/pages/Travel/getTripIllustrationStyle/index.ts deleted file mode 100644 index a2a141022d73..000000000000 --- a/src/pages/Travel/getTripIllustrationStyle/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type {ViewStyle} from 'react-native'; - -// Styling lottie animations for the ManageTrips component requires different margin values depending on the platform. -export default function getTripIllustrationStyle(): ViewStyle { - return { - marginTop: 20, - marginBottom: -20, - }; -} diff --git a/src/styles/index.ts b/src/styles/index.ts index f12b3856de9d..60e2a90ba515 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4911,6 +4911,11 @@ const styles = (theme: ThemeColors) => textLineThrough: { textDecorationLine: 'line-through', }, + + tripIllustrationSize: { + width: 190, + height: 172, + }, } satisfies Styles); type ThemeStyles = ReturnType; From a78a892ec912ee6ce6979247dd7d427711202ac2 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Mon, 27 May 2024 10:38:03 +0200 Subject: [PATCH 043/163] Refactor docs --- src/components/FeatureList.tsx | 2 +- src/components/Section/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index 132d58dec218..5ff417508d45 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -35,7 +35,7 @@ type FeatureListProps = { /** A list of menuItems representing the feature list. */ menuItems: FeatureListItem[]; - /** The illustration to display in the header. Can be a JSON object representing a Lottie animation or an image. */ + /** The illustration to display in the header. Can be an image or a JSON object representing a Lottie animation. */ illustration: DotLottieAnimation | IconAsset; /** The style passed to the illustration */ diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx index be9f7819ee42..3bc0b6b44250 100644 --- a/src/components/Section/index.tsx +++ b/src/components/Section/index.tsx @@ -60,7 +60,7 @@ type SectionProps = ChildrenProps & { /** Whether the section is in the central pane of the layout */ isCentralPane?: boolean; - /** The illustration to display in the header. Can be a JSON object representing a Lottie animation or an image. */ + /** The illustration to display in the header. Can be an image or a JSON object representing a Lottie animation. */ illustration?: DotLottieAnimation | IconAsset; /** The background color to apply in the upper half of the screen. */ From ce34f6b5fc4e48a14371c69a701e9dee219a6b8c Mon Sep 17 00:00:00 2001 From: Tienifr <113963320+tienifr@users.noreply.github.com> Date: Mon, 27 May 2024 17:39:03 +0700 Subject: [PATCH 044/163] Modify comment Co-authored-by: Nikki Wines --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 19dfc4308fa3..e132d3e2dc3b 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -38,7 +38,7 @@ type MoneyReportHeaderOnyxProps = { /** The chat report this report is linked to */ chatReport: OnyxEntry; - /** All the data for the transaction in one transaction view */ + /** All the data for the transaction in the one transaction view */ transaction: OnyxEntry; /** The next step for the report */ From 386bd5d218f8ba36d1c9fd6bfc9acb4454a12e3d Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 28 May 2024 00:00:48 +0700 Subject: [PATCH 045/163] feature: add tax claimable in distance rates --- src/ONYXKEYS.ts | 3 ++ src/languages/en.ts | 2 + src/libs/API/types.ts | 1 + src/libs/PolicyDistanceRatesUtils.ts | 15 ++++++- .../PolicyDistanceRateDetailsPage.tsx | 9 ++-- ...cyDistanceRateTaxReclaimableOnEditPage.tsx | 44 ++++++++++--------- ...icyDistanceRateTaxReclaimableOnEditForm.ts | 18 ++++++++ src/types/form/index.ts | 1 + src/types/onyx/Policy.ts | 4 ++ 9 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 src/types/form/PolicyDistanceRateTaxReclaimableOnEditForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index ddf37fba2354..1aff574d63c2 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -391,6 +391,8 @@ const ONYXKEYS = { POLICY_CREATE_DISTANCE_RATE_FORM: 'policyCreateDistanceRateForm', POLICY_CREATE_DISTANCE_RATE_FORM_DRAFT: 'policyCreateDistanceRateFormDraft', POLICY_DISTANCE_RATE_EDIT_FORM: 'policyDistanceRateEditForm', + POLICY_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT_FORM: 'policyDistanceRateTaxReclaimableOnEditForm', + POLICY_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT_FORM_DRAFT: 'policyDistanceRateTaxReclaimableOnEditFormDraft', POLICY_DISTANCE_RATE_EDIT_FORM_DRAFT: 'policyDistanceRateEditFormDraft', CLOSE_ACCOUNT_FORM: 'closeAccount', CLOSE_ACCOUNT_FORM_DRAFT: 'closeAccountDraft', @@ -530,6 +532,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.WORKSPACE_NEW_TAX_FORM]: FormTypes.WorkspaceNewTaxForm; [ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM]: FormTypes.PolicyCreateDistanceRateForm; [ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM]: FormTypes.PolicyDistanceRateEditForm; + [ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT_FORM]: FormTypes.PolicyDistanceRateTaxReclaimableOnEditForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_NAME_FORM]: FormTypes.WorkspaceTaxNameForm; [ONYXKEYS.FORMS.WORKSPACE_TAX_VALUE_FORM]: FormTypes.WorkspaceTaxValueForm; [ONYXKEYS.FORMS.NEW_CHAT_NAME_FORM]: FormTypes.NewChatNameForm; diff --git a/src/languages/en.ts b/src/languages/en.ts index 85470bd17a67..818b44243dcd 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2221,6 +2221,8 @@ export default { foreignDefault: 'Foreign currency default', customTaxName: 'Custom tax name', value: 'Value', + taxReclaimableOn: 'Tax reclaimable on', + taxRate: 'Tax rate', error: { taxRateAlreadyExists: 'This tax name is already in use.', valuePercentageRange: 'Please enter a valid percentage between 0 and 100.', diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 153de74dd1ec..88eeaf7eeef0 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -421,6 +421,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.RENAME_POLICY_TAX]: Parameters.RenamePolicyTaxParams; [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_UNIT]: Parameters.SetPolicyDistanceRatesUnitParams; [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_DEFAULT_CATEGORY]: Parameters.SetPolicyDistanceRatesDefaultCategoryParams; + [WRITE_COMMANDS.ENABLE_DISTANCE_REQUEST_TAX]: Parameters.SetPolicyDistanceRatesDefaultCategoryParams; // eslint-disable-next-line @typescript-eslint/no-explicit-any [WRITE_COMMANDS.UPDATE_POLICY_CONNECTION_CONFIG]: Parameters.UpdatePolicyConnectionConfigParams; diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index db739cc3b8c7..ae473923c34e 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -9,6 +9,8 @@ import * as NumberUtils from './NumberUtils'; type RateValueForm = typeof ONYXKEYS.FORMS.WORKSPACE_RATE_AND_UNIT_FORM | typeof ONYXKEYS.FORMS.POLICY_CREATE_DISTANCE_RATE_FORM | typeof ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_EDIT_FORM; +type TaxReclaimableForm = typeof ONYXKEYS.FORMS.POLICY_DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT_FORM; + function validateRateValue(values: FormOnyxValues, currency: string, toLocaleDigit: (arg: string) => string): FormInputErrors { const errors: FormInputErrors = {}; const parsedRate = MoneyRequestUtils.replaceAllDigits(values.rate, toLocaleDigit); @@ -24,6 +26,17 @@ function validateRateValue(values: FormOnyxValues, currency: stri return errors; } +function validateTaxClaimableValue(values: FormOnyxValues, rate: Rate): FormInputErrors { + const errors: FormInputErrors = {}; + if (rate.rate) { + return errors; + } + if (rate.rate && Number(values.taxClaimableValue) > rate.rate / 100) { + errors.taxClaimableValue = 'workspace.reimburse.invalidRateError'; + } + return errors; +} + /** * Get the optimistic rate name in a way that matches BE logic * @param rates @@ -33,4 +46,4 @@ function getOptimisticRateName(rates: Record): string { return existingRatesWithSameName.length ? `${CONST.CUSTOM_UNITS.DEFAULT_RATE} ${existingRatesWithSameName.length}` : CONST.CUSTOM_UNITS.DEFAULT_RATE; } -export {validateRateValue, getOptimisticRateName}; +export {validateRateValue, getOptimisticRateName, validateTaxClaimableValue}; diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index d85d5c879980..ad833e285f9f 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -51,6 +51,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const customUnit = customUnits[Object.keys(customUnits)[0]]; const rate = customUnit?.rates[rateID]; const currency = rate?.currency ?? CONST.CURRENCY.USD; + const taxClaimablePercentage = rate.attributes?.taxClaimablePercentage; const isTrackTaxEnabled = customUnit.attributes.taxEnabled; const defaultTaxRateID = policy?.taxRates?.defaultExternalID ?? ''; @@ -98,6 +99,8 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail }; const rateValueToDisplay = CurrencyUtils.convertAmountToDisplayString(rate?.rate, currency); + const taxClaimableValueToDisplay = + taxClaimablePercentage && rate.rate ? `${CurrencyUtils.getLocalizedCurrencySymbol(currency)}${((taxClaimablePercentage * rate.rate) / 100).toFixed(1)}` : ''; const unitToDisplay = translate(`common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`); const threeDotsMenuItems = [ @@ -170,7 +173,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail setIsTaxRateSelectionModalVisible(true)} /> @@ -191,8 +194,8 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail > diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx index 67c497281236..4578c2ae23ad 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx @@ -13,26 +13,27 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import {validateRateValue} from '@libs/PolicyDistanceRatesUtils'; +import {validateTaxClaimableValue} from '@libs/PolicyDistanceRatesUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/PolicyDistanceRateEditForm'; +import INPUT_IDS from '@src/types/form/PolicyDistanceRateTaxReclaimableOnEditForm'; import type * as OnyxTypes from '@src/types/onyx'; -type PolicyDistanceRateEditPageOnyxProps = { +type PolicyDistanceRateTaxReclaimableOnEditPageOnyxProps = { /** Policy details */ policy: OnyxEntry; }; -type PolicyDistanceRateEditPageProps = PolicyDistanceRateEditPageOnyxProps & StackScreenProps; +type PolicyDistanceRateTaxReclaimableOnEditPageProps = PolicyDistanceRateTaxReclaimableOnEditPageOnyxProps & + StackScreenProps; -function PolicyDistanceRateTaxReclaimableOnEditPage({policy, route}: PolicyDistanceRateEditPageProps) { +function PolicyDistanceRateTaxReclaimableOnEditPage({policy, route}: PolicyDistanceRateTaxReclaimableOnEditPageProps) { const styles = useThemeStyles(); - const {translate, toLocaleDigit} = useLocalize(); + const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const policyID = route.params.policyID; @@ -41,18 +42,21 @@ function PolicyDistanceRateTaxReclaimableOnEditPage({policy, route}: PolicyDista const customUnit = customUnits[Object.keys(customUnits)[0]]; const rate = customUnit.rates[rateID]; const currency = rate.currency ?? CONST.CURRENCY.USD; - const currentRateValue = (rate.rate ?? 0).toString(); - - const submitRate = (values: FormOnyxValues) => { - Policy.updatePolicyDistanceRateValue(policyID, customUnit, [{...rate, rate: Number(values.rate) * CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET}]); + const currentTaxReclaimableOnValue = rate.attributes?.taxClaimablePercentage && rate.rate ? (rate.attributes.taxClaimablePercentage * rate.rate) / 100 : ''; + const submitTaxReclaimableOn = (values: FormOnyxValues) => { + Policy.updatePolicyDistanceRateValue(policyID, customUnit, [ + { + ...rate, + attributes: { + taxClaimablePercentage: rate.rate ? (Number(values.taxClaimableValue) * 100) / rate.rate : undefined, + }, + }, + ]); Keyboard.dismiss(); Navigation.goBack(); }; - const validate = useCallback( - (values: FormOnyxValues) => validateRateValue(values, currency, toLocaleDigit), - [currency, toLocaleDigit], - ); + const validate = useCallback((values: FormOnyxValues) => validateTaxClaimableValue(values, rate), [rate]); return ( Navigation.goBack()} /> ({ +export default withOnyx({ policy: { key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, }, diff --git a/src/types/form/PolicyDistanceRateTaxReclaimableOnEditForm.ts b/src/types/form/PolicyDistanceRateTaxReclaimableOnEditForm.ts new file mode 100644 index 000000000000..b41facf3604f --- /dev/null +++ b/src/types/form/PolicyDistanceRateTaxReclaimableOnEditForm.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + TAX_CLAIMABLE_VALUE: 'taxClaimableValue', +} as const; + +type InputID = ValueOf; + +type PolicyDistanceRateTaxReclaimableOnEditForm = Form< + InputID, + { + [INPUT_IDS.TAX_CLAIMABLE_VALUE]: string; + } +>; + +export type {PolicyDistanceRateTaxReclaimableOnEditForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index b3e46733a44e..16ecea881328 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -46,6 +46,7 @@ export type {WorkspaceTaxNameForm} from './WorkspaceTaxNameForm'; export type {WorkspaceTaxValueForm} from './WorkspaceTaxValueForm'; export type {WorkspaceTaxCustomName} from './WorkspaceTaxCustomName'; export type {PolicyCreateDistanceRateForm} from './PolicyCreateDistanceRateForm'; +export type {PolicyDistanceRateTaxReclaimableOnEditForm} from './PolicyDistanceRateTaxReclaimableOnEditForm'; export type {PolicyDistanceRateEditForm} from './PolicyDistanceRateEditForm'; export type {WalletAdditionalDetailsForm} from './WalletAdditionalDetailsForm'; export type {NewChatNameForm} from './NewChatNameForm'; diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index be318342eeff..80b7dcb5f217 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -15,6 +15,10 @@ type Rate = OnyxCommon.OnyxValueWithOfflineFeedback<{ enabled?: boolean; errors?: OnyxCommon.Errors; errorFields?: OnyxCommon.ErrorFields; + attributes?: { + taxClaimablePercentage?: number; + taxRateExternalID?: string; + }; }>; type Attributes = { From d0d7f9c066df5c3d613a7e911530ad3e2cd652f1 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 28 May 2024 00:15:06 +0700 Subject: [PATCH 046/163] init shownHoldUseExplanation as false --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 1bd653ed7e08..c4b1b56fa452 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -67,7 +67,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID) as OnyxTypes.ReportAction & OnyxTypes.OriginalMessageIOU; }, [reportActions, transactionThreadReport?.parentReportActionID]); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${requestParentReportAction?.originalMessage?.IOUTransactionID ?? 0}`); - const [shownHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, {initWithStoredValues: true}); + const [shownHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, {initWithStoredValues: false}); const styles = useThemeStyles(); const theme = useTheme(); From d2ab2c146c99c582dcd3c9bafd3fc3219bd13d95 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 28 May 2024 00:25:19 +0700 Subject: [PATCH 047/163] fix: add tax rate in distace rate unit --- src/languages/en.ts | 1 + src/libs/PolicyDistanceRatesUtils.ts | 6 ++--- .../PolicyDistanceRateDetailsPage.tsx | 27 ++++++++++++++----- ...olicyDistanceRateTaxRateSelectionModal.tsx | 6 ++--- ...cyDistanceRateTaxReclaimableOnEditPage.tsx | 1 + 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 818b44243dcd..e63b9ccff8d2 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2230,6 +2230,7 @@ export default { deleteFailureMessage: 'An error occurred while deleting the tax rate. Please try again or ask Concierge for help.', updateFailureMessage: 'An error occurred while updating the tax rate. Please try again or ask Concierge for help.', createFailureMessage: 'An error occurred while creating the tax rate. Please try again or ask Concierge for help.', + updateTaxClaimableFailureMessage: 'The reclaimable portion must be less than the distance rate amount.', }, deleteTaxConfirmation: 'Are you sure you want to delete this tax?', deleteMultipleTaxConfirmation: ({taxAmount}) => `Are you sure you want to delete ${taxAmount} taxes?`, diff --git a/src/libs/PolicyDistanceRatesUtils.ts b/src/libs/PolicyDistanceRatesUtils.ts index ae473923c34e..b61f9d684a2f 100644 --- a/src/libs/PolicyDistanceRatesUtils.ts +++ b/src/libs/PolicyDistanceRatesUtils.ts @@ -28,11 +28,11 @@ function validateRateValue(values: FormOnyxValues, currency: stri function validateTaxClaimableValue(values: FormOnyxValues, rate: Rate): FormInputErrors { const errors: FormInputErrors = {}; - if (rate.rate) { + if (!rate.rate) { return errors; } - if (rate.rate && Number(values.taxClaimableValue) > rate.rate / 100) { - errors.taxClaimableValue = 'workspace.reimburse.invalidRateError'; + if (Number(values.taxClaimableValue) > rate.rate / 100) { + errors.taxClaimableValue = 'workspace.taxes.error.updateTaxClaimableFailureMessage'; } return errors; } diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index ad833e285f9f..0b4e71f52e84 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -1,6 +1,6 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useMemo, useState} from 'react'; -import {View} from 'react-native'; +import {Keyboard, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; @@ -52,20 +52,20 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail const rate = customUnit?.rates[rateID]; const currency = rate?.currency ?? CONST.CURRENCY.USD; const taxClaimablePercentage = rate.attributes?.taxClaimablePercentage; + const taxRateExternalID = rate.attributes?.taxRateExternalID; const isTrackTaxEnabled = customUnit.attributes.taxEnabled; - const defaultTaxRateID = policy?.taxRates?.defaultExternalID ?? ''; - const taxRate = `${policy?.taxRates?.taxes[defaultTaxRateID].name} (${policy?.taxRates?.taxes[defaultTaxRateID].value})`; + const taxRate = taxRateExternalID ? `${policy?.taxRates?.taxes[taxRateExternalID].name} (${policy?.taxRates?.taxes[taxRateExternalID].value})` : ''; const taxRateItems: ListItemType[] = useMemo(() => { const taxes = policy?.taxRates?.taxes; const result = Object.entries(taxes ?? {}).map(([key, value]) => ({ - value: value.value, + value: key, text: `${value.name} (${value.value})`, - isSelected: defaultTaxRateID === key, + isSelected: taxRateExternalID === key, keyForList: key, })); return result; - }, [policy, defaultTaxRateID]); + }, [policy, taxRateExternalID]); // Rates can be disabled or deleted as long as in the remaining rates there is always at least one enabled rate and there are no pending delete action const canDisableOrDeleteRate = Object.values(customUnit?.rates).some( @@ -98,6 +98,19 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail setIsDeleteModalVisible(false); }; + const onTaxRateChange = (newTaxRate: ListItemType) => { + Policy.updatePolicyDistanceRateValue(policyID, customUnit, [ + { + ...rate, + attributes: { + ...rate.attributes, + taxRateExternalID: newTaxRate.value, + }, + }, + ]); + Keyboard.dismiss(); + }; + const rateValueToDisplay = CurrencyUtils.convertAmountToDisplayString(rate?.rate, currency); const taxClaimableValueToDisplay = taxClaimablePercentage && rate.rate ? `${CurrencyUtils.getLocalizedCurrencySymbol(currency)}${((taxClaimablePercentage * rate.rate) / 100).toFixed(1)}` : ''; @@ -180,7 +193,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail {}} + onTaxRateChange={onTaxRateChange} onClose={() => setIsTaxRateSelectionModalVisible(false)} /> diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx index 1a7df8b437fc..e10f9461b466 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxRateSelectionModal.tsx @@ -5,7 +5,7 @@ import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; -// import useLocalize from '@hooks/useLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -31,7 +31,7 @@ type WorkspaceMemberDetailsPageProps = { }; function PolicyDistanceRateTaxRateSelectionModal({isVisible, items, onTaxRateChange, onClose}: WorkspaceMemberDetailsPageProps) { - // const {translate} = useLocalize(); + const {translate} = useLocalize(); const styles = useThemeStyles(); return ( @@ -48,7 +48,7 @@ function PolicyDistanceRateTaxRateSelectionModal({isVisible, items, onTaxRateCha includePaddingTop={false} > diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx index 4578c2ae23ad..bafe4c15ba82 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateTaxReclaimableOnEditPage.tsx @@ -48,6 +48,7 @@ function PolicyDistanceRateTaxReclaimableOnEditPage({policy, route}: PolicyDista { ...rate, attributes: { + ...rate.attributes, taxClaimablePercentage: rate.rate ? (Number(values.taxClaimableValue) * 100) / rate.rate : undefined, }, }, From 37b65f75fdd4f325fca1d638f7761dc9faa96f33 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 28 May 2024 00:32:23 +0700 Subject: [PATCH 048/163] update en.ts --- src/languages/es.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/languages/es.ts b/src/languages/es.ts index e0349d00e706..8eeb3d169997 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2257,6 +2257,8 @@ export default { foreignDefault: 'Moneda extranjera por defecto', customTaxName: 'Nombre del impuesto', value: 'Valor', + taxRate: 'Tasa de impuesto', + taxReclaimableOn: 'Impuesto recuperable en', error: { taxRateAlreadyExists: 'Ya existe un impuesto con este nombre.', customNameRequired: 'El nombre del impuesto es obligatorio.', @@ -2264,6 +2266,7 @@ export default { deleteFailureMessage: 'Se ha producido un error al intentar eliminar la tasa de impuesto. Por favor, inténtalo más tarde.', updateFailureMessage: 'Se ha producido un error al intentar modificar la tasa de impuesto. Por favor, inténtalo más tarde.', createFailureMessage: 'Se ha producido un error al intentar crear la tasa de impuesto. Por favor, inténtalo más tarde.', + updateTaxClaimableFailureMessage: 'La porción reclamable debe ser menor que el monto de la tarifa a distancia.', }, deleteTaxConfirmation: '¿Estás seguro de que quieres eliminar este impuesto?', deleteMultipleTaxConfirmation: ({taxAmount}) => `¿Estás seguro de que quieres eliminar ${taxAmount} impuestos?`, From ac9da5c4d108d89ecb9ad04f61f397c3a6d95174 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 28 May 2024 01:59:41 +0700 Subject: [PATCH 049/163] remove bottom border and padding from moneyrequestheaderstatusbar --- src/components/MoneyReportHeader.tsx | 95 ++++++++----------- src/components/MoneyRequestHeader.tsx | 25 +++-- .../MoneyRequestHeaderStatusBar.tsx | 20 +--- src/pages/iou/SplitBillDetailsPage.tsx | 13 +-- 4 files changed, 62 insertions(+), 91 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index c4b1b56fa452..aac866b249b4 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -123,17 +123,17 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const shouldShowMarkAsCashButton = isDraft && allHavePendingRTERViolation && !hasOnlyHeldExpenses; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const shouldShowNextStep = - !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; + const shouldShowStatusBar = allHavePendingRTERViolation || hasOnlyHeldExpenses; + const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar; const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount; - const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && shouldUseNarrowLayout); + const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); // Shows border if no buttons or banners are showing below the header - const shouldShowBorderBottom = !(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation && !hasOnlyHeldExpenses; + const shouldShowBorderBottom = !(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !shouldShowStatusBar; const confirmPayment = (type?: PaymentMethodType | undefined) => { if (!type) { @@ -205,10 +205,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => { if (hasOnlyHeldExpenses) { - return {title: translate('iou.hold'), description: translate('iou.expensesOnHold'), danger: true, shouldShowBorderBottom: true}; + return {title: translate('iou.hold'), description: translate('iou.expensesOnHold'), danger: true}; } if (allHavePendingRTERViolation) { - return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true}; + return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } }; @@ -348,8 +348,35 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea )} - {shouldShowMarkAsCashButton && shouldUseNarrowLayout && ( - + + {shouldShowSettlementButton && shouldUseNarrowLayout && ( + + )} + {shouldShowSubmitButton && shouldUseNarrowLayout && ( +