From 78f25f727d2f6da1d65c1a50e6af6e2496ce0393 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 31 May 2024 11:59:06 +0530 Subject: [PATCH 01/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 20 +++++++++++++++++-- .../MoneyRequestConfirmationList.tsx | 5 ++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 48f81923a663..b761a5374b64 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -82,6 +82,9 @@ type MoneyRequestAmountInputProps = { * Autogrow input container length based on the entered text. */ autoGrow?: boolean; + + /** Flag indicating if the reset button was clicked to reset the amount */ + resetClicked?: boolean; }; type Selection = { @@ -116,6 +119,7 @@ function MoneyRequestAmountInput( maxLength, hideFocusedState = true, autoGrow = true, + resetClicked = false, ...props }: MoneyRequestAmountInputProps, forwardedRef: ForwardedRef, @@ -135,6 +139,7 @@ function MoneyRequestAmountInput( }); const forwardDeletePressedRef = useRef(false); + const resetFlag = useRef(false); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -165,8 +170,11 @@ function MoneyRequestAmountInput( hasSelectionBeenSet = true; setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); } - onAmountChange?.(strippedAmount); - return strippedAmount; + const resultAmount = resetFlag.current ? selectedAmountAsString : strippedAmount; + onAmountChange?.(resultAmount); + + resetFlag.current = false; // Reset the flag after handling + return resultAmount; }); }, [decimals, onAmountChange], @@ -189,6 +197,14 @@ function MoneyRequestAmountInput( return selection; }, })); + useEffect(() => { + if (resetClicked) { + resetFlag.current = true; + setNewAmount(selectedAmountAsString); + setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end + + } + }, [resetClicked, selectedAmountAsString, setNewAmount]); useEffect(() => { if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b18a98b13304..8886f18fdab6 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -344,7 +344,8 @@ function MoneyRequestConfirmationList({ const shouldDisplayMerchantError = isMerchantRequired && (shouldDisplayFieldError || formError === 'iou.error.invalidMerchant') && isMerchantEmpty; const isCategoryRequired = !!policy?.requiresCategory; - + const [resetClicked, setResetClicked] = useState(false); + useEffect(() => { if (shouldDisplayFieldError && hasSmartScanFailed) { setFormError('iou.receiptScanningFailed'); @@ -513,6 +514,7 @@ function MoneyRequestConfirmationList({ onFormatAmount={CurrencyUtils.convertToDisplayStringWithoutCurrency} onAmountChange={(value: string) => onSplitShareChange(participantOption.accountID ?? 0, Number(value))} maxLength={formattedTotalAmount.length} + resetClicked={resetClicked} /> ), })); @@ -552,6 +554,7 @@ function MoneyRequestConfirmationList({ { IOU.resetSplitShares(transaction); + setResetClicked(true); }} accessibilityLabel={CONST.ROLE.BUTTON} role={CONST.ROLE.BUTTON} From 5a4feba0ed1eb256b5271e5244b147c81030d321 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 31 May 2024 12:58:10 +0530 Subject: [PATCH 02/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index b761a5374b64..7d5c398a26b6 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -197,14 +197,6 @@ function MoneyRequestAmountInput( return selection; }, })); - useEffect(() => { - if (resetClicked) { - resetFlag.current = true; - setNewAmount(selectedAmountAsString); - setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end - - } - }, [resetClicked, selectedAmountAsString, setNewAmount]); useEffect(() => { if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { @@ -271,6 +263,15 @@ function MoneyRequestAmountInput( end: formattedAmount.length, }); }, [amount, currency, onFormatAmount, formatAmountOnBlur, maxLength]); + + useEffect(() => { + if (resetClicked) { + resetFlag.current = true; + setNewAmount(selectedAmountAsString); + setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end + + } + }, [resetClicked, selectedAmountAsString, setNewAmount]); const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); From 98c2c78636f5d46a6cddb23067892b73404c5e36 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 31 May 2024 13:17:16 +0530 Subject: [PATCH 03/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 7d5c398a26b6..8f82a0ad528c 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -140,6 +140,7 @@ function MoneyRequestAmountInput( const forwardDeletePressedRef = useRef(false); const resetFlag = useRef(false); + const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -198,6 +199,15 @@ function MoneyRequestAmountInput( }, })); + useEffect(() => { + if (resetClicked) { + resetFlag.current = true; + setNewAmount(selectedAmountAsString); + setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end + + } + }, [resetClicked, selectedAmountAsString, setNewAmount]); + useEffect(() => { if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { return; @@ -263,17 +273,6 @@ function MoneyRequestAmountInput( end: formattedAmount.length, }); }, [amount, currency, onFormatAmount, formatAmountOnBlur, maxLength]); - - useEffect(() => { - if (resetClicked) { - resetFlag.current = true; - setNewAmount(selectedAmountAsString); - setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end - - } - }, [resetClicked, selectedAmountAsString, setNewAmount]); - - const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); return ( Date: Fri, 31 May 2024 13:29:08 +0530 Subject: [PATCH 04/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 8f82a0ad528c..d92135cc3852 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -200,14 +200,15 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (resetClicked) { - resetFlag.current = true; - setNewAmount(selectedAmountAsString); - setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end - + if (!resetClicked) { + return; } + + resetFlag.current = true; + setNewAmount(selectedAmountAsString); + setSelection({ start: formattedAmount.length, end: formattedAmount.length }); // Move cursor to the end }, [resetClicked, selectedAmountAsString, setNewAmount]); - + useEffect(() => { if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { return; @@ -263,7 +264,7 @@ function MoneyRequestAmountInput( if (!formatAmountOnBlur) { return; } - const formattedAmount = onFormatAmount(amount, currency); + formattedAmount = onFormatAmount(amount, currency); if (maxLength && formattedAmount.length > maxLength) { return; } From 4b30dd924bdc44b75b8bfb7d8f27cfba4b354242 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 31 May 2024 13:34:42 +0530 Subject: [PATCH 05/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index d92135cc3852..33a2c4c6dbe4 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -140,7 +140,6 @@ function MoneyRequestAmountInput( const forwardDeletePressedRef = useRef(false); const resetFlag = useRef(false); - const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -198,17 +197,16 @@ function MoneyRequestAmountInput( return selection; }, })); - + useEffect(() => { - if (!resetClicked) { - return; + if (resetClicked) { + resetFlag.current = true; + setNewAmount(selectedAmountAsString); + setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end + } - - resetFlag.current = true; - setNewAmount(selectedAmountAsString); - setSelection({ start: formattedAmount.length, end: formattedAmount.length }); // Move cursor to the end }, [resetClicked, selectedAmountAsString, setNewAmount]); - + useEffect(() => { if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { return; @@ -264,7 +262,7 @@ function MoneyRequestAmountInput( if (!formatAmountOnBlur) { return; } - formattedAmount = onFormatAmount(amount, currency); + const formattedAmount = onFormatAmount(amount, currency); if (maxLength && formattedAmount.length > maxLength) { return; } @@ -275,6 +273,18 @@ function MoneyRequestAmountInput( }); }, [amount, currency, onFormatAmount, formatAmountOnBlur, maxLength]); + const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit); + + useEffect(() => { + if (!resetClicked) { + return; + } + + resetFlag.current = true; + setNewAmount(selectedAmountAsString); + setSelection({ start: formattedAmount.length, end: formattedAmount.length }); // Move cursor to the end + }, [resetClicked, selectedAmountAsString, setNewAmount]); + return ( Date: Fri, 31 May 2024 13:42:56 +0530 Subject: [PATCH 06/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 33a2c4c6dbe4..0742351a2475 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -197,15 +197,6 @@ function MoneyRequestAmountInput( return selection; }, })); - - useEffect(() => { - if (resetClicked) { - resetFlag.current = true; - setNewAmount(selectedAmountAsString); - setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end - - } - }, [resetClicked, selectedAmountAsString, setNewAmount]); useEffect(() => { if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused())) { From cde4e5f24883f0990bc8042bf5d34daafd5cc4ad Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 31 May 2024 13:54:09 +0530 Subject: [PATCH 07/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 4 ++-- src/components/MoneyRequestConfirmationList.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 0742351a2475..1f0ce4fa5a9f 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -177,7 +177,7 @@ function MoneyRequestAmountInput( return resultAmount; }); }, - [decimals, onAmountChange], + [decimals, onAmountChange, selectedAmountAsString], ); useImperativeHandle(moneyRequestAmountInputRef, () => ({ @@ -274,7 +274,7 @@ function MoneyRequestAmountInput( resetFlag.current = true; setNewAmount(selectedAmountAsString); setSelection({ start: formattedAmount.length, end: formattedAmount.length }); // Move cursor to the end - }, [resetClicked, selectedAmountAsString, setNewAmount]); + }, [resetClicked, selectedAmountAsString, setNewAmount, formattedAmount]); return ( { From c805f325f41da644c9808b7251fb2f0f50fef020 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 31 May 2024 14:05:36 +0530 Subject: [PATCH 08/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 14 +++++++------- src/components/MoneyRequestConfirmationList.tsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 1f0ce4fa5a9f..22d57d024976 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -83,8 +83,8 @@ type MoneyRequestAmountInputProps = { */ autoGrow?: boolean; - /** Flag indicating if the reset button was clicked to reset the amount */ - resetClicked?: boolean; + /** Flag indicating if the reset button was clicked to reset the amount */ + resetClicked?: boolean; }; type Selection = { @@ -170,9 +170,9 @@ function MoneyRequestAmountInput( hasSelectionBeenSet = true; setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); } - const resultAmount = resetFlag.current ? selectedAmountAsString : strippedAmount; + const resultAmount = resetFlag.current ? selectedAmountAsString : strippedAmount; onAmountChange?.(resultAmount); - + resetFlag.current = false; // Reset the flag after handling return resultAmount; }); @@ -270,12 +270,12 @@ function MoneyRequestAmountInput( if (!resetClicked) { return; } - + resetFlag.current = true; setNewAmount(selectedAmountAsString); - setSelection({ start: formattedAmount.length, end: formattedAmount.length }); // Move cursor to the end + setSelection({start: formattedAmount.length, end: formattedAmount.length}); // Move cursor to the end }, [resetClicked, selectedAmountAsString, setNewAmount, formattedAmount]); - + return ( { if (shouldDisplayFieldError && hasSmartScanFailed) { setFormError('iou.receiptScanningFailed'); From 7cd56e93a2ba1369b7fafa63eb3da375d7cb4944 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Thu, 6 Jun 2024 19:26:53 +0530 Subject: [PATCH 09/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 36 ++++++++++------ .../MoneyRequestConfirmationList.tsx | 43 +++++++++---------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 1f44faa44f04..b0963940be4d 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -87,8 +87,10 @@ type MoneyRequestAmountInputProps = { */ autoGrow?: boolean; - /** Flag indicating if the reset button was clicked to reset the amount */ - resetClicked?: boolean; + shouldResetAmount?: boolean; + + onResetAmount?: (resetValue: boolean) => void; + }; type Selection = { @@ -124,7 +126,8 @@ function MoneyRequestAmountInput( hideFocusedState = true, shouldKeepUserInput = false, autoGrow = true, - resetClicked = false, + shouldResetAmount, + onResetAmount, ...props }: MoneyRequestAmountInputProps, forwardedRef: ForwardedRef, @@ -144,7 +147,6 @@ function MoneyRequestAmountInput( }); const forwardDeletePressedRef = useRef(false); - const resetFlag = useRef(false); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -175,14 +177,11 @@ function MoneyRequestAmountInput( hasSelectionBeenSet = true; setSelection((prevSelection) => getNewSelection(prevSelection, isForwardDelete ? strippedAmount.length : prevAmount.length, strippedAmount.length)); } - const resultAmount = resetFlag.current ? selectedAmountAsString : strippedAmount; - onAmountChange?.(resultAmount); - - resetFlag.current = false; // Reset the flag after handling - return resultAmount; + onAmountChange?.(strippedAmount); + return strippedAmount; }); }, - [decimals, onAmountChange, selectedAmountAsString], + [decimals, onAmountChange], ); useImperativeHandle(moneyRequestAmountInputRef, () => ({ @@ -204,10 +203,21 @@ function MoneyRequestAmountInput( })); useEffect(() => { - if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput) { + const shouldExitEarly = !currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput; + const frontendAmount = onFormatAmount(amount, currency); + + if (shouldResetAmount) { + setCurrentAmount(frontendAmount); + setSelection({ + start: frontendAmount.length, + end: frontendAmount.length, + }); + onResetAmount?.(false); return; } - const frontendAmount = onFormatAmount(amount, currency); + + if (shouldExitEarly) return; + setCurrentAmount(frontendAmount); // Only update selection if the amount prop was changed from the outside and is not the same as the current amount we just computed @@ -218,7 +228,7 @@ function MoneyRequestAmountInput( end: frontendAmount.length, }); } - + // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [amount, shouldKeepUserInput]); diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 80af305b026b..ee148a223738 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -25,7 +25,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import {getCustomUnitRate, isTaxTrackingEnabled} from '@libs/PolicyUtils'; +import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; @@ -179,6 +179,15 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & type MoneyRequestConfirmationListItem = Participant | ReportUtils.OptionData; +const getTaxAmount = (transaction: OnyxEntry, policy: OnyxEntry, isDistanceRequest: boolean) => { + if (isDistanceRequest) { + return DistanceRequestUtils.calculateTaxAmount(policy, transaction, TransactionUtils.getRateID(transaction) ?? ''); + } + const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; + const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, transaction?.taxCode ?? defaultTaxCode) ?? ''; + return TransactionUtils.calculateTaxAmount(taxPercentage, transaction?.amount ?? 0); +}; + function MoneyRequestConfirmationList({ transaction = null, onSendMoney, @@ -339,7 +348,7 @@ function MoneyRequestConfirmationList({ const shouldDisplayMerchantError = isMerchantRequired && (shouldDisplayFieldError || formError === 'iou.error.invalidMerchant') && isMerchantEmpty; const isCategoryRequired = !!policy?.requiresCategory; - const [resetClicked, setResetClicked] = useState(false); + const [shouldResetAmount, setShouldResetAmount] = useState(false); useEffect(() => { if (shouldDisplayFieldError && didConfirmSplit) { @@ -368,25 +377,15 @@ function MoneyRequestConfirmationList({ // Calculate and set tax amount in transaction draft useEffect(() => { - if (!shouldShowTax || (transaction?.taxAmount !== undefined && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency)) { - return; - } + const taxAmount = getTaxAmount(transaction, policy, isDistanceRequest).toString(); + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); - let taxableAmount: number; - let taxCode: string; - if (isDistanceRequest) { - const customUnitRate = getCustomUnitRate(policy, customUnitRateID); - taxCode = customUnitRate?.attributes?.taxRateExternalID ?? ''; - taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistance(transaction)); - } else { - taxableAmount = transaction?.amount ?? 0; - taxCode = transaction?.taxCode ?? TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; + if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency) { + return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount ?? 0, true); } - const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxCode) ?? ''; - const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount); - const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount.toString())); - IOU.setMoneyRequestTaxAmount(transaction?.transactionID ?? '', taxAmountInSmallestCurrencyUnits); - }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID]); + + IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true); + }, [policy, transaction, transactionID, previousTransactionAmount, previousTransactionCurrency, isDistanceRequest]); // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again if (isEditingSplitBill && didConfirm) { @@ -520,7 +519,8 @@ function MoneyRequestConfirmationList({ onFormatAmount={CurrencyUtils.convertToDisplayStringWithoutCurrency} onAmountChange={(value: string) => onSplitShareChange(participantOption.accountID ?? 0, Number(value))} maxLength={formattedTotalAmount.length} - resetClicked={resetClicked} + shouldResetAmount={shouldResetAmount} + onResetAmount={() => setShouldResetAmount(false)} /> ), })); @@ -543,7 +543,6 @@ function MoneyRequestConfirmationList({ transaction?.comment?.splits, transaction?.splitShares, onSplitShareChange, - resetClicked, ]); const isSplitModified = useMemo(() => { @@ -561,7 +560,7 @@ function MoneyRequestConfirmationList({ { IOU.resetSplitShares(transaction); - setResetClicked(true); + setShouldResetAmount(true); }} accessibilityLabel={CONST.ROLE.BUTTON} role={CONST.ROLE.BUTTON} From 8f71b5deeb5c20277e9ea1d2ec5d6124c6f5e7a8 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Thu, 6 Jun 2024 19:39:08 +0530 Subject: [PATCH 10/26] Reset amount on reset button click --- src/components/MoneyRequestConfirmationList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index ee148a223738..196ac7379d97 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -381,10 +381,10 @@ function MoneyRequestConfirmationList({ const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency) { - return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount ?? 0, true); + return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount ?? 0); } - IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits, true); + IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits); }, [policy, transaction, transactionID, previousTransactionAmount, previousTransactionCurrency, isDistanceRequest]); // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again From 057afebb51066aa23964af65bafb97b2437a8bc9 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Thu, 6 Jun 2024 19:49:00 +0530 Subject: [PATCH 11/26] Reset amount on reset button click --- .../MoneyRequestConfirmationList.tsx | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 196ac7379d97..9e8a5e08e0c2 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -25,7 +25,7 @@ import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; +import {getCustomUnitRate, isTaxTrackingEnabled} from '@libs/PolicyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; @@ -179,15 +179,6 @@ type MoneyRequestConfirmationListProps = MoneyRequestConfirmationListOnyxProps & type MoneyRequestConfirmationListItem = Participant | ReportUtils.OptionData; -const getTaxAmount = (transaction: OnyxEntry, policy: OnyxEntry, isDistanceRequest: boolean) => { - if (isDistanceRequest) { - return DistanceRequestUtils.calculateTaxAmount(policy, transaction, TransactionUtils.getRateID(transaction) ?? ''); - } - const defaultTaxCode = TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; - const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, transaction?.taxCode ?? defaultTaxCode) ?? ''; - return TransactionUtils.calculateTaxAmount(taxPercentage, transaction?.amount ?? 0); -}; - function MoneyRequestConfirmationList({ transaction = null, onSendMoney, @@ -377,15 +368,25 @@ function MoneyRequestConfirmationList({ // Calculate and set tax amount in transaction draft useEffect(() => { - const taxAmount = getTaxAmount(transaction, policy, isDistanceRequest).toString(); - const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); - - if (transaction?.taxAmount && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency) { - return IOU.setMoneyRequestTaxAmount(transactionID, transaction?.taxAmount ?? 0); + if (!shouldShowTax || (transaction?.taxAmount !== undefined && previousTransactionAmount === transaction?.amount && previousTransactionCurrency === transaction?.currency)) { + return; } - IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits); - }, [policy, transaction, transactionID, previousTransactionAmount, previousTransactionCurrency, isDistanceRequest]); + let taxableAmount: number; + let taxCode: string; + if (isDistanceRequest) { + const customUnitRate = getCustomUnitRate(policy, customUnitRateID); + taxCode = customUnitRate?.attributes?.taxRateExternalID ?? ''; + taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistance(transaction)); + } else { + taxableAmount = transaction?.amount ?? 0; + taxCode = transaction?.taxCode ?? TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''; + } + const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxCode) ?? ''; + const taxAmount = TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount); + const taxAmountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount.toString())); + IOU.setMoneyRequestTaxAmount(transaction?.transactionID ?? '', taxAmountInSmallestCurrencyUnits); + }, [policy, shouldShowTax, previousTransactionAmount, previousTransactionCurrency, transaction, isDistanceRequest, customUnitRateID]); // If completing a split expense fails, set didConfirm to false to allow the user to edit the fields again if (isEditingSplitBill && didConfirm) { @@ -1295,4 +1296,4 @@ export default withOnyx Date: Thu, 6 Jun 2024 20:00:15 +0530 Subject: [PATCH 12/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 2 +- src/components/MoneyRequestConfirmationList.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index b0963940be4d..882bd968e50e 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -216,7 +216,7 @@ function MoneyRequestAmountInput( return; } - if (shouldExitEarly) return; + if (shouldExitEarly) {return}; setCurrentAmount(frontendAmount); diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9e8a5e08e0c2..330fe12c70e0 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -544,6 +544,8 @@ function MoneyRequestConfirmationList({ transaction?.comment?.splits, transaction?.splitShares, onSplitShareChange, + shouldResetAmount, + onResetAmount, ]); const isSplitModified = useMemo(() => { From 96c4aca13870e7648895c140f8468760b3460d82 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Thu, 6 Jun 2024 20:06:31 +0530 Subject: [PATCH 13/26] Reset amount on reset button click --- src/components/MoneyRequestConfirmationList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 330fe12c70e0..5ad67bac5996 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -545,7 +545,6 @@ function MoneyRequestConfirmationList({ transaction?.splitShares, onSplitShareChange, shouldResetAmount, - onResetAmount, ]); const isSplitModified = useMemo(() => { From 1f5660c88bf25ef5da2e9661d9943594a19f6e8e Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Thu, 6 Jun 2024 20:56:34 +0530 Subject: [PATCH 14/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 9 +++++---- src/components/MoneyRequestConfirmationList.tsx | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 882bd968e50e..aaec876775d6 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -90,7 +90,6 @@ type MoneyRequestAmountInputProps = { shouldResetAmount?: boolean; onResetAmount?: (resetValue: boolean) => void; - }; type Selection = { @@ -216,8 +215,10 @@ function MoneyRequestAmountInput( return; } - if (shouldExitEarly) {return}; - + if (shouldExitEarly) { + return; + } + setCurrentAmount(frontendAmount); // Only update selection if the amount prop was changed from the outside and is not the same as the current amount we just computed @@ -228,7 +229,7 @@ function MoneyRequestAmountInput( end: frontendAmount.length, }); } - + // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [amount, shouldKeepUserInput]); diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 5ad67bac5996..cbcc90c5d74d 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1297,4 +1297,4 @@ export default withOnyx Date: Mon, 24 Jun 2024 18:57:11 +0530 Subject: [PATCH 15/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index ec38c04ec8e2..5a8d3742b5f8 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -9,7 +9,6 @@ import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import shouldIgnoreSelectionWhenUpdatedManually from '@libs/shouldIgnoreSelectionWhenUpdatedManually'; import CONST from '@src/CONST'; -import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -107,7 +106,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: return {start: cursorPosition, end: cursorPosition}; }; -const defaultOnFormatAmount = (amount: number) => CurrencyUtils.convertToFrontendAmountAsString(amount); +const defaultOnFormatAmount = (amount: number, currency?: string): string => CurrencyUtils.convertToFrontendAmountAsString(amount); function MoneyRequestAmountInput( { From b454ac27ae3a971f0b9033ca9863cad34be3051d Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Mon, 24 Jun 2024 19:16:09 +0530 Subject: [PATCH 16/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 5a8d3742b5f8..80b86afc9e06 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -106,7 +106,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: return {start: cursorPosition, end: cursorPosition}; }; -const defaultOnFormatAmount = (amount: number, currency?: string): string => CurrencyUtils.convertToFrontendAmountAsString(amount); +const defaultOnFormatAmount = (amount: number) => CurrencyUtils.convertToFrontendAmountAsString(amount); function MoneyRequestAmountInput( { @@ -208,7 +208,7 @@ function MoneyRequestAmountInput( useEffect(() => { const shouldExitEarly = !currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput; - const frontendAmount = onFormatAmount(amount, currency); + const frontendAmount = onFormatAmount ? onFormatAmount(amount, currency) : defaultOnFormatAmount(amount, currency); if (shouldResetAmount) { setCurrentAmount(frontendAmount); From bc1069262f87cdd9eac9f6b7c37f4318bf6af8a8 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Mon, 24 Jun 2024 19:22:30 +0530 Subject: [PATCH 17/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 80b86afc9e06..05b733c85fff 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -208,7 +208,7 @@ function MoneyRequestAmountInput( useEffect(() => { const shouldExitEarly = !currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput; - const frontendAmount = onFormatAmount ? onFormatAmount(amount, currency) : defaultOnFormatAmount(amount, currency); + const frontendAmount = onFormatAmount(amount, currency); if (shouldResetAmount) { setCurrentAmount(frontendAmount); From 4db9a0afc2cdd53daec37ccfbd0d61f407303570 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Mon, 24 Jun 2024 19:45:59 +0530 Subject: [PATCH 18/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 05b733c85fff..a63622eb4434 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -9,6 +9,7 @@ import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; import shouldIgnoreSelectionWhenUpdatedManually from '@libs/shouldIgnoreSelectionWhenUpdatedManually'; import CONST from '@src/CONST'; +import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused'; import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; import TextInputWithCurrencySymbol from './TextInputWithCurrencySymbol'; @@ -207,7 +208,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - const shouldExitEarly = !currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput; + const shouldExitEarly = (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) || shouldKeepUserInput; const frontendAmount = onFormatAmount(amount, currency); if (shouldResetAmount) { From b3087ed1561f1f2661de3574275621adeac5849f Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Mon, 24 Jun 2024 19:56:21 +0530 Subject: [PATCH 19/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index a63622eb4434..6569e1fbdd8e 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -208,7 +208,7 @@ function MoneyRequestAmountInput( })); useEffect(() => { - const shouldExitEarly = (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) || shouldKeepUserInput; + const shouldExitEarly = (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) ?? shouldKeepUserInput; const frontendAmount = onFormatAmount(amount, currency); if (shouldResetAmount) { From 1b409341946d8842751b5bbb9604b73dc97d80ec Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Thu, 27 Jun 2024 18:31:42 +0530 Subject: [PATCH 20/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 4 +--- src/components/MoneyRequestConfirmationList.tsx | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index 6569e1fbdd8e..f062d2b14c52 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -88,7 +88,6 @@ type MoneyRequestAmountInputProps = { * Autogrow input container length based on the entered text. */ autoGrow?: boolean; - shouldResetAmount?: boolean; onResetAmount?: (resetValue: boolean) => void; @@ -210,7 +209,6 @@ function MoneyRequestAmountInput( useEffect(() => { const shouldExitEarly = (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) ?? shouldKeepUserInput; const frontendAmount = onFormatAmount(amount, currency); - if (shouldResetAmount) { setCurrentAmount(frontendAmount); setSelection({ @@ -238,7 +236,7 @@ function MoneyRequestAmountInput( // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [amount, shouldKeepUserInput]); + }, [amount, shouldKeepUserInput, shouldResetAmount]); // Modifies the amount to match the decimals for changed currency. useEffect(() => { diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 04f0c798a1c1..016c6426e85e 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -288,7 +288,6 @@ function MoneyRequestConfirmationList({ const isCategoryRequired = !!policy?.requiresCategory; const [shouldResetAmount, setShouldResetAmount] = useState(false); - useEffect(() => { if (shouldDisplayFieldError && didConfirmSplit) { setFormError('iou.error.genericSmartscanFailureMessage'); From 5a81a360f3bc512ad9d222a5f8fa971b936eecc4 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 28 Jun 2024 11:01:56 +0530 Subject: [PATCH 21/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 18 +++++++++++++----- .../MoneyRequestConfirmationList.tsx | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index f062d2b14c52..a67475dc3bcf 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -88,9 +88,18 @@ type MoneyRequestAmountInputProps = { * Autogrow input container length based on the entered text. */ autoGrow?: boolean; + + /** + * Determines whether the amount should be reset. + */ shouldResetAmount?: boolean; - onResetAmount?: (resetValue: boolean) => void; + /** + * Callback function triggered when the amount is reset. + * + * @param resetValue - A boolean indicating whether the amount should be reset. + */ + onResetAmount?: (resetValue: boolean) => void; }; type Selection = { @@ -207,10 +216,9 @@ function MoneyRequestAmountInput( })); useEffect(() => { - const shouldExitEarly = (!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) ?? shouldKeepUserInput; const frontendAmount = onFormatAmount(amount, currency); + setCurrentAmount(frontendAmount); if (shouldResetAmount) { - setCurrentAmount(frontendAmount); setSelection({ start: frontendAmount.length, end: frontendAmount.length, @@ -219,7 +227,7 @@ function MoneyRequestAmountInput( return; } - if (shouldExitEarly) { + if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput) { return; } @@ -236,7 +244,7 @@ function MoneyRequestAmountInput( // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [amount, shouldKeepUserInput, shouldResetAmount]); + }, [amount, shouldKeepUserInput,shouldResetAmount]); // Modifies the amount to match the decimals for changed currency. useEffect(() => { diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 016c6426e85e..9b338f7f5c28 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -467,7 +467,7 @@ function MoneyRequestConfirmationList({ onAmountChange={(value: string) => onSplitShareChange(participantOption.accountID ?? -1, Number(value))} maxLength={formattedTotalAmount.length} shouldResetAmount={shouldResetAmount} - onResetAmount={() => setShouldResetAmount(false)} + onResetAmount={(resetValue) => setShouldResetAmount(resetValue)} /> ), })); From ff59531a679872454f55c1804488eb374fd757fb Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 28 Jun 2024 11:16:41 +0530 Subject: [PATCH 22/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index a67475dc3bcf..c9e814b3ff7d 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -227,7 +227,7 @@ function MoneyRequestAmountInput( return; } - if (!currency || typeof amount !== 'number' || (formatAmountOnBlur && textInput.current?.isFocused()) || shouldKeepUserInput) { + if ((!currency || typeof amount !== 'number' || (formatAmountOnBlur && isTextInputFocused(textInput))) ?? shouldKeepUserInput) { return; } From 804228673472ca90bba8b80265ab4bdc138e01cf Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Fri, 28 Jun 2024 11:28:26 +0530 Subject: [PATCH 23/26] Reset amount on reset button click --- src/components/MoneyRequestAmountInput.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestAmountInput.tsx b/src/components/MoneyRequestAmountInput.tsx index c9e814b3ff7d..5ac824e3c546 100644 --- a/src/components/MoneyRequestAmountInput.tsx +++ b/src/components/MoneyRequestAmountInput.tsx @@ -96,10 +96,10 @@ type MoneyRequestAmountInputProps = { /** * Callback function triggered when the amount is reset. - * + * * @param resetValue - A boolean indicating whether the amount should be reset. */ - onResetAmount?: (resetValue: boolean) => void; + onResetAmount?: (resetValue: boolean) => void; }; type Selection = { @@ -244,7 +244,7 @@ function MoneyRequestAmountInput( // we want to re-initialize the state only when the amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [amount, shouldKeepUserInput,shouldResetAmount]); + }, [amount, shouldKeepUserInput, shouldResetAmount]); // Modifies the amount to match the decimals for changed currency. useEffect(() => { From 5a2602c441170093e99850e2194808218b0aa838 Mon Sep 17 00:00:00 2001 From: Kaushik Kapadiya Date: Wed, 10 Jul 2024 11:32:09 +0530 Subject: [PATCH 24/26] Reset amount on reset button click --- .eslintrc.js | 11 +- .../javascript/bumpVersion/bumpVersion.ts | 2 +- .../actions/javascript/bumpVersion/index.js | 2 +- .../createOrUpdateStagingDeploy.ts | 4 +- .../getDeployPullRequestList.ts | 22 +- .../getDeployPullRequestList/index.js | 17 +- .../getGraphiteString/getGraphiteString.ts | 2 +- .../getPreviousVersion/getPreviousVersion.ts | 2 +- .../validateReassureOutput.ts | 2 +- .github/workflows/deploy.yml | 38 +- .github/workflows/e2ePerformanceTests.yml | 3 + .../workflows/reassurePerformanceTests.yml | 4 +- .prettierrc.js | 1 + Gemfile.lock | 18 +- __mocks__/@react-navigation/native/index.ts | 66 +- __mocks__/@ua/react-native-airship.ts | 16 +- __mocks__/fs.ts | 1 + __mocks__/react-native.ts | 11 +- android/app/build.gradle | 4 +- assets/images/circular-arrow-backwards.svg | 9 + assets/images/computer.svg | 1 + .../images/expensifyCard/cardIllustration.svg | 1 + .../integrationicons/netsuite-icon-square.svg | 58 +- .../sage-intacct-icon-square.svg | 1 + .../folder-with-papers.svg | 33 + .../simple-illustration__virtualcard.svg | 48 + config/electronBuilder.config.js | 2 +- config/webpack/webpack.common.ts | 13 +- desktop/main.ts | 4 +- docs/_data/_routes.yml | 4 +- docs/_includes/lhn-template.html | 18 +- docs/_layouts/default.html | 2 +- docs/_sass/_main.scss | 1 + .../Global-Reimbursements.md | 28 +- .../Admin-Card-Settings-and-Features.md | 224 +-- .../expensify-card/Auto-Reconciliation.md | 213 --- .../Cardholder-Settings-and-Features.md | 107 +- .../expensify-card/Dispute-A-Transaction.md | 64 +- .../Expensify-Card-Reconciliation.md | 108 ++ .../expensify-card/Request-the-Card.md | 68 +- .../expensify-card/Statements.md | 91 +- .../Approve-and-pay-expenses.md | 0 .../Connect-a-Business-Bank-Account.md | 0 .../Create-an-expense.md | 0 .../Distance-Requests.md | 0 .../Resolve-Errors-Adding-a-Bank-Account.md | 0 .../Send-an-invoice.md | 0 .../Set-up-your-wallet.md | 0 .../Split-an-expense.md | 0 .../Track-expenses.md | 0 .../Unlock-a-Business-Bank-Account.md | 0 .../Validate-a-Business-Bank-Account.md | 0 .../Enable-Expensify-Card-notifications.md | 57 + .../assets/images/ExpensifyHelp-Invoice-1.png | Bin 0 -> 301544 bytes docs/assets/images/ExpensifyHelp-QBO-1.png | Bin 159939 -> 134826 bytes docs/assets/images/ExpensifyHelp-QBO-2.png | Bin 163653 -> 185128 bytes docs/assets/images/ExpensifyHelp-QBO-3.png | Bin 93485 -> 159939 bytes docs/assets/images/ExpensifyHelp-QBO-4.png | Bin 0 -> 163653 bytes docs/assets/images/ExpensifyHelp-QBO-5.png | Bin 0 -> 93485 bytes .../index.html | 0 docs/redirects.csv | 12 + fastlane/Fastfile | 50 +- ios/NewExpensify/Info.plist | 4 +- ios/NewExpensifyTests/Info.plist | 4 +- ios/NotificationServiceExtension/Info.plist | 4 +- ios/Podfile.lock | 34 +- jest/setup.ts | 21 +- jest/setupMockFullstoryLib.ts | 2 +- package-lock.json | 255 ++- package.json | 28 +- ...fy+react-native-live-markdown+0.1.85.patch | 13 - ...fy+react-native-live-markdown+0.1.91.patch | 13 + patches/@perf-profiler+android+0.12.0.patch | 54 - patches/@perf-profiler+android+0.12.1.patch | 26 + patches/@perf-profiler+reporter+0.8.1.patch | 25 - ...core+6.4.11+001+fix-react-strictmode.patch | 45 + ...+0.0.0-experimental-53bb89e-20240515.patch | 13 - ...+0.0.0-experimental-b130d5f-20240625.patch | 90 + ...t-native-keyboard-controller+1.12.2.patch} | 0 ...ated+3.8.1+001+fix-boost-dependency.patch} | 0 ...ive-reanimated+3.8.1+002+copy-state.patch} | 0 ...reanimated+3.8.1+003+fix-strict-mode.patch | 13 + ...ch => react-native-reanimated+3.8.1.patch} | 0 ...e-screens+3.30.1+001+fix-screen-type.patch | 12 - scripts/applyPatches.sh | 56 + scripts/postInstall.sh | 7 +- src/App.tsx | 2 + src/CONST.ts | 435 ++++- src/Expensify.tsx | 36 +- src/NAVIGATORS.ts | 1 + src/ONYXKEYS.ts | 90 +- src/ROUTES.ts | 292 +++- src/SCREENS.ts | 73 +- .../AccountingConnectionConfirmationModal.tsx | 30 + .../PaymentCardChangeCurrencyForm.tsx | 14 +- .../PaymentCardCurrencyModal.tsx | 8 +- .../AddPaymentCard/PaymentCardForm.tsx | 4 +- src/components/AddPlaidBankAccount.tsx | 2 +- src/components/AddressForm.tsx | 1 + src/components/AddressSearch/index.tsx | 60 +- src/components/AddressSearch/types.ts | 11 +- src/components/AmountForm.tsx | 2 +- src/components/AnimatedStep/index.tsx | 1 + src/components/AttachmentModal.tsx | 8 +- .../AttachmentPicker/index.native.tsx | 1 + src/components/AttachmentPicker/index.tsx | 1 + .../Pager/AttachmentCarouselPagerContext.ts | 19 +- .../AttachmentCarousel/Pager/index.tsx | 53 +- .../AttachmentCarousel/index.native.tsx | 20 +- .../Attachments/AttachmentCarousel/index.tsx | 102 +- .../AttachmentCarousel/useCarouselArrows.ts | 5 +- .../useCarouselContextEvents.ts | 64 + .../BaseAttachmentViewPdf.tsx | 2 +- .../AttachmentViewPdf/index.android.tsx | 1 + .../BaseAutoCompleteSuggestions.tsx | 1 + .../AvatarCropModal/AvatarCropModal.tsx | 1 + src/components/AvatarCropModal/Slider.tsx | 3 +- src/components/Button/index.tsx | 2 +- .../ButtonWithDropdownMenu/types.ts | 8 +- src/components/Composer/index.native.tsx | 4 +- src/components/Composer/index.tsx | 33 +- src/components/ConfirmationPage.tsx | 2 +- .../ConnectToNetSuiteButton/index.tsx | 52 + .../ConnectToNetSuiteButton/types.ts | 10 + .../index.native.tsx | 16 +- .../ConnectToQuickbooksOnlineButton/index.tsx | 13 +- .../ConnectToSageIntacctButton/index.tsx | 128 ++ .../ConnectToXeroButton/index.native.tsx | 11 +- src/components/ConnectToXeroButton/index.tsx | 11 +- src/components/ConnectionLayout.tsx | 18 +- src/components/CountrySelector.tsx | 32 +- src/components/CurrencySelector.tsx | 2 +- .../DisplayNames/DisplayNamesTooltipItem.tsx | 2 +- src/components/EReceiptThumbnail.tsx | 12 +- src/components/EmojiPicker/EmojiPicker.tsx | 1 + .../EmojiPickerMenu/useEmojiPickerMenu.ts | 2 +- .../EmojiPicker/EmojiSkinToneList.tsx | 2 +- src/components/ExplanationModal.tsx | 41 + src/components/FeatureTrainingModal.tsx | 5 + src/components/FlatList/index.android.tsx | 2 +- src/components/FlatList/index.tsx | 1 - src/components/FloatingActionButton.tsx | 1 + .../FocusTrap/FocusTrapForModal/index.web.tsx | 7 + .../FocusTrapForScreen/index.web.tsx | 11 +- .../FocusTrap/SCREENS_WITH_AUTOFOCUS.ts | 16 +- .../FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts | 1 + src/components/Form/FormProvider.tsx | 6 +- src/components/Form/FormWrapper.tsx | 11 + src/components/Form/InputWrapper.tsx | 2 +- src/components/Form/types.ts | 17 +- .../BaseHTMLEngineProvider.tsx | 7 +- src/components/Hoverable/ActiveHoverable.tsx | 4 +- .../HybridAppMiddleware/index.ios.tsx | 130 ++ src/components/HybridAppMiddleware/index.tsx | 107 ++ src/components/IFrame.tsx | 2 +- src/components/Icon/Expensicons.ts | 4 + src/components/Icon/Illustrations.ts | 6 + src/components/Image/index.tsx | 2 +- src/components/ImageView/index.tsx | 25 +- src/components/Indicator.tsx | 4 +- src/components/InteractiveStepSubHeader.tsx | 6 + .../LHNOptionsList/OptionRowLHN.tsx | 13 +- .../LHNOptionsList/OptionRowLHNData.tsx | 2 +- src/components/LottieAnimations/index.tsx | 30 +- src/components/MagicCodeInput.tsx | 6 +- src/components/MapView/MapView.tsx | 10 +- src/components/MapView/MapView.website.tsx | 2 +- src/components/MenuItem.tsx | 40 +- src/components/Modal/BaseModal.tsx | 4 +- src/components/Modal/ModalContent.tsx | 2 +- src/components/MoneyReportHeader.tsx | 187 +-- src/components/MoneyRequestAmountInput.tsx | 5 +- .../MoneyRequestConfirmationList.tsx | 27 +- .../MoneyRequestConfirmationListFooter.tsx | 10 +- src/components/MoneyRequestHeader.tsx | 100 +- src/components/MultiGestureCanvas/index.tsx | 8 +- .../MultiGestureCanvas/usePanGesture.ts | 8 +- .../MultiGestureCanvas/usePinchGesture.ts | 1 + .../MultiGestureCanvas/useTapGestures.ts | 1 + src/components/OfflineWithFeedback.tsx | 9 +- src/components/OnboardingWelcomeVideo.tsx | 1 - src/components/Onfido/BaseOnfidoWeb.tsx | 2 +- src/components/Onfido/index.native.tsx | 2 +- src/components/OpacityView.tsx | 1 + src/components/OptionListContextProvider.tsx | 4 +- src/components/PDFView/index.tsx | 2 +- src/components/Picker/BasePicker.tsx | 2 +- src/components/PlaidLink/index.native.tsx | 2 +- src/components/PopoverMenu.tsx | 9 +- .../PopoverWithoutOverlay/index.tsx | 2 +- .../GenericPressable/BaseGenericPressable.tsx | 32 +- .../Pressable/GenericPressable/types.ts | 6 + .../Pressable/PressableWithDelayToggle.tsx | 2 +- src/components/ProcessMoneyReportHoldMenu.tsx | 15 +- .../Reactions/EmojiReactionBubble.tsx | 3 +- .../index.ios.ts | 6 - .../index.ts | 5 - src/components/ReceiptImage.tsx | 7 +- .../ReportActionItem/MoneyReportView.tsx | 7 +- .../MoneyRequestPreviewContent.tsx | 37 +- .../ReportActionItem/ReportPreview.tsx | 3 +- src/components/ScreenWrapper.tsx | 4 +- src/components/Search/SearchContext.tsx | 58 + .../Search/SearchListWithHeader.tsx | 124 ++ src/components/Search/SearchPageHeader.tsx | 141 ++ .../{Search.tsx => Search/index.tsx} | 64 +- src/components/Search/types.ts | 29 + src/components/SelectionList/BaseListItem.tsx | 1 + .../SelectionList/BaseSelectionList.tsx | 30 +- .../SelectionList/Search/ActionCell.tsx | 70 +- .../Search/ExpenseItemHeaderNarrow.tsx | 11 +- .../SelectionList/Search/ReportListItem.tsx | 27 +- .../Search/TransactionListItem.tsx | 7 +- .../Search/TransactionListItemRow.tsx | 124 +- .../SelectionList/SearchTableHeader.tsx | 12 +- .../SelectionList/SortableHeaderText.tsx | 2 +- .../SelectionList/TableListItem.tsx | 6 +- src/components/SelectionList/types.ts | 17 + src/components/SelectionScreen.tsx | 59 +- src/components/SettlementButton.tsx | 9 +- src/components/SingleChoiceQuestion.tsx | 2 +- src/components/SpacerView.tsx | 3 +- .../SplashScreenHider/index.native.tsx | 1 + src/components/StateSelector.tsx | 6 +- src/components/Switch.tsx | 11 +- src/components/TestToolsModal.tsx | 4 +- .../TextInput/BaseTextInput/index.native.tsx | 2 +- .../TextInput/BaseTextInput/index.tsx | 2 +- .../TextInput/TextInputLabel/index.tsx | 2 +- src/components/TextInput/index.tsx | 2 +- .../TextPicker/TextSelectorModal.tsx | 26 +- src/components/TextPicker/types.ts | 7 +- src/components/ThumbnailImage.tsx | 10 +- src/components/TimePicker/TimePicker.tsx | 11 +- .../BaseEducationalTooltip.tsx | 1 + .../Tooltip/PopoverAnchorTooltip.tsx | 2 +- .../VideoPlayer/BaseVideoPlayer.tsx | 3 +- .../VideoPlayerControls/ProgressBar/index.tsx | 1 + .../VolumeButton/index.tsx | 1 + .../VideoPlayer/VideoPlayerControls/index.tsx | 1 + .../VideoPopoverMenuContext.tsx | 1 + .../VideoPlayerContexts/VolumeContext.tsx | 1 + src/components/WorkspaceEmptyStateSection.tsx | 9 +- .../withNavigationTransitionEnd.tsx | 2 +- .../index.native.tsx | 27 +- .../withPrepareCentralPaneScreen/index.tsx | 8 +- src/hooks/useAnimatedHighlightStyle/index.ts | 1 + src/hooks/useArrowKeyFocusManager.ts | 2 +- src/hooks/useCopySelectionHelper.ts | 6 +- src/hooks/useExitTo.ts | 17 + ...useGeographicalStateAndCountryFromRoute.ts | 27 + src/hooks/useGeographicalStateFromRoute.ts | 23 - src/hooks/useHtmlPaste/index.ts | 10 +- src/hooks/useIsSplashHidden.ts | 11 - src/hooks/useKeyboardShortcut.ts | 2 +- src/hooks/useMarkdownStyle.ts | 3 +- src/hooks/useReportIDs.tsx | 58 +- src/hooks/useReviewDuplicatesNavigation.tsx | 52 + src/hooks/useSplashScreen.ts | 11 + .../useSubscriptionPossibleCostSavings.ts | 34 + src/hooks/useSubscriptionPrice.ts | 10 - src/hooks/useSyncFocus/index.ts | 2 +- src/hooks/useTabNavigatorFocus/index.ts | 2 +- src/languages/en.ts | 830 ++++++++- src/languages/es.ts | 846 +++++++++- src/languages/types.ts | 58 + src/libs/API/index.ts | 216 ++- .../API/parameters/AddPaymentCardParams.ts | 2 +- .../ConnectPolicyToNetSuiteParams.ts | 8 + .../ConnectPolicyToSageIntacctParams.ts | 8 + ...eateWorkspaceReportFieldListValueParams.ts | 10 + .../CreateWorkspaceReportFieldParams.ts | 10 + .../DeleteMoneyRequestOnSearchParams.ts | 6 + .../EnablePolicyExpensifyCardsParams.ts | 7 + ...ableWorkspaceReportFieldListValueParams.ts | 10 + .../HoldMoneyRequestOnSearchParams.ts | 7 + .../OpenPolicyExpensifyCardsPageParams.ts | 6 + .../parameters/PolicyReportFieldsReplace.ts | 10 + ...moveWorkspaceReportFieldListValueParams.ts | 10 + ...RequestExpensifyCardLimitIncreaseParams.ts | 6 + src/libs/API/parameters/Search.ts | 2 +- .../parameters/SyncPolicyToNetSuiteParams.ts | 6 + .../UnapproveExpenseReportParams.ts | 6 + .../UnholdMoneyRequestOnSearchParams.ts | 6 + .../parameters/UpdateBillingCurrencyParams.ts | 2 +- .../UpdateNetSuiteCustomFormIDParams.ts | 10 + .../UpdateNetSuiteGenericTypeParams.ts | 7 + .../UpdateSageIntacctGenericParams.ts | 7 + .../UpdateSageIntacctGenericTypeParams.ts | 7 + ...eWorkspaceReportFieldInitialValueParams.ts | 10 + .../parameters/UpgradeToCorporateParams.ts | 6 + src/libs/API/parameters/index.ts | 24 +- src/libs/API/types.ts | 183 +- src/libs/CardUtils.ts | 5 + src/libs/ConnectionUtils.ts | 19 + src/libs/Console/index.ts | 7 +- src/libs/CurrencyUtils.ts | 16 +- src/libs/DateUtils.ts | 26 +- src/libs/DistanceRequestUtils.ts | 13 +- src/libs/E2E/reactNativeLaunchingTest.ts | 2 +- src/libs/E2E/tests/appStartTimeTest.e2e.ts | 3 +- src/libs/E2E/tests/chatOpeningTest.e2e.ts | 6 +- src/libs/E2E/tests/linkingTest.e2e.ts | 3 +- .../E2E/tests/openChatFinderPageTest.e2e.ts | 6 +- src/libs/E2E/tests/reportTypingTest.e2e.ts | 3 +- src/libs/E2E/types.ts | 12 +- src/libs/E2E/utils/NetworkInterceptor.ts | 2 +- src/libs/ExportOnyxState/common.ts | 34 + src/libs/ExportOnyxState/index.native.ts | 42 + src/libs/ExportOnyxState/index.ts | 50 + src/libs/Log.ts | 3 +- src/libs/Middleware/Pagination.ts | 137 ++ src/libs/Middleware/index.ts | 3 +- src/libs/Middleware/types.ts | 3 +- src/libs/ModifiedExpenseMessage.ts | 12 +- .../Navigation/AppNavigator/AuthScreens.tsx | 20 +- .../AppNavigator/CENTRAL_PANE_SCREENS.tsx | 22 +- .../ModalStackNavigators/index.tsx | 108 +- .../useModalScreenOptions.ts | 19 +- .../Navigators/ExplanationModalNavigator.tsx | 28 + .../Navigators/FullScreenNavigator.tsx | 1 + .../Navigators/OnboardingModalNavigator.tsx | 12 +- .../Navigators/RightModalNavigator.tsx | 17 +- .../AppNavigator/ReportScreenIDSetter.ts | 91 - .../AppNavigator/ReportScreenWrapper.tsx | 30 - .../BottomTabBar/index.tsx | 19 +- .../BottomTabBar/index.website.tsx | 23 +- .../createCustomFullScreenNavigator/index.tsx | 2 +- .../CustomRouter.ts | 32 +- .../createCustomStackNavigator/index.tsx | 2 +- src/libs/Navigation/FreezeWrapper.tsx | 2 +- src/libs/Navigation/Navigation.ts | 65 +- src/libs/Navigation/NavigationRoot.tsx | 60 +- src/libs/Navigation/dismissModalWithReport.ts | 2 +- .../Navigation/getTopmostCentralPaneRoute.ts | 2 +- .../Navigation/getTopmostReportActionID.ts | 2 +- src/libs/Navigation/getTopmostReportId.ts | 2 +- src/libs/Navigation/isReportOpenInRHP.ts | 17 + .../linkTo/getActionForBottomTabNavigator.ts | 7 +- src/libs/Navigation/linkTo/index.ts | 8 +- .../CENTRAL_PANE_TO_RHP_MAPPING.ts | 2 +- .../FULL_SCREEN_TO_RHP_MAPPING.ts | 69 +- src/libs/Navigation/linkingConfig/config.ts | 178 ++ .../linkingConfig/customGetPathFromState.ts | 11 +- .../linkingConfig/getAdaptedStateFromPath.ts | 11 +- .../getMatchingBottomTabRouteForState.ts | 2 +- src/libs/Navigation/linkingConfig/index.ts | 1 - .../linkingConfig/subscribe/index.native.ts | 2 +- src/libs/Navigation/switchPolicyID.ts | 6 +- src/libs/Navigation/types.ts | 245 ++- src/libs/NavigationUtils.ts | 25 +- src/libs/Network/enhanceParameters.ts | 21 + .../PushNotification/index.native.ts | 2 +- .../shouldShowPushNotification.ts | 2 +- .../subscribePushNotification/index.ts | 13 +- .../clearReportNotifications/index.native.ts | 4 +- src/libs/OnyxAwareParser.ts | 52 - src/libs/OptionsListUtils.ts | 87 +- src/libs/PaginationUtils.ts | 195 +++ src/libs/Parser.ts | 51 + src/libs/Permissions.ts | 10 + src/libs/PersonalDetailsUtils.ts | 12 +- src/libs/PolicyUtils.ts | 258 ++- src/libs/Pusher/pusher.ts | 2 +- src/libs/ReportActionComposeFocusManager.ts | 11 +- .../index.android.ts | 14 + .../ReportActionItemEventHandler/index.ts | 7 + .../ReportActionItemEventHandler/types.ts | 8 + src/libs/ReportActionsUtils.ts | 234 +-- src/libs/ReportConnection.ts | 47 + src/libs/ReportUtils.ts | 437 ++--- src/libs/SearchUtils.ts | 66 +- src/libs/SidebarUtils.ts | 128 +- src/libs/StringUtils.ts | 11 +- src/libs/SubscriptionUtils.ts | 364 +++- src/libs/TaskUtils.ts | 16 +- src/libs/TransactionUtils.ts | 126 +- src/libs/TripReservationUtils.ts | 25 +- src/libs/UnreadIndicatorUpdater/index.ts | 24 +- src/libs/ValidationUtils.ts | 2 +- src/libs/WorkspaceReportFieldUtils.ts | 89 + src/libs/WorkspacesSettingsUtils.ts | 10 +- src/libs/__mocks__/Permissions.ts | 3 +- src/libs/actions/Card.ts | 46 +- src/libs/actions/IOU.ts | 196 ++- src/libs/actions/Link.ts | 33 +- .../utils/__mocks__/index.ts | 2 +- src/libs/actions/PaymentMethods.ts | 39 +- src/libs/actions/Policy/Member.ts | 49 +- src/libs/actions/Policy/Policy.ts | 172 +- src/libs/actions/Policy/ReportField.ts | 545 ++++++ src/libs/actions/Policy/Tag.ts | 31 +- src/libs/actions/PriorityMode.ts | 19 +- src/libs/actions/Report.ts | 279 ++-- src/libs/actions/ReportActions.ts | 12 +- src/libs/actions/Search.ts | 30 +- src/libs/actions/Session/index.ts | 9 +- src/libs/actions/Subscription.ts | 58 +- src/libs/actions/Task.ts | 132 +- src/libs/actions/Transaction.ts | 21 +- src/libs/actions/User.ts | 5 + src/libs/actions/Welcome.ts | 152 +- src/libs/actions/__mocks__/App.ts | 4 +- .../actions/connections/NetSuiteCommands.ts | 799 ++++++++- src/libs/actions/connections/SageIntacct.ts | 760 +++++++++ src/libs/actions/connections/index.ts | 74 +- src/libs/fileDownload/FileUtils.ts | 4 +- src/libs/fileDownload/index.ios.ts | 2 +- src/libs/fileDownload/index.ts | 8 +- src/libs/freezeScreenWithLazyLoading.tsx | 20 + .../hasCompletedGuidedSetupFlowSelector.ts | 12 + src/libs/markAllPolicyReportsAsRead.ts | 35 +- src/libs/migrateOnyx.ts | 2 - .../CheckForPreviousReportActionID.ts | 66 - src/libs/migrations/Participants.ts | 10 +- .../PersonalDetails => }/AddressPage.tsx | 73 +- src/pages/EditReportFieldPage.tsx | 4 +- src/pages/EnablePayments/EnablePayments.tsx | 11 +- src/pages/EnablePayments/IdologyQuestions.tsx | 50 +- .../PersonalInfo/PersonalInfo.tsx | 23 +- src/pages/InviteReportParticipantsPage.tsx | 3 +- .../LogInWithShortLivedAuthTokenPage.tsx | 8 +- src/pages/LogOutPreviousUserPage.tsx | 14 +- src/pages/NewChatConfirmPage.tsx | 4 +- src/pages/NewChatPage.tsx | 51 +- .../BaseOnboardingPersonalDetails.tsx | 21 +- src/pages/OnboardingPersonalDetails/types.ts | 3 + .../BaseOnboardingPurpose.tsx | 39 +- src/pages/OnboardingPurpose/types.ts | 23 +- .../OnboardingWork/BaseOnboardingWork.tsx | 3 - .../PrivateNotes/PrivateNotesEditPage.tsx | 6 +- src/pages/ProfilePage.tsx | 16 + .../ReimbursementAccount/BankAccountStep.tsx | 5 +- .../ReimbursementAccountPage.tsx | 4 +- src/pages/ReportDetailsPage.tsx | 161 +- .../WorkspaceAdminRestrictedAction.tsx | 3 +- .../WorkspaceOwnerRestrictedAction.tsx | 1 + .../WorkspaceUserRestrictedAction.tsx | 3 +- src/pages/RoomDescriptionPage.tsx | 4 +- src/pages/RoomInvitePage.tsx | 5 +- src/pages/RoomMembersPage.tsx | 8 +- src/pages/Search/SearchHoldReasonPage.tsx | 69 + src/pages/Search/SearchPage.tsx | 17 - .../DuplicateTransactionItem.tsx | 29 +- .../DuplicateTransactionsList.tsx | 4 + src/pages/TransactionDuplicate/Review.tsx | 8 +- .../TransactionDuplicate/ReviewBillable.tsx | 54 + .../TransactionDuplicate/ReviewCategory.tsx | 58 + .../ReviewDescription.tsx | 57 + .../TransactionDuplicate/ReviewFields.tsx | 91 + .../TransactionDuplicate/ReviewMerchant.tsx | 58 + .../ReviewReimbursable.tsx | 54 + src/pages/TransactionDuplicate/ReviewTag.tsx | 58 + .../TransactionDuplicate/ReviewTaxCode.tsx | 64 + src/pages/TransactionReceiptPage.tsx | 2 +- src/pages/UnlinkLoginPage.tsx | 2 +- src/pages/ValidateLoginPage/index.tsx | 2 +- src/pages/ValidateLoginPage/index.website.tsx | 2 +- src/pages/home/HeaderView.tsx | 107 +- src/pages/home/ReportScreen.tsx | 76 +- .../report/AnimatedEmptyStateBackground.tsx | 1 + .../BaseReportActionContextMenu.tsx | 1 - .../report/ContextMenu/ContextMenuActions.tsx | 21 +- .../AttachmentPickerWithMenuItems.tsx | 22 +- .../ComposerWithSuggestions.tsx | 12 +- .../ReportActionCompose.tsx | 6 +- .../SilentCommentUpdater/index.android.tsx | 2 +- .../SilentCommentUpdater/index.tsx | 2 +- src/pages/home/report/ReportActionItem.tsx | 23 +- .../report/ReportActionItemContentCreated.tsx | 2 +- .../home/report/ReportActionItemCreated.tsx | 2 +- .../home/report/ReportActionItemFragment.tsx | 1 + .../report/ReportActionItemMessageEdit.tsx | 14 +- .../report/ReportActionItemParentAction.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 11 +- .../report/ReportActionsListItemRenderer.tsx | 4 +- src/pages/home/report/ReportActionsView.tsx | 45 +- src/pages/home/report/ReportFooter.tsx | 2 +- .../home/report/UserTypingEventListener.tsx | 2 +- .../withReportAndPrivateNotesOrNotFound.tsx | 2 +- .../withReportAndReportActionOrNotFound.tsx | 2 +- .../home/report/withReportOrNotFound.tsx | 2 +- src/pages/home/sidebar/SidebarLinks.tsx | 4 +- src/pages/home/sidebar/SidebarLinksData.tsx | 27 +- .../FloatingActionButtonAndPopover.tsx | 44 +- src/pages/iou/HoldReasonFormView.tsx | 72 + src/pages/iou/HoldReasonPage.tsx | 55 +- src/pages/iou/MoneyRequestAmountForm.tsx | 27 +- .../request/IOURequestRedirectToStartPage.tsx | 2 +- .../MoneyRequestParticipantsSelector.tsx | 19 +- .../iou/request/step/IOURequestStepAmount.tsx | 4 +- .../request/step/IOURequestStepCategory.tsx | 2 +- .../step/IOURequestStepConfirmation.tsx | 6 +- .../request/step/IOURequestStepDistance.tsx | 2 +- .../step/IOURequestStepDistanceRate.tsx | 2 +- .../request/step/IOURequestStepMerchant.tsx | 3 +- .../step/IOURequestStepScan/index.native.tsx | 1 + .../request/step/IOURequestStepScan/index.tsx | 4 +- .../step/IOURequestStepTaxAmountPage.tsx | 2 +- .../step/IOURequestStepTaxRatePage.tsx | 3 +- .../request/step/IOURequestStepWaypoint.tsx | 4 +- .../step/withWritableReportOrNotFound.tsx | 2 +- src/pages/settings/AboutPage/AboutPage.tsx | 2 +- src/pages/settings/InitialSettingsPage.tsx | 81 +- .../PaymentCard/ChangeCurrency/index.tsx | 2 +- .../Contacts/ContactMethodDetailsPage.tsx | 2 +- .../Profile/Contacts/NewContactMethodPage.tsx | 2 +- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 2 +- .../CustomStatus/StatusClearAfterPage.tsx | 4 +- .../Profile/CustomStatus/StatusPage.tsx | 2 +- .../settings/Profile/DisplayNamePage.tsx | 2 + .../PersonalDetails/PersonalAddressPage.tsx | 61 + src/pages/settings/Profile/PronounsPage.tsx | 2 +- .../TwoFactorAuth/Steps/CodesStep.tsx | 2 +- .../TwoFactorAuth/TwoFactorAuthSteps.tsx | 23 +- .../BillingBanner/BillingBanner.tsx | 55 +- .../SubscriptionBillingBanner.tsx | 39 + .../TrialStartedBillingBanner.tsx | 21 + .../Subscription/CardSection/CardSection.tsx | 159 +- .../Subscription/CardSection/utils.ts | 131 +- .../ChangeBillingCurrency/index.tsx | 4 +- .../Subscription/PaymentCard/index.tsx | 4 +- .../SubscriptionSettings/index.tsx | 11 +- .../Troubleshoot/TroubleshootPage.tsx | 35 +- .../settings/Wallet/ExpensifyCardPage.tsx | 2 +- .../settings/Wallet/TransferBalancePage.tsx | 2 +- src/pages/signin/LoginForm/BaseLoginForm.tsx | 3 +- .../BackgroundImage/index.ios.tsx | 5 +- .../SignInPageLayout/SignInHeroImage.tsx | 4 +- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 2 +- src/pages/tasks/NewTaskDescriptionPage.tsx | 7 +- src/pages/tasks/NewTaskDetailsPage.tsx | 9 +- src/pages/tasks/TaskDescriptionPage.tsx | 6 +- src/pages/wallet/WalletStatementPage.tsx | 2 +- .../workspace/AccessOrNotFoundWrapper.tsx | 2 +- src/pages/workspace/WorkspaceInitialPage.tsx | 40 +- .../workspace/WorkspaceInviteMessagePage.tsx | 15 +- src/pages/workspace/WorkspaceInvitePage.tsx | 25 +- src/pages/workspace/WorkspaceJoinUserPage.tsx | 3 +- src/pages/workspace/WorkspaceMembersPage.tsx | 47 +- .../workspace/WorkspaceMoreFeaturesPage.tsx | 72 +- src/pages/workspace/WorkspaceNewRoomPage.tsx | 2 +- .../workspace/WorkspacePageWithSections.tsx | 2 +- .../workspace/WorkspaceProfileAddressPage.tsx | 117 +- .../WorkspaceProfileDescriptionPage.tsx | 9 +- src/pages/workspace/WorkspaceProfilePage.tsx | 20 +- src/pages/workspace/WorkspacesListRow.tsx | 6 +- .../accounting/PolicyAccountingPage.tsx | 66 +- .../EnterSageIntacctCredentialsPage.tsx | 98 ++ .../intacct/ExistingConnectionsPage.tsx | 64 + .../intacct/SageIntacctPrerequisitesPage.tsx | 104 ++ .../advanced/SageIntacctAdvancedPage.tsx | 149 ++ .../SageIntacctPaymentAccountPage.tsx | 76 + .../intacct/export/SageIntacctDatePage.tsx | 74 + .../export/SageIntacctDefaultVendorPage.tsx | 112 ++ .../intacct/export/SageIntacctExportPage.tsx | 91 + ...ctNonReimbursableCreditCardAccountPage.tsx | 72 + ...SageIntacctNonReimbursableExpensesPage.tsx | 174 ++ .../SageIntacctPreferredExporterPage.tsx | 104 ++ .../SageIntacctReimbursableExpensesPage.tsx | 136 ++ .../intacct/import/DimensionTypeSelector.tsx | 75 + .../SageIntacctAddUserDimensionPage.tsx | 101 ++ .../SageIntacctEditUserDimensionsPage.tsx | 141 ++ .../intacct/import/SageIntacctImportPage.tsx | 144 ++ .../import/SageIntacctMappingsTypePage.tsx | 81 + .../import/SageIntacctToggleMappingsPage.tsx | 122 ++ .../import/SageIntacctUserDimensionsPage.tsx | 120 ++ .../NetSuiteTokenInputPage.tsx | 90 + .../substeps/NetSuiteTokenInputForm.tsx | 91 + .../substeps/NetSuiteTokenSetupContent.tsx | 44 + .../advanced/NetSuiteAdvancedPage.tsx | 291 ++++ .../NetSuiteApprovalAccountSelectPage.tsx | 90 + .../NetSuiteCollectionAccountSelectPage.tsx | 89 + .../advanced/NetSuiteCustomFormIDPage.tsx | 101 ++ ...teExpenseReportApprovalLevelSelectPage.tsx | 74 + ...iteJournalEntryApprovalLevelSelectPage.tsx | 77 + ...NetSuiteReimbursementAccountSelectPage.tsx | 89 + ...SuiteVendorBillApprovalLevelSelectPage.tsx | 81 + .../export/NetSuiteDateSelectPage.tsx | 74 + .../NetSuiteExportConfigurationPage.tsx | 247 +++ ...iteExportExpensesDestinationSelectPage.tsx | 72 + ...nsesJournalPostingPreferenceSelectPage.tsx | 71 + .../export/NetSuiteExportExpensesPage.tsx | 140 ++ ...ExportExpensesPayableAccountSelectPage.tsx | 89 + ...NetSuiteExportExpensesVendorSelectPage.tsx | 80 + ...etSuiteInvoiceItemPreferenceSelectPage.tsx | 105 ++ .../export/NetSuiteInvoiceItemSelectPage.tsx | 74 + .../NetSuitePreferredExporterSelectPage.tsx | 104 ++ ...eProvincialTaxPostingAccountSelectPage.tsx | 80 + .../NetSuiteReceivableAccountSelectPage.tsx | 76 + .../NetSuiteTaxPostingAccountSelectPage.tsx | 82 + .../import/NetSuiteImportCustomFieldEdit.tsx | 170 ++ .../NetSuiteCustomFieldMappingPicker.tsx | 42 + .../NetSuiteCustomListPicker.tsx | 67 + .../NetSuiteCustomListSelectorModal.tsx | 112 ++ .../NetSuiteImportAddCustomListPage.tsx | 176 ++ .../NetSuiteImportAddCustomSegmentPage.tsx | 220 +++ .../NetSuiteMenuWithTopDescriptionForm.tsx | 24 + .../substeps/ChooseCustomListStep.tsx | 28 + .../substeps/ChooseSegmentTypeStep.tsx | 49 + .../substeps/ConfirmCustomListStep.tsx | 39 + .../substeps/ConfirmCustomSegmentList.tsx | 46 + .../substeps/CustomSegmentInternalIdStep.tsx | 44 + .../substeps/CustomSegmentNameStep.tsx | 45 + .../substeps/CustomSegmentScriptIdStep.tsx | 49 + .../substeps/MappingStep.tsx | 37 + .../substeps/TransactionFieldIDStep.tsx | 38 + .../import/NetSuiteImportCustomFieldPage.tsx | 164 ++ .../import/NetSuiteImportCustomFieldView.tsx | 137 ++ ...uiteImportCustomersOrProjectSelectPage.tsx | 73 + .../NetSuiteImportCustomersOrProjectsPage.tsx | 141 ++ .../import/NetSuiteImportMappingPage.tsx | 114 ++ .../netsuite/import/NetSuiteImportPage.tsx | 134 ++ .../workspace/accounting/netsuite/types.ts | 72 + .../advanced/QuickbooksAccountSelectPage.tsx | 2 +- .../qbo/advanced/QuickbooksAdvancedPage.tsx | 6 +- ...uickbooksCompanyCardExpenseAccountPage.tsx | 13 +- ...ompanyCardExpenseAccountSelectCardPage.tsx | 3 +- ...oksCompanyCardExpenseAccountSelectPage.tsx | 9 +- .../QuickbooksExportConfigurationPage.tsx | 4 +- ...NonReimbursableDefaultVendorSelectPage.tsx | 4 +- ...oksOutOfPocketExpenseConfigurationPage.tsx | 2 +- ...ooksOutOfPocketExpenseEntitySelectPage.tsx | 3 +- ...ooksPreferredExporterConfigurationPage.tsx | 6 +- .../xero/advanced/XeroAdvancedPage.tsx | 10 +- .../export/XeroExportConfigurationPage.tsx | 4 +- .../XeroPreferredExporterSelectPage.tsx | 6 +- .../workspace/card/issueNew/AssigneeStep.tsx | 154 ++ .../workspace/card/issueNew/CardNameStep.tsx | 104 ++ .../workspace/card/issueNew/CardTypeStep.tsx | 93 ++ .../card/issueNew/ConfirmationStep.tsx | 130 ++ .../card/issueNew/IssueNewCardPage.tsx | 41 + .../workspace/card/issueNew/LimitStep.tsx | 105 ++ .../workspace/card/issueNew/LimitTypeStep.tsx | 125 ++ .../categories/CategorySettingsPage.tsx | 12 +- .../categories/WorkspaceCategoriesPage.tsx | 22 +- .../WorkspaceCategoriesSettingsPage.tsx | 15 +- .../distanceRates/PolicyDistanceRatesPage.tsx | 6 +- .../PolicyDistanceRatesSettingsPage.tsx | 2 +- .../expensifyCard/WorkspaceCardListHeader.tsx | 79 + .../expensifyCard/WorkspaceCardListRow.tsx | 78 + .../expensifyCard/WorkspaceCardsListLabel.tsx | 133 ++ .../WorkspaceExpensifyCardListPage.tsx | 174 ++ .../WorkspaceExpensifyCardPage.tsx | 37 + .../WorkspaceExpensifyCardPageEmptyState.tsx | 74 + .../members/WorkspaceMemberDetailsPage.tsx | 12 +- .../members/WorkspaceOwnerChangeCheck.tsx | 2 +- .../WorkspaceOwnerChangeWrapperPage.tsx | 2 + .../members/WorkspaceOwnerPaymentCardForm.tsx | 4 +- .../WorkspaceRateAndUnitPage/InitialPage.tsx | 2 +- .../WorkspaceRateAndUnitPage/RatePage.tsx | 2 +- .../WorkspaceRateAndUnitPage/UnitPage.tsx | 2 +- .../reportFields/CreateReportFieldPage.tsx | 195 +++ .../InitialListValueSelectorModal.tsx | 74 + .../ReportFieldsInitialListValuePicker.tsx | 48 + .../InitialListValueSelector/index.tsx | 75 + .../ReportFieldAddListValuePage.tsx | 96 ++ .../reportFields/ReportFieldEditValuePage.tsx | 103 ++ .../ReportFieldInitialValuePage.tsx | 149 ++ .../ReportFieldListValuesPage.tsx | 318 ++++ .../reportFields/ReportFieldSettingsPage.tsx | 126 ++ .../ReportFieldTypePicker/index.tsx | 60 + .../ReportFieldValueSettingsPage.tsx | 144 ++ .../TypeSelector/TypeSelectorModal.tsx | 70 + .../reportFields/TypeSelector/index.tsx | 73 + .../WorkspaceReportFieldsPage.tsx | 149 +- .../workspace/tags/WorkspaceTagsPage.tsx | 25 +- .../tags/WorkspaceTagsSettingsPage.tsx | 10 +- .../workspace/tags/WorkspaceViewTagsPage.tsx | 12 +- src/pages/workspace/tags/types.ts | 28 +- src/pages/workspace/taxes/NamePage.tsx | 4 +- .../workspace/taxes/WorkspaceTaxesPage.tsx | 10 +- .../workspace/upgrade/UpgradeConfirmation.tsx | 40 + src/pages/workspace/upgrade/UpgradeIntro.tsx | 79 + .../upgrade/WorkspaceUpgradePage.tsx | 64 + src/pages/workspace/withPolicy.tsx | 5 + .../workflows/ToggleSettingsOptionRow.tsx | 46 +- .../WorkspaceWorkflowsApproverPage.tsx | 2 +- .../workflows/WorkspaceWorkflowsPayerPage.tsx | 2 +- src/stories/Composer.stories.tsx | 1 + src/styles/index.ts | 75 +- src/styles/theme/themes/dark.ts | 2 +- src/styles/theme/themes/light.ts | 4 +- src/styles/utils/flex.ts | 4 + src/styles/utils/index.ts | 61 +- src/styles/utils/sizing.ts | 3 + src/styles/utils/spacing.ts | 8 + src/styles/variables.ts | 1 + src/types/form/AddPaymentCardForm.ts | 2 +- src/types/form/ChangeBillingCurrencyForm.ts | 2 +- src/types/form/IssueNewExpensifyCardForm.ts | 20 + src/types/form/NetSuiteCustomFieldForm.ts | 29 + src/types/form/NetSuiteCustomFormIDForm.ts | 16 + src/types/form/NetSuiteTokenInputForm.ts | 22 + src/types/form/SageIntacctDimensionsForm.ts | 21 + src/types/form/SageIntactCredentialsForm.ts | 22 + src/types/form/WorkspaceReportFieldForm.ts | 31 + src/types/form/index.ts | 7 + src/types/modules/jest.d.ts | 13 + src/types/onyx/Account.ts | 3 + src/types/onyx/BankAccount.ts | 3 + src/types/onyx/BillingGraceEndPeriod.ts | 6 - src/types/onyx/BillingStatus.ts | 16 + src/types/onyx/Card.ts | 43 +- src/types/onyx/ExpensifyCardSettings.ts | 15 + src/types/onyx/OldDotAction.ts | 484 ++++++ src/types/onyx/OriginalMessage.ts | 62 +- src/types/onyx/Pages.ts | 59 + src/types/onyx/Policy.ts | 392 ++++- src/types/onyx/PolicyEmployee.ts | 8 +- src/types/onyx/Report.ts | 3 + src/types/onyx/ReportAction.ts | 11 +- src/types/onyx/Request.ts | 28 +- src/types/onyx/SearchResults.ts | 25 +- src/types/onyx/StripeCustomerID.ts | 16 + src/types/onyx/TryNewDot.ts | 25 + src/types/onyx/UserMetadata.ts | 3 + src/types/onyx/index.ts | 14 +- src/utils/createProxyForObject.ts | 2 +- tests/actions/OnyxUpdateManagerTest.ts | 2 +- tests/actions/PolicyTagTest.ts | 2 +- tests/actions/ReportFieldTest.ts | 737 ++++++++ tests/e2e/ADDING_TESTS.md | 3 +- tests/e2e/README.md | 3 +- tests/e2e/TestSpec.yml | 1 + tests/e2e/compare/compare.ts | 13 +- tests/e2e/compare/output/console.ts | 4 +- tests/e2e/compare/output/format.ts | 18 +- tests/e2e/compare/output/markdown.ts | 19 +- tests/e2e/merge.ts | 27 - tests/e2e/testRunner.ts | 35 +- tests/perf-test/ChatFinderPage.perf-test.tsx | 11 +- tests/perf-test/OptionsListUtils.perf-test.ts | 4 +- .../ReportActionCompose.perf-test.tsx | 20 +- .../perf-test/ReportActionsList.perf-test.tsx | 4 +- .../perf-test/ReportActionsUtils.perf-test.ts | 4 +- tests/perf-test/ReportScreen.perf-test.tsx | 20 +- tests/perf-test/ReportUtils.perf-test.ts | 12 +- tests/perf-test/SidebarLinks.perf-test.tsx | 5 + tests/perf-test/SidebarUtils.perf-test.ts | 4 +- tests/ui/PaginationTest.tsx | 350 ++++ tests/ui/UnreadIndicatorsTest.tsx | 137 +- tests/unit/CIGitLogicTest.ts | 69 +- tests/unit/CardsSectionUtilsTest.ts | 201 +++ tests/unit/CurrencyUtilsTest.ts | 45 +- tests/unit/DateUtilsTest.ts | 14 + tests/unit/E2EMarkdownTest.ts | 18 + tests/unit/GithubUtilsTest.ts | 4 +- tests/unit/MigrationTest.ts | 227 --- tests/unit/PaginationUtilsTest.ts | 568 +++++++ tests/unit/ReportActionsUtilsTest.ts | 1482 +---------------- tests/unit/RequestTest.ts | 4 +- tests/unit/SidebarOrderTest.ts | 2 +- tests/unit/SubscriptionUtilsTest.ts | 212 ++- tests/unit/UnreadIndicatorUpdaterTest.ts | 8 +- .../__snapshots__/E2EMarkdownTest.ts.snap | 24 + tests/unit/createOrUpdateStagingDeployTest.ts | 2 +- tests/unit/enhanceParametersTest.ts | 5 + tests/unit/markPullRequestsAsDeployedTest.ts | 12 +- tests/unit/sanitizeStringForJSONParseTest.ts | 7 +- tests/utils/LHNTestUtils.tsx | 8 +- tests/utils/ReportTestUtils.ts | 2 - tests/utils/TestHelper.ts | 160 +- tests/utils/collections/reportActions.ts | 12 +- tests/utils/createAddListenerMock.ts | 28 + tests/utils/debug.ts | 114 ++ tsconfig.json | 1 + workflow_tests/mocks/deployMocks.ts | 4 + workflow_tests/utils/JobMocker.ts | 4 +- workflow_tests/utils/preGenerateTest.ts | 2 +- workflow_tests/utils/utils.ts | 2 +- 771 files changed, 28480 insertions(+), 6601 deletions(-) create mode 100644 assets/images/circular-arrow-backwards.svg create mode 100644 assets/images/computer.svg create mode 100644 assets/images/expensifyCard/cardIllustration.svg create mode 100644 assets/images/integrationicons/sage-intacct-icon-square.svg create mode 100644 assets/images/product-illustrations/folder-with-papers.svg create mode 100644 assets/images/simple-illustrations/simple-illustration__virtualcard.svg delete mode 100644 docs/articles/expensify-classic/expensify-card/Auto-Reconciliation.md create mode 100644 docs/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation.md rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Approve-and-pay-expenses.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Connect-a-Business-Bank-Account.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Create-an-expense.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Distance-Requests.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Resolve-Errors-Adding-a-Bank-Account.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Send-an-invoice.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Set-up-your-wallet.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Split-an-expense.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Track-expenses.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Unlock-a-Business-Bank-Account.md (100%) rename docs/articles/new-expensify/{expenses => expenses-&-payments}/Validate-a-Business-Bank-Account.md (100%) create mode 100644 docs/articles/new-expensify/expensify-card/Enable-Expensify-Card-notifications.md create mode 100644 docs/assets/images/ExpensifyHelp-Invoice-1.png create mode 100644 docs/assets/images/ExpensifyHelp-QBO-4.png create mode 100644 docs/assets/images/ExpensifyHelp-QBO-5.png rename docs/new-expensify/hubs/{expenses => expenses-&-payments}/index.html (100%) delete mode 100644 patches/@expensify+react-native-live-markdown+0.1.85.patch create mode 100644 patches/@expensify+react-native-live-markdown+0.1.91.patch delete mode 100644 patches/@perf-profiler+android+0.12.0.patch create mode 100644 patches/@perf-profiler+android+0.12.1.patch delete mode 100644 patches/@perf-profiler+reporter+0.8.1.patch delete mode 100644 patches/eslint-plugin-react-compiler+0.0.0-experimental-53bb89e-20240515.patch create mode 100644 patches/react-compiler-healthcheck+0.0.0-experimental-b130d5f-20240625.patch rename patches/{react-native-keyboard-controller+1.12.2.patch.patch => react-native-keyboard-controller+1.12.2.patch} (100%) rename patches/{react-native-reanimated+3.7.2+001+fix-boost-dependency.patch => react-native-reanimated+3.8.1+001+fix-boost-dependency.patch} (100%) rename patches/{react-native-reanimated+3.7.2+002+copy-state.patch => react-native-reanimated+3.8.1+002+copy-state.patch} (100%) rename patches/{react-native-reanimated+3.7.2.patch => react-native-reanimated+3.8.1.patch} (100%) delete mode 100644 patches/react-native-screens+3.30.1+001+fix-screen-type.patch create mode 100755 scripts/applyPatches.sh create mode 100644 src/components/AccountingConnectionConfirmationModal.tsx create mode 100644 src/components/Attachments/AttachmentCarousel/useCarouselContextEvents.ts create mode 100644 src/components/ConnectToNetSuiteButton/index.tsx create mode 100644 src/components/ConnectToNetSuiteButton/types.ts create mode 100644 src/components/ConnectToSageIntacctButton/index.tsx create mode 100644 src/components/ExplanationModal.tsx create mode 100644 src/components/HybridAppMiddleware/index.ios.tsx create mode 100644 src/components/HybridAppMiddleware/index.tsx delete mode 100644 src/components/Reactions/getEmojiReactionBubbleTextOffsetStyle/index.ios.ts delete mode 100644 src/components/Reactions/getEmojiReactionBubbleTextOffsetStyle/index.ts create mode 100644 src/components/Search/SearchContext.tsx create mode 100644 src/components/Search/SearchListWithHeader.tsx create mode 100644 src/components/Search/SearchPageHeader.tsx rename src/components/{Search.tsx => Search/index.tsx} (80%) create mode 100644 src/components/Search/types.ts create mode 100644 src/hooks/useExitTo.ts create mode 100644 src/hooks/useGeographicalStateAndCountryFromRoute.ts delete mode 100644 src/hooks/useGeographicalStateFromRoute.ts delete mode 100644 src/hooks/useIsSplashHidden.ts create mode 100644 src/hooks/useReviewDuplicatesNavigation.tsx create mode 100644 src/hooks/useSplashScreen.ts create mode 100644 src/hooks/useSubscriptionPossibleCostSavings.ts create mode 100644 src/libs/API/parameters/ConnectPolicyToNetSuiteParams.ts create mode 100644 src/libs/API/parameters/ConnectPolicyToSageIntacctParams.ts create mode 100644 src/libs/API/parameters/CreateWorkspaceReportFieldListValueParams.ts create mode 100644 src/libs/API/parameters/CreateWorkspaceReportFieldParams.ts create mode 100644 src/libs/API/parameters/DeleteMoneyRequestOnSearchParams.ts create mode 100644 src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts create mode 100644 src/libs/API/parameters/EnableWorkspaceReportFieldListValueParams.ts create mode 100644 src/libs/API/parameters/HoldMoneyRequestOnSearchParams.ts create mode 100644 src/libs/API/parameters/OpenPolicyExpensifyCardsPageParams.ts create mode 100644 src/libs/API/parameters/PolicyReportFieldsReplace.ts create mode 100644 src/libs/API/parameters/RemoveWorkspaceReportFieldListValueParams.ts create mode 100644 src/libs/API/parameters/RequestExpensifyCardLimitIncreaseParams.ts create mode 100644 src/libs/API/parameters/SyncPolicyToNetSuiteParams.ts create mode 100644 src/libs/API/parameters/UnapproveExpenseReportParams.ts create mode 100644 src/libs/API/parameters/UnholdMoneyRequestOnSearchParams.ts create mode 100644 src/libs/API/parameters/UpdateNetSuiteCustomFormIDParams.ts create mode 100644 src/libs/API/parameters/UpdateNetSuiteGenericTypeParams.ts create mode 100644 src/libs/API/parameters/UpdateSageIntacctGenericParams.ts create mode 100644 src/libs/API/parameters/UpdateSageIntacctGenericTypeParams.ts create mode 100644 src/libs/API/parameters/UpdateWorkspaceReportFieldInitialValueParams.ts create mode 100644 src/libs/API/parameters/UpgradeToCorporateParams.ts create mode 100644 src/libs/ConnectionUtils.ts create mode 100644 src/libs/ExportOnyxState/common.ts create mode 100644 src/libs/ExportOnyxState/index.native.ts create mode 100644 src/libs/ExportOnyxState/index.ts create mode 100644 src/libs/Middleware/Pagination.ts create mode 100644 src/libs/Navigation/AppNavigator/Navigators/ExplanationModalNavigator.tsx delete mode 100644 src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts delete mode 100644 src/libs/Navigation/AppNavigator/ReportScreenWrapper.tsx create mode 100644 src/libs/Navigation/isReportOpenInRHP.ts delete mode 100644 src/libs/OnyxAwareParser.ts create mode 100644 src/libs/PaginationUtils.ts create mode 100644 src/libs/Parser.ts create mode 100644 src/libs/ReportActionItemEventHandler/index.android.ts create mode 100644 src/libs/ReportActionItemEventHandler/index.ts create mode 100644 src/libs/ReportActionItemEventHandler/types.ts create mode 100644 src/libs/ReportConnection.ts create mode 100644 src/libs/WorkspaceReportFieldUtils.ts create mode 100644 src/libs/actions/Policy/ReportField.ts create mode 100644 src/libs/actions/connections/SageIntacct.ts create mode 100644 src/libs/freezeScreenWithLazyLoading.tsx create mode 100644 src/libs/hasCompletedGuidedSetupFlowSelector.ts delete mode 100644 src/libs/migrations/CheckForPreviousReportActionID.ts rename src/pages/{settings/Profile/PersonalDetails => }/AddressPage.tsx (57%) create mode 100644 src/pages/Search/SearchHoldReasonPage.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewBillable.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewCategory.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewDescription.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewFields.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewMerchant.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewReimbursable.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewTag.tsx create mode 100644 src/pages/TransactionDuplicate/ReviewTaxCode.tsx create mode 100644 src/pages/iou/HoldReasonFormView.tsx create mode 100644 src/pages/settings/Profile/PersonalDetails/PersonalAddressPage.tsx create mode 100644 src/pages/settings/Subscription/CardSection/BillingBanner/SubscriptionBillingBanner.tsx create mode 100644 src/pages/settings/Subscription/CardSection/BillingBanner/TrialStartedBillingBanner.tsx create mode 100644 src/pages/workspace/accounting/intacct/EnterSageIntacctCredentialsPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/ExistingConnectionsPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/SageIntacctPrerequisitesPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/advanced/SageIntacctAdvancedPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/advanced/SageIntacctPaymentAccountPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctDatePage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctDefaultVendorPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctExportPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableCreditCardAccountPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctNonReimbursableExpensesPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctPreferredExporterPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/export/SageIntacctReimbursableExpensesPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/DimensionTypeSelector.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/SageIntacctAddUserDimensionPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/SageIntacctEditUserDimensionsPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/SageIntacctImportPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/SageIntacctMappingsTypePage.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/SageIntacctToggleMappingsPage.tsx create mode 100644 src/pages/workspace/accounting/intacct/import/SageIntacctUserDimensionsPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/NetSuiteTokenInputPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenInputForm.tsx create mode 100644 src/pages/workspace/accounting/netsuite/NetSuiteTokenInput/substeps/NetSuiteTokenSetupContent.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteAdvancedPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteApprovalAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteCollectionAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteCustomFormIDPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteExpenseReportApprovalLevelSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteJournalEntryApprovalLevelSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteReimbursementAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/advanced/NetSuiteVendorBillApprovalLevelSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteDateSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteExportConfigurationPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesDestinationSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesJournalPostingPreferenceSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesPayableAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteExportExpensesVendorSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteInvoiceItemPreferenceSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteInvoiceItemSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuitePreferredExporterSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteProvincialTaxPostingAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteReceivableAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/export/NetSuiteTaxPostingAccountSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldEdit.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomFieldMappingPicker.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListPicker.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteCustomListSelectorModal.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomListPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteImportAddCustomSegmentPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/NetSuiteMenuWithTopDescriptionForm.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseCustomListStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ChooseSegmentTypeStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomListStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/ConfirmCustomSegmentList.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentInternalIdStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentNameStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/CustomSegmentScriptIdStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/MappingStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldNew/substeps/TransactionFieldIDStep.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomFieldView.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectSelectPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportCustomersOrProjectsPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportMappingPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/import/NetSuiteImportPage.tsx create mode 100644 src/pages/workspace/accounting/netsuite/types.ts create mode 100644 src/pages/workspace/card/issueNew/AssigneeStep.tsx create mode 100644 src/pages/workspace/card/issueNew/CardNameStep.tsx create mode 100644 src/pages/workspace/card/issueNew/CardTypeStep.tsx create mode 100644 src/pages/workspace/card/issueNew/ConfirmationStep.tsx create mode 100644 src/pages/workspace/card/issueNew/IssueNewCardPage.tsx create mode 100644 src/pages/workspace/card/issueNew/LimitStep.tsx create mode 100644 src/pages/workspace/card/issueNew/LimitTypeStep.tsx create mode 100644 src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx create mode 100644 src/pages/workspace/expensifyCard/WorkspaceCardListRow.tsx create mode 100644 src/pages/workspace/expensifyCard/WorkspaceCardsListLabel.tsx create mode 100644 src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx create mode 100644 src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx create mode 100644 src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPageEmptyState.tsx create mode 100644 src/pages/workspace/reportFields/CreateReportFieldPage.tsx create mode 100644 src/pages/workspace/reportFields/InitialListValueSelector/InitialListValueSelectorModal.tsx create mode 100644 src/pages/workspace/reportFields/InitialListValueSelector/ReportFieldsInitialListValuePicker.tsx create mode 100644 src/pages/workspace/reportFields/InitialListValueSelector/index.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldAddListValuePage.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldEditValuePage.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldInitialValuePage.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldListValuesPage.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldSettingsPage.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldTypePicker/index.tsx create mode 100644 src/pages/workspace/reportFields/ReportFieldValueSettingsPage.tsx create mode 100644 src/pages/workspace/reportFields/TypeSelector/TypeSelectorModal.tsx create mode 100644 src/pages/workspace/reportFields/TypeSelector/index.tsx create mode 100644 src/pages/workspace/upgrade/UpgradeConfirmation.tsx create mode 100644 src/pages/workspace/upgrade/UpgradeIntro.tsx create mode 100644 src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx create mode 100644 src/types/form/IssueNewExpensifyCardForm.ts create mode 100644 src/types/form/NetSuiteCustomFieldForm.ts create mode 100644 src/types/form/NetSuiteCustomFormIDForm.ts create mode 100644 src/types/form/NetSuiteTokenInputForm.ts create mode 100644 src/types/form/SageIntacctDimensionsForm.ts create mode 100644 src/types/form/SageIntactCredentialsForm.ts create mode 100644 src/types/form/WorkspaceReportFieldForm.ts create mode 100644 src/types/modules/jest.d.ts create mode 100644 src/types/onyx/BillingStatus.ts create mode 100644 src/types/onyx/ExpensifyCardSettings.ts create mode 100644 src/types/onyx/OldDotAction.ts create mode 100644 src/types/onyx/Pages.ts create mode 100644 src/types/onyx/StripeCustomerID.ts create mode 100644 src/types/onyx/TryNewDot.ts create mode 100644 tests/actions/ReportFieldTest.ts delete mode 100644 tests/e2e/merge.ts create mode 100644 tests/ui/PaginationTest.tsx create mode 100644 tests/unit/E2EMarkdownTest.ts create mode 100644 tests/unit/PaginationUtilsTest.ts create mode 100644 tests/unit/__snapshots__/E2EMarkdownTest.ts.snap create mode 100644 tests/utils/createAddListenerMock.ts create mode 100644 tests/utils/debug.ts diff --git a/.eslintrc.js b/.eslintrc.js index 22bb0158bc8e..198620c70b0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ const restrictedImportPaths = [ '', "For 'useWindowDimensions', please use '@src/hooks/useWindowDimensions' instead.", "For 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight', 'Pressable', please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from '@components/Pressable' instead.", - "For 'StatusBar', please use '@src/libs/StatusBar' instead.", + "For 'StatusBar', please use '@libs/StatusBar' instead.", "For 'Text', please use '@components/Text' instead.", "For 'ScrollView', please use '@components/ScrollView' instead.", ].join('\n'), @@ -59,8 +59,12 @@ const restrictedImportPaths = [ }, { name: 'expensify-common', - importNames: ['Device'], - message: "Do not import Device directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + importNames: ['Device', 'ExpensiMark'], + message: [ + '', + "For 'Device', do not import it directly, it's known to make VSCode's IntelliSense crash. Please import the desired module from `expensify-common/dist/Device` instead.", + "For 'ExpensiMark', please use '@libs/Parser' instead.", + ].join('\n'), }, ]; @@ -109,7 +113,6 @@ module.exports = { }, rules: { // TypeScript specific rules - '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-non-null-assertion': 'error', diff --git a/.github/actions/javascript/bumpVersion/bumpVersion.ts b/.github/actions/javascript/bumpVersion/bumpVersion.ts index ff43ab9ee5c5..92b81836ce13 100644 --- a/.github/actions/javascript/bumpVersion/bumpVersion.ts +++ b/.github/actions/javascript/bumpVersion/bumpVersion.ts @@ -49,7 +49,7 @@ if (!semanticVersionLevel || !versionUpdater.isValidSemverLevel(semanticVersionL console.log(`Invalid input for 'SEMVER_LEVEL': ${semanticVersionLevel}`, `Defaulting to: ${semanticVersionLevel}`); } -const {version: previousVersion}: PackageJson = JSON.parse(fs.readFileSync('./package.json').toString()); +const {version: previousVersion} = JSON.parse(fs.readFileSync('./package.json').toString()) as PackageJson; if (!previousVersion) { core.setFailed('Error: Could not read package.json'); } diff --git a/.github/actions/javascript/bumpVersion/index.js b/.github/actions/javascript/bumpVersion/index.js index 93ea47bed2ae..c8360931845a 100644 --- a/.github/actions/javascript/bumpVersion/index.js +++ b/.github/actions/javascript/bumpVersion/index.js @@ -1928,7 +1928,7 @@ class SemVer { do { const a = this.build[i] const b = other.build[i] - debug('prerelease compare', i, a, b) + debug('build compare', i, a, b) if (a === undefined && b === undefined) { return 0 } else if (b === undefined) { diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.ts b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.ts index aed8b9dcba0a..caff455e9fa5 100644 --- a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.ts +++ b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.ts @@ -8,13 +8,13 @@ import GitUtils from '@github/libs/GitUtils'; type IssuesCreateResponse = Awaited>['data']; -type PackageJSON = { +type PackageJson = { version: string; }; async function run(): Promise { // Note: require('package.json').version does not work because ncc will resolve that to a plain string at compile time - const packageJson: PackageJSON = JSON.parse(fs.readFileSync('package.json', 'utf8')); + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')) as PackageJson; const newVersionTag = packageJson.version; try { diff --git a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts index ecf242f00cc2..08519c40413b 100644 --- a/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts +++ b/.github/actions/javascript/getDeployPullRequestList/getDeployPullRequestList.ts @@ -21,7 +21,27 @@ async function run() { status: 'completed', event: isProductionDeploy ? 'release' : 'push', }) - ).data.workflow_runs; + ).data.workflow_runs + // Note: we filter out cancelled runs instead of looking only for success runs + // because if a build fails on even one platform, then it will have the status 'failure' + .filter((workflowRun) => workflowRun.conclusion !== 'cancelled'); + + // Find the most recent deploy workflow for which at least one of the build jobs finished successfully. + let lastSuccessfulDeploy = completedDeploys.shift(); + while ( + lastSuccessfulDeploy && + !( + await GithubUtils.octokit.actions.listJobsForWorkflowRun({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + run_id: lastSuccessfulDeploy.id, + filter: 'latest', + }) + ).data.jobs.some((job) => job.name.startsWith('Build and deploy') && job.conclusion === 'success') + ) { + lastSuccessfulDeploy = completedDeploys.shift(); + } const priorTag = completedDeploys[0].head_branch; console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`); diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js index 6b956f17be25..cfe512076ecd 100644 --- a/.github/actions/javascript/getDeployPullRequestList/index.js +++ b/.github/actions/javascript/getDeployPullRequestList/index.js @@ -11515,7 +11515,22 @@ async function run() { workflow_id: 'platformDeploy.yml', status: 'completed', event: isProductionDeploy ? 'release' : 'push', - })).data.workflow_runs; + })).data.workflow_runs + // Note: we filter out cancelled runs instead of looking only for success runs + // because if a build fails on even one platform, then it will have the status 'failure' + .filter((workflowRun) => workflowRun.conclusion !== 'cancelled'); + // Find the most recent deploy workflow for which at least one of the build jobs finished successfully. + let lastSuccessfulDeploy = completedDeploys.shift(); + while (lastSuccessfulDeploy && + !(await GithubUtils_1.default.octokit.actions.listJobsForWorkflowRun({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + // eslint-disable-next-line @typescript-eslint/naming-convention + run_id: lastSuccessfulDeploy.id, + filter: 'latest', + })).data.jobs.some((job) => job.name.startsWith('Build and deploy') && job.conclusion === 'success')) { + lastSuccessfulDeploy = completedDeploys.shift(); + } const priorTag = completedDeploys[0].head_branch; console.log(`Looking for PRs deployed to ${deployEnv} between ${priorTag} and ${inputTag}`); const prList = await GitUtils_1.default.getPullRequestsMergedBetween(priorTag ?? '', inputTag); diff --git a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts index 5231caa79ed5..93d5d8a9618b 100644 --- a/.github/actions/javascript/getGraphiteString/getGraphiteString.ts +++ b/.github/actions/javascript/getGraphiteString/getGraphiteString.ts @@ -33,7 +33,7 @@ const run = () => { } try { - const current: RegressionEntry = JSON.parse(entry); + const current = JSON.parse(entry) as RegressionEntry; // Extract timestamp, Graphite accepts timestamp in seconds if (current.metadata?.creationDate) { diff --git a/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts b/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts index a178d4073cbb..7799ffe7c9ec 100644 --- a/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts +++ b/.github/actions/javascript/getPreviousVersion/getPreviousVersion.ts @@ -11,7 +11,7 @@ function run() { core.setFailed(`'Error: Invalid input for 'SEMVER_LEVEL': ${semverLevel}`); } - const {version: currentVersion}: PackageJson = JSON.parse(readFileSync('./package.json', 'utf8')); + const {version: currentVersion} = JSON.parse(readFileSync('./package.json', 'utf8')) as PackageJson; if (!currentVersion) { core.setFailed('Error: Could not read package.json'); } diff --git a/.github/actions/javascript/validateReassureOutput/validateReassureOutput.ts b/.github/actions/javascript/validateReassureOutput/validateReassureOutput.ts index ad0f393a96a2..d843caf61518 100644 --- a/.github/actions/javascript/validateReassureOutput/validateReassureOutput.ts +++ b/.github/actions/javascript/validateReassureOutput/validateReassureOutput.ts @@ -3,7 +3,7 @@ import type {CompareResult, PerformanceEntry} from '@callstack/reassure-compare/ import fs from 'fs'; const run = (): boolean => { - const regressionOutput: CompareResult = JSON.parse(fs.readFileSync('.reassure/output.json', 'utf8')); + const regressionOutput = JSON.parse(fs.readFileSync('.reassure/output.json', 'utf8')) as CompareResult; const countDeviation = Number(core.getInput('COUNT_DEVIATION', {required: true})); const durationDeviation = Number(core.getInput('DURATION_DEVIATION_PERCENTAGE', {required: true})); diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 624c00de6831..5030ea1c2f2b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,6 +28,24 @@ jobs: - name: 🚀 Push tags to trigger staging deploy 🚀 run: git push --tags + + - name: Warn deployers if staging deploy failed + if: ${{ failure() }} + uses: 8398a7/action-slack@v3 + with: + status: custom + custom_payload: | + { + channel: '#deployer', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 NewDot staging deploy failed. 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} deployProduction: runs-on: ubuntu-latest @@ -65,6 +83,24 @@ jobs: PR_LIST: ${{ steps.getReleasePRList.outputs.PR_LIST }} - name: 🚀 Create release to trigger production deploy 🚀 - run: gh release create ${{ env.PRODUCTION_VERSION }} --generate-notes + run: gh release create ${{ env.PRODUCTION_VERSION }} --notes '${{ steps.getReleaseBody.outputs.RELEASE_BODY }}' env: GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} + + - name: Warn deployers if production deploy failed + if: ${{ failure() }} + uses: 8398a7/action-slack@v3 + with: + status: custom + custom_payload: | + { + channel: '#deployer', + attachments: [{ + color: "#DB4545", + pretext: ``, + text: `💥 NewDot production deploy failed. 💥`, + }] + } + env: + GITHUB_TOKEN: ${{ github.token }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml index 7e7d55ac5d2e..ffce73644263 100644 --- a/.github/workflows/e2ePerformanceTests.yml +++ b/.github/workflows/e2ePerformanceTests.yml @@ -184,6 +184,9 @@ jobs: - name: Copy e2e code into zip folder run: cp tests/e2e/dist/index.js zip/testRunner.ts + + - name: Copy profiler binaries into zip folder + run: cp -r node_modules/@perf-profiler/android/cpp-profiler/bin zip/bin - name: Zip everything in the zip directory up run: zip -qr App.zip ./zip diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index a695c0acf942..a2aadc331f19 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -48,12 +48,12 @@ jobs: git fetch origin "$BASELINE_BRANCH" --no-tags --depth=1 git switch "$BASELINE_BRANCH" npm install --force - npx reassure --baseline + NODE_OPTIONS=--experimental-vm-modules npx reassure --baseline git switch --force --detach - git merge --no-commit --allow-unrelated-histories "$BASELINE_BRANCH" -X ours git checkout --ours . npm install --force - npx reassure --branch + NODE_OPTIONS=--experimental-vm-modules npx reassure --branch - name: Validate output.json id: validateReassureOutput diff --git a/.prettierrc.js b/.prettierrc.js index 3118dc378694..d981112fffae 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -6,6 +6,7 @@ module.exports = { arrowParens: 'always', printWidth: 190, singleAttributePerLine: true, + plugins: [require.resolve('@trivago/prettier-plugin-sort-imports')], /** `importOrder` should be defined in an alphabetical order. */ importOrder: [ '@assets/(.*)$', diff --git a/Gemfile.lock b/Gemfile.lock index 3780235053ad..64f4d81c9e76 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,8 +10,8 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) @@ -20,17 +20,17 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.944.0) - aws-sdk-core (3.197.0) + aws-partitions (1.948.0) + aws-sdk-core (3.199.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.85.0) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-kms (1.87.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.152.3) - aws-sdk-core (~> 3, >= 3.197.0) + aws-sdk-s3 (1.154.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -119,7 +119,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.221.0) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) diff --git a/__mocks__/@react-navigation/native/index.ts b/__mocks__/@react-navigation/native/index.ts index 0b7dda4621ad..5bcafdc1856c 100644 --- a/__mocks__/@react-navigation/native/index.ts +++ b/__mocks__/@react-navigation/native/index.ts @@ -1,9 +1,65 @@ -import {useIsFocused as realUseIsFocused, useTheme as realUseTheme} from '@react-navigation/native'; +/* eslint-disable import/prefer-default-export, import/no-import-module-exports */ +import type * as ReactNavigation from '@react-navigation/native'; +import createAddListenerMock from '../../../tests/utils/createAddListenerMock'; -// We only want these mocked for storybook, not jest -const useIsFocused: typeof realUseIsFocused = process.env.NODE_ENV === 'test' ? realUseIsFocused : () => true; +const isJestEnv = process.env.NODE_ENV === 'test'; -const useTheme = process.env.NODE_ENV === 'test' ? realUseTheme : () => ({}); +const realReactNavigation = isJestEnv ? jest.requireActual('@react-navigation/native') : (require('@react-navigation/native') as typeof ReactNavigation); + +const useIsFocused = isJestEnv ? realReactNavigation.useIsFocused : () => true; +const useTheme = isJestEnv ? realReactNavigation.useTheme : () => ({}); + +const {triggerTransitionEnd, addListener} = isJestEnv + ? createAddListenerMock() + : { + triggerTransitionEnd: () => {}, + addListener: () => {}, + }; + +const useNavigation = () => ({ + ...realReactNavigation.useNavigation, + navigate: jest.fn(), + getState: () => ({ + routes: [], + }), + addListener, +}); + +type NativeNavigationMock = typeof ReactNavigation & { + triggerTransitionEnd: () => void; +}; export * from '@react-navigation/core'; -export {useIsFocused, useTheme}; +const Link = realReactNavigation.Link; +const LinkingContext = realReactNavigation.LinkingContext; +const NavigationContainer = realReactNavigation.NavigationContainer; +const ServerContainer = realReactNavigation.ServerContainer; +const DarkTheme = realReactNavigation.DarkTheme; +const DefaultTheme = realReactNavigation.DefaultTheme; +const ThemeProvider = realReactNavigation.ThemeProvider; +const useLinkBuilder = realReactNavigation.useLinkBuilder; +const useLinkProps = realReactNavigation.useLinkProps; +const useLinkTo = realReactNavigation.useLinkTo; +const useScrollToTop = realReactNavigation.useScrollToTop; +export { + // Overriden modules + useIsFocused, + useTheme, + useNavigation, + triggerTransitionEnd, + + // Theme modules are left alone + Link, + LinkingContext, + NavigationContainer, + ServerContainer, + DarkTheme, + DefaultTheme, + ThemeProvider, + useLinkBuilder, + useLinkProps, + useLinkTo, + useScrollToTop, +}; + +export type {NativeNavigationMock}; diff --git a/__mocks__/@ua/react-native-airship.ts b/__mocks__/@ua/react-native-airship.ts index ae7661ab672f..14909b58b31c 100644 --- a/__mocks__/@ua/react-native-airship.ts +++ b/__mocks__/@ua/react-native-airship.ts @@ -15,31 +15,31 @@ const iOS: Partial = { }, }; -const pushIOS: AirshipPushIOS = jest.fn().mockImplementation(() => ({ +const pushIOS = jest.fn().mockImplementation(() => ({ setBadgeNumber: jest.fn(), setForegroundPresentationOptions: jest.fn(), setForegroundPresentationOptionsCallback: jest.fn(), -}))(); +}))() as AirshipPushIOS; -const pushAndroid: AirshipPushAndroid = jest.fn().mockImplementation(() => ({ +const pushAndroid = jest.fn().mockImplementation(() => ({ setForegroundDisplayPredicate: jest.fn(), -}))(); +}))() as AirshipPushAndroid; -const push: AirshipPush = jest.fn().mockImplementation(() => ({ +const push = jest.fn().mockImplementation(() => ({ iOS: pushIOS, android: pushAndroid, enableUserNotifications: () => Promise.resolve(false), clearNotifications: jest.fn(), getNotificationStatus: () => Promise.resolve({airshipOptIn: false, systemEnabled: false, airshipEnabled: false}), getActiveNotifications: () => Promise.resolve([]), -}))(); +}))() as AirshipPush; -const contact: AirshipContact = jest.fn().mockImplementation(() => ({ +const contact = jest.fn().mockImplementation(() => ({ identify: jest.fn(), getNamedUserId: () => Promise.resolve(undefined), reset: jest.fn(), module: jest.fn(), -}))(); +}))() as AirshipContact; const Airship: Partial = { addListener: jest.fn(), diff --git a/__mocks__/fs.ts b/__mocks__/fs.ts index cca0aa9520ec..3f8579557c82 100644 --- a/__mocks__/fs.ts +++ b/__mocks__/fs.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ const {fs} = require('memfs'); module.exports = fs; diff --git a/__mocks__/react-native.ts b/__mocks__/react-native.ts index 27b78b308446..3deeabf6df2a 100644 --- a/__mocks__/react-native.ts +++ b/__mocks__/react-native.ts @@ -41,7 +41,7 @@ jest.doMock('react-native', () => { }; }; - const reactNativeMock: ReactNativeMock = Object.setPrototypeOf( + const reactNativeMock = Object.setPrototypeOf( { NativeModules: { ...ReactNative.NativeModules, @@ -86,7 +86,7 @@ jest.doMock('react-native', () => { }, Dimensions: { ...ReactNative.Dimensions, - addEventListener: jest.fn(), + addEventListener: jest.fn(() => ({remove: jest.fn()})), get: () => dimensions, set: (newDimensions: Record) => { dimensions = newDimensions; @@ -98,11 +98,14 @@ jest.doMock('react-native', () => { // so it seems easier to just run the callback immediately in tests. InteractionManager: { ...ReactNative.InteractionManager, - runAfterInteractions: (callback: () => void) => callback(), + runAfterInteractions: (callback: () => void) => { + callback(); + return {cancel: () => {}}; + }, }, }, ReactNative, - ); + ) as ReactNativeMock; return reactNativeMock; }); diff --git a/android/app/build.gradle b/android/app/build.gradle index ea7e6bdfce45..c53ad2cd3cc7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009000203 - versionName "9.0.2-3" + versionCode 1009000512 + versionName "9.0.5-12" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/assets/images/circular-arrow-backwards.svg b/assets/images/circular-arrow-backwards.svg new file mode 100644 index 000000000000..209c0aea5fa7 --- /dev/null +++ b/assets/images/circular-arrow-backwards.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/assets/images/computer.svg b/assets/images/computer.svg new file mode 100644 index 000000000000..be9eca391e0b --- /dev/null +++ b/assets/images/computer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/expensifyCard/cardIllustration.svg b/assets/images/expensifyCard/cardIllustration.svg new file mode 100644 index 000000000000..c81bb21568a7 --- /dev/null +++ b/assets/images/expensifyCard/cardIllustration.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/integrationicons/netsuite-icon-square.svg b/assets/images/integrationicons/netsuite-icon-square.svg index d4f19f4f44c0..1b4557c5a044 100644 --- a/assets/images/integrationicons/netsuite-icon-square.svg +++ b/assets/images/integrationicons/netsuite-icon-square.svg @@ -1,57 +1 @@ - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/assets/images/integrationicons/sage-intacct-icon-square.svg b/assets/images/integrationicons/sage-intacct-icon-square.svg new file mode 100644 index 000000000000..fe10342d711e --- /dev/null +++ b/assets/images/integrationicons/sage-intacct-icon-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/product-illustrations/folder-with-papers.svg b/assets/images/product-illustrations/folder-with-papers.svg new file mode 100644 index 000000000000..3d00fb147ccd --- /dev/null +++ b/assets/images/product-illustrations/folder-with-papers.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__virtualcard.svg b/assets/images/simple-illustrations/simple-illustration__virtualcard.svg new file mode 100644 index 000000000000..2c1f538102a2 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__virtualcard.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/config/electronBuilder.config.js b/config/electronBuilder.config.js index 5a995fb5de91..ad3a23407b89 100644 --- a/config/electronBuilder.config.js +++ b/config/electronBuilder.config.js @@ -47,7 +47,7 @@ module.exports = { }, target: [ { - target: 'dmg', + target: 'default', arch: ['universal'], }, ], diff --git a/config/webpack/webpack.common.ts b/config/webpack/webpack.common.ts index bedd7e50ef94..33fd9131eca0 100644 --- a/config/webpack/webpack.common.ts +++ b/config/webpack/webpack.common.ts @@ -4,13 +4,13 @@ import dotenv from 'dotenv'; import fs from 'fs'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import path from 'path'; -import type {Compiler, Configuration} from 'webpack'; +import type {Class} from 'type-fest'; +import type {Configuration, WebpackPluginInstance} from 'webpack'; import {DefinePlugin, EnvironmentPlugin, IgnorePlugin, ProvidePlugin} from 'webpack'; import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; import CustomVersionFilePlugin from './CustomVersionFilePlugin'; import type Environment from './types'; -// importing anything from @vue/preload-webpack-plugin causes an error type Options = { rel: string; as: string; @@ -18,13 +18,10 @@ type Options = { include: string; }; -type PreloadWebpackPluginClass = { - new (options?: Options): PreloadWebpackPluginClass; - apply: (compiler: Compiler) => void; -}; +type PreloadWebpackPluginClass = Class; -// require is necessary, there are no types for this package and the declaration file can't be seen by the build process which causes an error. -const PreloadWebpackPlugin: PreloadWebpackPluginClass = require('@vue/preload-webpack-plugin'); +// require is necessary, importing anything from @vue/preload-webpack-plugin causes an error +const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin') as PreloadWebpackPluginClass; const includeModules = [ 'react-native-animatable', diff --git a/desktop/main.ts b/desktop/main.ts index 6ab0bc6579d7..d8c46bbbc89b 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -141,7 +141,7 @@ const manuallyCheckForUpdates = (menuItem?: MenuItem, browserWindow?: BrowserWin autoUpdater .checkForUpdates() - .catch((error) => { + .catch((error: unknown) => { isSilentUpdating = false; return {error}; }) @@ -617,7 +617,7 @@ const mainWindow = (): Promise => { }); const downloadQueue = createDownloadQueue(); - ipcMain.on(ELECTRON_EVENTS.DOWNLOAD, (event, downloadData) => { + ipcMain.on(ELECTRON_EVENTS.DOWNLOAD, (event, downloadData: DownloadItem) => { const downloadItem: DownloadItem = { ...downloadData, win: browserWindow, diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index 5fd65532c021..7f416951b58c 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -114,8 +114,8 @@ platforms: icon: /assets/images/shield.svg description: Configure rules, settings, and limits for your company’s spending. - - href: expenses - title: Expenses + - href: expenses-&-payments + title: Expenses & Payments icon: /assets/images/money-into-wallet.svg description: Learn more about expense tracking and submission. diff --git a/docs/_includes/lhn-template.html b/docs/_includes/lhn-template.html index 80302f33f52e..32078c1a8de6 100644 --- a/docs/_includes/lhn-template.html +++ b/docs/_includes/lhn-template.html @@ -21,25 +21,25 @@ {% for platform in site.data.routes.platforms %} {% if platform.href == activePlatform %}
  • - - + {% for hub in platform.hubs %}