From c8084ffbc9476592e3f7203632e42ba36bed92f6 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Sun, 20 Oct 2024 00:44:43 +0200 Subject: [PATCH 01/73] Hide approve button if report has violations --- src/libs/actions/IOU.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index fb8cd014ec7b..047d081fbacb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6988,8 +6988,9 @@ function canApproveIOU(iouReport: OnyxTypes.OnyxInputOrEntry, const reportNameValuePairs = ReportUtils.getReportNameValuePairs(iouReport?.reportID); const isArchivedReport = ReportUtils.isArchivedRoom(iouReport, reportNameValuePairs); const unheldTotalIsZero = iouReport && iouReport.unheldTotal === 0; + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); - return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled && !isArchivedReport && !unheldTotalIsZero; + return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled && !isArchivedReport && !unheldTotalIsZero && !hasViolations; } function canIOUBePaid( From f63804fda2685824aee85d1a1f9eba15d0bed65e Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 25 Oct 2024 19:24:52 +0800 Subject: [PATCH 02/73] fix hidden shows briefly when mentioning unknown user --- .../HTMLRenderers/MentionUserRenderer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx index 36586b09e514..96bdf8e9e1e8 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/MentionUserRenderer.tsx @@ -4,9 +4,9 @@ import isEmpty from 'lodash/isEmpty'; import React from 'react'; import {StyleSheet} from 'react-native'; import type {TextStyle} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; import {TNodeChildrenRenderer} from 'react-native-render-html'; -import {usePersonalDetails} from '@components/OnyxProvider'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; @@ -20,6 +20,7 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import asMutable from '@src/types/utils/asMutable'; @@ -31,7 +32,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const htmlAttribAccountID = tnode.attributes.accountid; - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const htmlAttributeAccountID = tnode.attributes.accountid; let accountID: number; @@ -56,7 +57,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona return displayText.split('@').at(0); }; - if (!isEmpty(htmlAttribAccountID)) { + if (!isEmpty(htmlAttribAccountID) && personalDetails?.[htmlAttribAccountID]) { const user = personalDetails[htmlAttribAccountID]; accountID = parseInt(htmlAttribAccountID, 10); mentionDisplayText = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') || PersonalDetailsUtils.getDisplayNameOrDefault(user); From 3204148992ce2e3927608e2c6359439a17bd313f Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:39:52 +0100 Subject: [PATCH 03/73] Disable the pay button if the report has violaitons --- src/libs/actions/IOU.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 047d081fbacb..f0461d03dd26 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7042,8 +7042,9 @@ function canIOUBePaid( const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); const shouldBeApproved = canApproveIOU(iouReport, policy); - + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); + return ( isPayer && !isOpenExpenseReport && @@ -7053,8 +7054,10 @@ function canIOUBePaid( !isChatReportArchived && !isAutoReimbursable && !shouldBeApproved && + !hasViolations && !isPayAtEndExpenseReport ); + } function getIOUReportActionToApproveOrPay(chatReport: OnyxEntry, excludedIOUReportID: string): OnyxEntry { From f760f272cdfd83f421fe929553a84c699d23a40a Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:40:51 +0100 Subject: [PATCH 04/73] minor edit --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f0461d03dd26..575e14e36fd3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7057,7 +7057,6 @@ function canIOUBePaid( !hasViolations && !isPayAtEndExpenseReport ); - } function getIOUReportActionToApproveOrPay(chatReport: OnyxEntry, excludedIOUReportID: string): OnyxEntry { From 38a56502c4168ba86d0ae449b8aa1fe03b7d69fa Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:45:12 +0100 Subject: [PATCH 05/73] minor edit --- src/libs/actions/IOU.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 575e14e36fd3..3a0ca373d3de 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7043,6 +7043,7 @@ function canIOUBePaid( const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); const shouldBeApproved = canApproveIOU(iouReport, policy); const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); return ( From 7af0e6a9cebac8ae5c2bc1c53ffd5b4833dc9193 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:46:17 +0100 Subject: [PATCH 06/73] minor edit --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3a0ca373d3de..575e14e36fd3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7043,7 +7043,6 @@ function canIOUBePaid( const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); const shouldBeApproved = canApproveIOU(iouReport, policy); const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); - const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); return ( From 6c716bb01bf104c444a4597c4cb140d9b99097bd Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:49:51 +0100 Subject: [PATCH 07/73] minor edit --- src/libs/actions/IOU.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 575e14e36fd3..f911f17d0103 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7044,7 +7044,7 @@ function canIOUBePaid( const shouldBeApproved = canApproveIOU(iouReport, policy); const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); - + return ( isPayer && !isOpenExpenseReport && From c9bcc679361382b9ba6c30fdd26a14828aa09ad7 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 12 Nov 2024 14:28:07 +0800 Subject: [PATCH 08/73] fix go back/forward gesture doesn't work --- web/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/web/index.html b/web/index.html index c15f79b428a7..aad82434c342 100644 --- a/web/index.html +++ b/web/index.html @@ -44,7 +44,6 @@ } body { overflow: hidden; - overscroll-behavior: none; touch-action: none; } [data-drag-area='true'] { From 6c69ab83991f4f4b610bf14670402ca358e61e59 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 13 Nov 2024 01:08:47 +0100 Subject: [PATCH 09/73] Update the reviewer and author checklist to include unit tests and update the proposal template --- .github/PULL_REQUEST_TEMPLATE.md | 3 ++- contributingGuides/PROPOSAL_TEMPLATE.md | 3 +++ contributingGuides/REVIEWER_CHECKLIST.md | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 459a780ca8b4..917ca7fff142 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -94,7 +94,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] I followed the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md) - [ ] I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like `Avatar`, I verified the components using `Avatar` are working as expected) - [ ] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests) -- [ ] I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such +- [ ] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such - [ ] I verified that if a function's arguments changed that all usages have also been updated correctly - [ ] If any new file was added I verified that: - [ ] The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory @@ -109,6 +109,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] I verified that all the inputs inside a form are aligned with each other. - [ ] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. +- [ ] I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow. - [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. ### Screenshots/Videos diff --git a/contributingGuides/PROPOSAL_TEMPLATE.md b/contributingGuides/PROPOSAL_TEMPLATE.md index 8c9fa7968fe2..437355448269 100644 --- a/contributingGuides/PROPOSAL_TEMPLATE.md +++ b/contributingGuides/PROPOSAL_TEMPLATE.md @@ -7,6 +7,9 @@ ### What changes do you think we should make in order to solve the problem? +### What specific scenarios should we cover in unit tests to prevent reintroducing this issue in the future? + + ### What alternative solutions did you explore? (Optional) **Reminder:** Please use plain English, be brief and avoid jargon. Feel free to use images, charts or pseudo-code if necessary. Do not post large multi-line diffs or write walls of text. Do not create PRs unless you have been hired for this job. diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index 5fc14328f3b4..baee49e27156 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -30,7 +30,7 @@ - [ ] I verified that this PR follows the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md) - [ ] I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like `Avatar`, I verified the components using `Avatar` have been tested & I retested again) - [ ] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests) -- [ ] I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such +- [ ] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such - [ ] If a new component is created I verified that: - [ ] A similar component doesn't exist in the codebase - [ ] All props are defined accurately and each prop has a `/** comment above it */` @@ -54,6 +54,7 @@ - [ ] I verified that all the inputs inside a form are aligned with each other. - [ ] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. +- [ ] For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow. - [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR. From f1a930d0687272601b2cb0e7c74cde6923c096a4 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Wed, 13 Nov 2024 01:13:26 +0100 Subject: [PATCH 10/73] Update the template --- contributingGuides/PROPOSAL_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributingGuides/PROPOSAL_TEMPLATE.md b/contributingGuides/PROPOSAL_TEMPLATE.md index 437355448269..aee4aa5d22e5 100644 --- a/contributingGuides/PROPOSAL_TEMPLATE.md +++ b/contributingGuides/PROPOSAL_TEMPLATE.md @@ -8,7 +8,7 @@ ### What specific scenarios should we cover in unit tests to prevent reintroducing this issue in the future? - + ### What alternative solutions did you explore? (Optional) From 1051bda9bee8be4c994b5fe1d1a389454528bded Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Nov 2024 13:34:38 +0800 Subject: [PATCH 11/73] fix tooltip for QAB doesn't show for new user --- .../BaseEducationalTooltip.tsx | 47 +++++-------------- .../EducationalTooltip/TooltipManager.ts | 29 ++++++++++++ .../Navigation/AppNavigator/AuthScreens.tsx | 2 + src/libs/actions/Modal.ts | 4 ++ 4 files changed, 46 insertions(+), 36 deletions(-) create mode 100644 src/components/Tooltip/EducationalTooltip/TooltipManager.ts diff --git a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx index a9481f3cf3b3..3538adfd5f49 100644 --- a/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx +++ b/src/components/Tooltip/EducationalTooltip/BaseEducationalTooltip.tsx @@ -2,10 +2,8 @@ import React, {memo, useCallback, useEffect, useRef, useState} from 'react'; import type {LayoutRectangle, NativeSyntheticEvent} from 'react-native'; import GenericTooltip from '@components/Tooltip/GenericTooltip'; import type {EducationalTooltipProps} from '@components/Tooltip/types'; -import onyxSubscribe from '@libs/onyxSubscribe'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type {Modal} from '@src/types/onyx'; import measureTooltipCoordinate from './measureTooltipCoordinate'; +import * as TooltipManager from './TooltipManager'; type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>; @@ -18,30 +16,8 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false, const [shouldMeasure, setShouldMeasure] = useState(false); const show = useRef<() => void>(); - const [modal, setModal] = useState({ - willAlertModalBecomeVisible: false, - isVisible: false, - }); - - const shouldShow = !modal?.willAlertModalBecomeVisible && !modal?.isVisible && shouldRender; - - useEffect(() => { - if (!shouldRender) { - return; - } - const unsubscribeOnyxModal = onyxSubscribe({ - key: ONYXKEYS.MODAL, - callback: (modalArg) => { - if (modalArg === undefined) { - return; - } - setModal(modalArg); - }, - }); - return () => { - unsubscribeOnyxModal(); - }; - }, [shouldRender]); + const removeActiveTooltipRef = useRef(() => {}); + const removePendingToltipRef = useRef(() => {}); const didShow = useRef(false); @@ -51,6 +27,7 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false, } hideTooltipRef.current?.(); onHideTooltip?.(); + removeActiveTooltipRef.current(); }, [onHideTooltip]); useEffect( @@ -70,12 +47,6 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false, return; } - // If the modal is open, hide the tooltip immediately and clear the timeout - if (!shouldShow) { - closeTooltip(); - return; - } - // Automatically hide tooltip after 5 seconds if shouldAutoDismiss is true const timerID = setTimeout(() => { closeTooltip(); @@ -83,21 +54,25 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false, return () => { clearTimeout(timerID); }; - }, [shouldAutoDismiss, shouldShow, closeTooltip]); + }, [shouldAutoDismiss, closeTooltip]); useEffect(() => { - if (!shouldMeasure || !shouldShow || didShow.current) { + if (!shouldMeasure || !shouldRender || didShow.current) { return; } // When tooltip is used inside an animated view (e.g. popover), we need to wait for the animation to finish before measuring content. const timerID = setTimeout(() => { + removePendingToltipRef.current(); show.current?.(); didShow.current = true; + removeActiveTooltipRef.current = TooltipManager.addActiveTooltip(closeTooltip); }, 500); + removePendingToltipRef.current = TooltipManager.addPendingTooltip(timerID); return () => { + removePendingToltipRef.current(); clearTimeout(timerID); }; - }, [shouldMeasure, shouldShow]); + }, [shouldMeasure, shouldRender, closeTooltip]); useEffect( () => closeTooltip, diff --git a/src/components/Tooltip/EducationalTooltip/TooltipManager.ts b/src/components/Tooltip/EducationalTooltip/TooltipManager.ts new file mode 100644 index 000000000000..fd6953b558d0 --- /dev/null +++ b/src/components/Tooltip/EducationalTooltip/TooltipManager.ts @@ -0,0 +1,29 @@ +const pendingTooltip: NodeJS.Timeout[] = []; +const tooltipCloseCallback: Function[] = []; + +function addPendingTooltip(timeout: NodeJS.Timeout) { + pendingTooltip.push(timeout); + return () => { + const index = pendingTooltip.indexOf(timeout); + pendingTooltip.splice(index, 1); + }; +} + +function addActiveTooltip(closeCallback: () => void) { + tooltipCloseCallback.push(closeCallback); + return () => { + const index = tooltipCloseCallback.indexOf(closeCallback); + tooltipCloseCallback.splice(index, 1); + }; +} + +function cancelPendingAndActiveTooltips() { + while (pendingTooltip.length > 0) { + clearTimeout(pendingTooltip.pop()); + } + while (tooltipCloseCallback.length > 0) { + tooltipCloseCallback.pop()?.(); + } +} + +export {addPendingTooltip, addActiveTooltip, cancelPendingAndActiveTooltips}; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index a061d5b52d22..d1072833b6e1 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -10,6 +10,7 @@ import {SearchContextProvider} from '@components/Search/SearchContext'; import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; import TestToolsModal from '@components/TestToolsModal'; +import * as TooltipManager from '@components/Tooltip/EducationalTooltip/TooltipManager'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import usePermissions from '@hooks/usePermissions'; @@ -202,6 +203,7 @@ const RootStack = createCustomStackNavigator(); const modalScreenListeners = { focus: () => { + TooltipManager.cancelPendingAndActiveTooltips(); Modal.setModalVisibility(true); }, blur: () => { diff --git a/src/libs/actions/Modal.ts b/src/libs/actions/Modal.ts index 00853e9546d5..ec2fbbb7ea1b 100644 --- a/src/libs/actions/Modal.ts +++ b/src/libs/actions/Modal.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import * as TooltipManager from '@components/Tooltip/EducationalTooltip/TooltipManager'; import ONYXKEYS from '@src/ONYXKEYS'; const closeModals: Array<(isNavigating?: boolean) => void> = []; @@ -86,6 +87,9 @@ function setDisableDismissOnEscape(disableDismissOnEscape: boolean) { * isPopover indicates that the next open modal is popover or bottom docked */ function willAlertModalBecomeVisible(isVisible: boolean, isPopover = false) { + if (isVisible) { + TooltipManager.cancelPendingAndActiveTooltips(); + } Onyx.merge(ONYXKEYS.MODAL, {willAlertModalBecomeVisible: isVisible, isPopover}); } From da872735b65945515f98b3bda150201eaf82c662 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Nov 2024 13:58:19 +0800 Subject: [PATCH 12/73] lint --- src/components/Tooltip/EducationalTooltip/TooltipManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Tooltip/EducationalTooltip/TooltipManager.ts b/src/components/Tooltip/EducationalTooltip/TooltipManager.ts index fd6953b558d0..339f9f4faa30 100644 --- a/src/components/Tooltip/EducationalTooltip/TooltipManager.ts +++ b/src/components/Tooltip/EducationalTooltip/TooltipManager.ts @@ -1,5 +1,5 @@ const pendingTooltip: NodeJS.Timeout[] = []; -const tooltipCloseCallback: Function[] = []; +const tooltipCloseCallback: (() => void)[] = []; function addPendingTooltip(timeout: NodeJS.Timeout) { pendingTooltip.push(timeout); From 3b055180b18532300b046ae854033178a034af72 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 13 Nov 2024 14:09:01 +0800 Subject: [PATCH 13/73] lint --- src/components/Tooltip/EducationalTooltip/TooltipManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Tooltip/EducationalTooltip/TooltipManager.ts b/src/components/Tooltip/EducationalTooltip/TooltipManager.ts index 339f9f4faa30..f128916a0430 100644 --- a/src/components/Tooltip/EducationalTooltip/TooltipManager.ts +++ b/src/components/Tooltip/EducationalTooltip/TooltipManager.ts @@ -1,5 +1,5 @@ const pendingTooltip: NodeJS.Timeout[] = []; -const tooltipCloseCallback: (() => void)[] = []; +const tooltipCloseCallback: Array<() => void> = []; function addPendingTooltip(timeout: NodeJS.Timeout) { pendingTooltip.push(timeout); From 939dd095ac383488fe1ae10af2befcd3acd83c73 Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 13 Nov 2024 21:16:18 +0500 Subject: [PATCH 14/73] fix navigation to room description --- src/components/ReportWelcomeText.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 3c38c9f4c4a3..80046b3ee8dc 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -45,6 +45,7 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); const welcomeMessage = SidebarUtils.getWelcomeMessage(report, policy); const moneyRequestOptions = ReportUtils.temporary_getMoneyRequestOptions(report, policy, participantAccountIDs); + const canEdit = ReportUtils.canEditReportDescription(report, policy); const filteredOptions = moneyRequestOptions.filter( (item): item is Exclude => item !== CONST.IOU.TYPE.INVOICE, @@ -123,12 +124,13 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { (welcomeMessage?.messageHtml ? ( { - if (!canEditPolicyDescription) { + if (!canEdit) { return; } - Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(policy?.id ?? '-1')); + const activeRoute = Navigation.getReportRHPActiveRoute(); + Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1', activeRoute)); }} - style={[styles.renderHTML, canEditPolicyDescription ? styles.cursorPointer : styles.cursorText]} + style={[styles.renderHTML, canEdit ? styles.cursorPointer : styles.cursorText]} accessibilityLabel={translate('reportDescriptionPage.roomDescription')} > From 24ca21589f3e305537c23f4d17a1f894f1437d7b Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 13 Nov 2024 22:39:28 +0500 Subject: [PATCH 15/73] change variable name --- src/components/ReportWelcomeText.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 80046b3ee8dc..128b7b9e81a1 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -45,7 +45,7 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); const welcomeMessage = SidebarUtils.getWelcomeMessage(report, policy); const moneyRequestOptions = ReportUtils.temporary_getMoneyRequestOptions(report, policy, participantAccountIDs); - const canEdit = ReportUtils.canEditReportDescription(report, policy); + const canEditReportDescription = ReportUtils.canEditReportDescription(report, policy); const filteredOptions = moneyRequestOptions.filter( (item): item is Exclude => item !== CONST.IOU.TYPE.INVOICE, @@ -124,13 +124,13 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { (welcomeMessage?.messageHtml ? ( { - if (!canEdit) { + if (!canEditReportDescription) { return; } const activeRoute = Navigation.getReportRHPActiveRoute(); Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1', activeRoute)); }} - style={[styles.renderHTML, canEdit ? styles.cursorPointer : styles.cursorText]} + style={[styles.renderHTML, canEditReportDescription ? styles.cursorPointer : styles.cursorText]} accessibilityLabel={translate('reportDescriptionPage.roomDescription')} > @@ -156,7 +156,7 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { { const activeRoute = Navigation.getReportRHPActiveRoute(); - if (ReportUtils.canEditReportDescription(report, policy)) { + if (canEditReportDescription) { Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1', activeRoute)); return; } From e2fcccf8e6b60adb6ab2c58931bc5ec3c80b8c13 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 13 Nov 2024 14:12:57 -0700 Subject: [PATCH 16/73] Add more info on given/when/then format --- tests/README.md | 70 ++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/tests/README.md b/tests/README.md index 890697d80719..d119179114f3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,54 +59,40 @@ expect(onyxData).toBe(expectedOnyxData); ## Documenting Tests -Tests aren't always clear about what exactly is being tested. To make this a bit easier we recommend adopting the following format for code comments: +Comments are just as criticle in tests as the tests themselves. They provide context behind why the test was written and what the expected behavior is supposed to be which will benefit the future generations of engineers looking at them. Think about it. When was the last time you saw a unit test and wondered why it was written that way and then you didn't want to touch it because you didn't know if changing the behavior of the test was appropriate or not? It was probably pretty recent :D -``` -// Given -... code that sets initial condition +In order to give future engineers the best context for a unit test, follow this guide: + +1. DO add three sections to every test: + - "Given" - This introduces the initial condition of the test + - "When" - Describes what action is being done and the thing that is being tested + - "Then" - Describes what is expected to happen -// When -... code that does something +2. DO explain **WHY** the test is doing what it is doing in every comment. +3. DO NOT explain **WHAT** the code is doing in comments. This information should be self-evident. + +The format looks like this: -// Then -... code that performs the assertion ``` +// BAD +// Given an account +{* code that sets initial condition *} -## Example Test +// When it is closed +{* code that does something *} -```javascript -HttpUtils.xhr = jest.fn(); - -describe('actions/Report', () => { - it('adds an optimistic comment', () => { - // Given an Onyx subscription to a report's `reportActions` - const ACTION_ID = 1; - const REPORT_ID = 1; - let reportActions; - Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, - callback: val => reportActions = val, - }); - - // Mock Report_AddComment command so it can succeed - HttpUtils.xhr.mockImplementation(() => Promise.resolve({ - jsonCode: 200, - })); - - // When we add a new action to that report - Report.addComment(REPORT_ID, 'Hello!'); - return waitForBatchedUpdates() - .then(() => { - const action = reportActions[ACTION_ID]; - - // Then the action set in the Onyx callback should match - // the comment we left and it will be in a loading state because - // it's an "optimistic comment" - expect(action.message[0].text).toBe('Hello!'); - expect(action.isPending).toBe(true); - }); - }); -}); +// Then the user is logged out +{* code that performs the assertion *} + +// GOOD +// Given an account of a brand new user +{* code that sets initial condition *} + +// When the account is closed by clicking on the close account button +{* code that does something *} + +// Then the user should be logged out because their account is no longer active +{* code that performs the assertion *} ``` ## When to Write a Test From 258a87a3e74b33f5443d4877bc607c172c6699d3 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 13 Nov 2024 14:18:05 -0700 Subject: [PATCH 17/73] Improve some more of the docs --- tests/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index d119179114f3..8a97660f09b5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -97,10 +97,11 @@ The format looks like this: ## When to Write a Test -Many of the UI features of our application should go through rigorous testing by you, your PR reviewer, and finally QA before deployment. It's also difficult to maintain UI tests when the UI changes often. Therefore, it's not valuable for us to place every single part of the application UI under test at this time. The manual testing steps should catch most major UI bugs. Therefore, if we are writing any test there should be a **good reason**. +Many of the UI features of our application should go through rigorous testing by you, your PR reviewer, and finally QA before deployment. However, the code is mature enough now that protecting code against regressions is the top priority. **What's a "good reason" to write a test?** +- Any PR that fixes a bug - Anything that is difficult or impossible to run a manual tests on - e.g. a test to verify an outcome after an authentication token expires (which normally takes two hours) - Areas of the code that are changing often, breaking often, and would benefit from the resiliency an automated test would provide From ab621ba09a9f8155202029d18324d7694e0c10df Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 14 Nov 2024 11:58:40 +0700 Subject: [PATCH 18/73] feat: Require magic code validation for add missing detail --- ...sonalDetailsAndShipExpensifyCardsParams.ts | 1 + src/libs/actions/PersonalDetails.ts | 3 +- .../MissingPersonalDetailsContent.tsx | 42 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/libs/API/parameters/SetPersonalDetailsAndShipExpensifyCardsParams.ts b/src/libs/API/parameters/SetPersonalDetailsAndShipExpensifyCardsParams.ts index 0ab82ba6b755..7f57658f2016 100644 --- a/src/libs/API/parameters/SetPersonalDetailsAndShipExpensifyCardsParams.ts +++ b/src/libs/API/parameters/SetPersonalDetailsAndShipExpensifyCardsParams.ts @@ -9,6 +9,7 @@ type SetPersonalDetailsAndShipExpensifyCardsParams = { addressCountry: string; addressState: string; dob: string; + validateCode: string; }; export default SetPersonalDetailsAndShipExpensifyCardsParams; diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts index f759decda812..94a9dc95e846 100644 --- a/src/libs/actions/PersonalDetails.ts +++ b/src/libs/actions/PersonalDetails.ts @@ -465,7 +465,7 @@ function clearAvatarErrors() { }); } -function updatePersonalDetailsAndShipExpensifyCards(values: FormOnyxValues) { +function updatePersonalDetailsAndShipExpensifyCards(values: FormOnyxValues, validateCode: string) { const parameters: SetPersonalDetailsAndShipExpensifyCardsParams = { legalFirstName: values.legalFirstName?.trim() ?? '', legalLastName: values.legalLastName?.trim() ?? '', @@ -477,6 +477,7 @@ function updatePersonalDetailsAndShipExpensifyCards(values: FormOnyxValues = useRef(null); const values = useMemo(() => getSubstepValues(privatePersonalDetails, draftValues), [privatePersonalDetails, draftValues]); @@ -44,9 +52,7 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi if (!values) { return; } - PersonalDetails.updatePersonalDetailsAndShipExpensifyCards(values); - FormActions.clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); - Navigation.goBack(); + setIsValidateCodeActionModalVisible(true); }, [values]); const { @@ -75,6 +81,23 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi prevScreen(); }; + const handleValidateCodeEntered = useCallback( + (validateCode: string) => { + PersonalDetails.updatePersonalDetailsAndShipExpensifyCards(values, validateCode); + FormActions.clearDraftValues(ONYXKEYS.FORMS.PERSONAL_DETAILS_FORM); + Navigation.goBack(); + }, + [values], + ); + + const sendValidateCode = () => { + if (validateCodeAction?.validateCodeSent) { + return; + } + + requestValidateCodeAction(); + }; + const handleNextScreen = useCallback(() => { if (isEditing) { goToTheLastStep(); @@ -108,6 +131,17 @@ function MissingPersonalDetailsContent({privatePersonalDetails, draftValues}: Mi screenIndex={screenIndex} personalDetailsValues={values} /> + + {}} + onClose={() => setIsValidateCodeActionModalVisible(false)} + isVisible={isValidateCodeActionModalVisible} + title={translate('cardPage.validateCardTitle')} + descriptionPrimary={translate('cardPage.enterMagicCode', {contactMethod: primaryLogin})} + hasMagicCodeBeenSent={!!validateCodeAction?.validateCodeSent} + /> ); } From a6fb2039de9dbb9125b651e594daca64399d84ce Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 14 Nov 2024 19:19:37 +0100 Subject: [PATCH 19/73] Address PR feedback --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- contributingGuides/PROPOSAL_TEMPLATE.md | 2 +- contributingGuides/REVIEWER_CHECKLIST.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 917ca7fff142..228a605d2c86 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -109,7 +109,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] I verified that all the inputs inside a form are aligned with each other. - [ ] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. -- [ ] I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow. +- [ ] I added [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow. - [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. ### Screenshots/Videos diff --git a/contributingGuides/PROPOSAL_TEMPLATE.md b/contributingGuides/PROPOSAL_TEMPLATE.md index aee4aa5d22e5..ea908b5b0666 100644 --- a/contributingGuides/PROPOSAL_TEMPLATE.md +++ b/contributingGuides/PROPOSAL_TEMPLATE.md @@ -8,7 +8,7 @@ ### What specific scenarios should we cover in unit tests to prevent reintroducing this issue in the future? - + ### What alternative solutions did you explore? (Optional) diff --git a/contributingGuides/REVIEWER_CHECKLIST.md b/contributingGuides/REVIEWER_CHECKLIST.md index baee49e27156..545c79a95af1 100644 --- a/contributingGuides/REVIEWER_CHECKLIST.md +++ b/contributingGuides/REVIEWER_CHECKLIST.md @@ -54,7 +54,7 @@ - [ ] I verified that all the inputs inside a form are aligned with each other. - [ ] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes. - [ ] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page. -- [ ] For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow. +- [ ] For any bug fix or new feature in this PR, I verified that sufficient [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) are included to prevent regressions in this flow. - [ ] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps. - [ ] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR. From e5baec5f9f91821f464bf6c1ff5523482504194d Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 14 Nov 2024 19:27:17 +0100 Subject: [PATCH 20/73] Also mention new features --- tests/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/README.md b/tests/README.md index 8a97660f09b5..cd6cdefd0b40 100644 --- a/tests/README.md +++ b/tests/README.md @@ -102,6 +102,7 @@ Many of the UI features of our application should go through rigorous testing by **What's a "good reason" to write a test?** - Any PR that fixes a bug +- When introducing a new feature, cover as much logic as possible by unit tests - Anything that is difficult or impossible to run a manual tests on - e.g. a test to verify an outcome after an authentication token expires (which normally takes two hours) - Areas of the code that are changing often, breaking often, and would benefit from the resiliency an automated test would provide From 067263ae31492cd568b3cbc0c76a552238cdc18b Mon Sep 17 00:00:00 2001 From: thelullabyy Date: Fri, 15 Nov 2024 15:08:14 +0700 Subject: [PATCH 21/73] fix: apply 4 decimal values for distance rate display --- src/CONST.ts | 1 + src/libs/CurrencyUtils.ts | 3 ++- src/libs/PolicyUtils.ts | 14 +++++++------- tests/unit/PolicyUtilsTest.ts | 9 +++++++-- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 7c8a6791d65b..26cf63535458 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5885,6 +5885,7 @@ const CONST = { MAX_TAX_RATE_INTEGER_PLACES: 4, MAX_TAX_RATE_DECIMAL_PLACES: 4, + MIN_TAX_RATE_DECIMAL_PLACES: 2, DOWNLOADS_PATH: '/Downloads', DOWNLOADS_TIMEOUT: 5000, diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index f9ac681cb468..b701a32a7c98 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -164,7 +164,8 @@ function convertAmountToDisplayString(amount = 0, currency: string = CONST.CURRE return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, - minimumFractionDigits: CONST.MAX_TAX_RATE_DECIMAL_PLACES, + minimumFractionDigits: CONST.MIN_TAX_RATE_DECIMAL_PLACES, + maximumFractionDigits: CONST.MAX_TAX_RATE_DECIMAL_PLACES, }); } diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 4592500e9250..dc819bd623c3 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -124,11 +124,12 @@ function hasCustomUnitsError(policy: OnyxEntry): boolean { } function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => string): number | string { - const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); + // Rounding to 4 decimal places + const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')).toPrecision(4); if (Number.isNaN(numValue)) { return NaN; } - return numValue.toFixed(CONST.CUSTOM_UNITS.RATE_DECIMALS); + return numValue; } /** @@ -160,11 +161,10 @@ function getRateDisplayValue(value: number, toLocaleDigit: (arg: string) => stri } if (withDecimals) { - const decimalPart = numValue.toString().split('.').at(1); - if (decimalPart) { - const fixedDecimalPoints = decimalPart.length > 2 && !decimalPart.endsWith('0') ? 3 : 2; - return Number(numValue).toFixed(fixedDecimalPoints).toString().replace('.', toLocaleDigit('.')); - } + const decimalPart = numValue.toString().split('.').at(1) ?? ''; + // Set the fraction digits to be between 2 and 4 (OD Behavior) + const fractionDigits = Math.min(Math.max(decimalPart.length, CONST.MIN_TAX_RATE_DECIMAL_PLACES), CONST.MAX_TAX_RATE_DECIMAL_PLACES); + return Number(numValue).toFixed(fractionDigits).toString().replace('.', toLocaleDigit('.')); } return numValue.toString().replace('.', toLocaleDigit('.')).substring(0, value.toString().length); diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index 8178bb99e877..ffdc53d12387 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -34,14 +34,19 @@ describe('PolicyUtils', () => { expect(rate).toEqual('10.50'); }); + it('should return non-integer value with 4 decimals as is', () => { + const rate = PolicyUtils.getRateDisplayValue(10.5312, toLocaleDigitMock, true); + expect(rate).toEqual('10.5312'); + }); + it('should return non-integer value with 3 decimals as is', () => { const rate = PolicyUtils.getRateDisplayValue(10.531, toLocaleDigitMock, true); expect(rate).toEqual('10.531'); }); - it('should return non-integer value with 3+ decimals cut to 3', () => { + it('should return non-integer value with 4+ decimals cut to 4', () => { const rate = PolicyUtils.getRateDisplayValue(10.531345, toLocaleDigitMock, true); - expect(rate).toEqual('10.531'); + expect(rate).toEqual('10.5314'); }); }); }); From 24b74bbbe8e5ecb9d6e3163b19a8c35f6cc82d9e Mon Sep 17 00:00:00 2001 From: thelullabyy Date: Fri, 15 Nov 2024 16:18:25 +0700 Subject: [PATCH 22/73] fix: test --- src/libs/PolicyUtils.ts | 6 +++--- tests/unit/PolicyUtilsTest.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index dc819bd623c3..2aff043c1b84 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -124,12 +124,12 @@ function hasCustomUnitsError(policy: OnyxEntry): boolean { } function getNumericValue(value: number | string, toLocaleDigit: (arg: string) => string): number | string { - // Rounding to 4 decimal places - const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')).toPrecision(4); + const numValue = parseFloat(value.toString().replace(toLocaleDigit('.'), '.')); if (Number.isNaN(numValue)) { return NaN; } - return numValue; + // Rounding to 4 decimal places + return parseFloat(numValue.toFixed(CONST.MAX_TAX_RATE_DECIMAL_PLACES)); } /** diff --git a/tests/unit/PolicyUtilsTest.ts b/tests/unit/PolicyUtilsTest.ts index ffdc53d12387..e96e1011801e 100644 --- a/tests/unit/PolicyUtilsTest.ts +++ b/tests/unit/PolicyUtilsTest.ts @@ -45,8 +45,8 @@ describe('PolicyUtils', () => { }); it('should return non-integer value with 4+ decimals cut to 4', () => { - const rate = PolicyUtils.getRateDisplayValue(10.531345, toLocaleDigitMock, true); - expect(rate).toEqual('10.5314'); + const rate = PolicyUtils.getRateDisplayValue(10.53135, toLocaleDigitMock, true); + expect(rate).toEqual('10.5313'); }); }); }); From 5ba7e362b448f00eba49e8dfda1d5e6e732e056a Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 18 Nov 2024 13:32:43 +0700 Subject: [PATCH 23/73] update childStateNum and childStatusNum for parent report action --- src/libs/actions/IOU.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3b0b43accbc6..30ff1ccf7a78 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7065,6 +7065,19 @@ function unapproveExpenseReport(expenseReport: OnyxEntry) { const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionData, optimisticNextStepData]; + if (expenseReport.parentReportID && expenseReport.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, + value: { + [expenseReport.parentReportActionID]: { + childStateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + childStatusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + }, + }, + }); + } + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -7276,6 +7289,19 @@ function cancelPayment(expenseReport: OnyxEntry, chatReport: O }, ]; + if (expenseReport.parentReportID && expenseReport.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, + value: { + [expenseReport.parentReportActionID]: { + childStateNum: stateNum, + childStatusNum: statusNum, + }, + }, + }); + } + optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, From 110403f296bc692e5f2c404269f611fa8d88a5dc Mon Sep 17 00:00:00 2001 From: Anusha Date: Tue, 19 Nov 2024 03:34:48 +0500 Subject: [PATCH 24/73] fix back navigation --- src/components/ReportWelcomeText.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 128b7b9e81a1..1e3ce6119315 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -127,7 +127,7 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { if (!canEditReportDescription) { return; } - const activeRoute = Navigation.getReportRHPActiveRoute(); + const activeRoute = Navigation.getActiveRoute(); Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1', activeRoute)); }} style={[styles.renderHTML, canEditReportDescription ? styles.cursorPointer : styles.cursorText]} @@ -155,7 +155,7 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { (welcomeMessage?.messageHtml ? ( { - const activeRoute = Navigation.getReportRHPActiveRoute(); + const activeRoute = Navigation.getActiveRoute(); if (canEditReportDescription) { Navigation.navigate(ROUTES.REPORT_DESCRIPTION.getRoute(report?.reportID ?? '-1', activeRoute)); return; From 91a0d0d4a5e27dccbe5fccca835d11eba9ade529 Mon Sep 17 00:00:00 2001 From: abzokhattab Date: Tue, 19 Nov 2024 10:28:24 +0100 Subject: [PATCH 25/73] adding violations as an argument to the can approve and pay functions --- src/components/MoneyReportHeader.tsx | 12 ++++++++---- src/components/ReportActionItem/ReportPreview.tsx | 6 +++--- src/libs/actions/IOU.ts | 15 ++++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index d01b69ed5649..88c947b5ee6f 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -108,7 +108,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID, transactions), [moneyRequestReport?.reportID, transactions]); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; - const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); + const isDraft = !ReportUtils.isOpenExpenseReport(moneyRequestReport); const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); const navigateBackToAfterDelete = useRef(); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); @@ -120,10 +120,11 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isPayAtEndExpense = TransactionUtils.isPayAtEndExpense(transaction); const isArchivedReport = ReportUtils.isArchivedRoomWithID(moneyRequestReport?.reportID); const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID ?? '-1'}`, {selector: ReportUtils.getArchiveReason}); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const getCanIOUBePaid = useCallback( - (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy, transaction ? [transaction] : undefined, onlyShowPayElsewhere), - [moneyRequestReport, chatReport, policy, transaction], + (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy, transactionViolations, transaction ? [transaction] : undefined, onlyShowPayElsewhere), + [moneyRequestReport, chatReport, policy, transaction, transactionViolations], ); const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); @@ -135,7 +136,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const shouldShowPayButton = canIOUBePaid || onlyShowPayElsewhere; - const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(moneyRequestReport, policy) && !hasOnlyPendingTransactions, [moneyRequestReport, policy, hasOnlyPendingTransactions]); + const shouldShowApproveButton = useMemo( + () => IOU.canApproveIOU(moneyRequestReport, policy, transactionViolations) && !hasOnlyPendingTransactions, + [moneyRequestReport, policy, hasOnlyPendingTransactions, transactionViolations], + ); const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 5edeffd4dea4..bbceeb18bdcd 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -330,14 +330,14 @@ function ReportPreview({ const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const getCanIOUBePaid = useCallback( - (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere), - [iouReport, chatReport, policy, allTransactions], + (onlyShowPayElsewhere = false) => IOU.canIOUBePaid(iouReport, chatReport, policy, transactionViolations, allTransactions, onlyShowPayElsewhere), + [iouReport, chatReport, policy, allTransactions, transactionViolations], ); const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; - const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy), [iouReport, policy]); + const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, policy, transactionViolations), [iouReport, policy, transactionViolations]); const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ee529f28cc9b..eac4178660b7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6741,7 +6741,11 @@ function sendMoneyWithWallet(report: OnyxEntry, amount: number Report.notifyNewAction(params.chatReportID, managerID); } -function canApproveIOU(iouReport: OnyxTypes.OnyxInputOrEntry, policy: OnyxTypes.OnyxInputOrEntry) { +function canApproveIOU( + iouReport: OnyxTypes.OnyxInputOrEntry, + policy: OnyxTypes.OnyxInputOrEntry, + violations: OnyxCollection, +) { // Only expense reports can be approved const isPaidGroupPolicy = policy && PolicyUtils.isPaidGroupPolicy(policy); if (!isPaidGroupPolicy) { @@ -6760,7 +6764,7 @@ function canApproveIOU(iouReport: OnyxTypes.OnyxInputOrEntry, const iouSettled = ReportUtils.isSettled(iouReport?.reportID); const reportNameValuePairs = ReportUtils.getReportNameValuePairs(iouReport?.reportID); const isArchivedReport = ReportUtils.isArchivedRoom(iouReport, reportNameValuePairs); - const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', violations); let isTransactionBeingScanned = false; const reportTransactions = TransactionUtils.getAllReportTransactions(iouReport?.reportID); for (const transaction of reportTransactions) { @@ -6780,6 +6784,7 @@ function canIOUBePaid( iouReport: OnyxTypes.OnyxInputOrEntry, chatReport: OnyxTypes.OnyxInputOrEntry, policy: OnyxTypes.OnyxInputOrEntry, + violations: OnyxCollection, transactions?: OnyxTypes.Transaction[], onlyShowPayElsewhere = false, ) { @@ -6824,10 +6829,10 @@ function canIOUBePaid( const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport); const isAutoReimbursable = policy?.reimbursementChoice === CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES ? false : ReportUtils.canBeAutoReimbursed(iouReport, policy); - const shouldBeApproved = canApproveIOU(iouReport, policy); - const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', allTransactionViolations); - const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); + const shouldBeApproved = canApproveIOU(iouReport, policy, violations); + const hasViolations = ReportUtils.hasViolations(iouReport?.reportID ?? '-1', violations); + const isPayAtEndExpenseReport = ReportUtils.isPayAtEndExpenseReport(iouReport?.reportID, transactions); return ( isPayer && !isOpenExpenseReport && From 29e5c226a7620faed55bf43103b2df12c60fb3d9 Mon Sep 17 00:00:00 2001 From: abzokhattab Date: Tue, 19 Nov 2024 10:36:51 +0100 Subject: [PATCH 26/73] minor edit --- src/components/MoneyReportHeader.tsx | 2 +- src/libs/actions/IOU.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 88c947b5ee6f..3976538afe0a 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -108,7 +108,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID, transactions), [moneyRequestReport?.reportID, transactions]); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; - const isDraft = !ReportUtils.isOpenExpenseReport(moneyRequestReport); + const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); const navigateBackToAfterDelete = useRef(); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index eac4178660b7..7ac367e3b559 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -6853,7 +6853,7 @@ function getIOUReportActionToApproveOrPay(chatReport: OnyxEntry { const iouReport = ReportUtils.getReportOrDraftReport(action.childReportID ?? '-1'); const policy = PolicyUtils.getPolicy(iouReport?.policyID); - const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, policy); + const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy, allTransactionViolations) || canApproveIOU(iouReport, policy, allTransactionViolations); return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && shouldShowSettlementButton; }); } From e30f72e98a1768ab7406b8e16c5cafc96c74e9fa Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 19 Nov 2024 21:29:01 +0700 Subject: [PATCH 27/73] add failureData --- src/libs/actions/IOU.ts | 74 ++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4d487fa14aaa..64c2eb02269b 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7083,19 +7083,6 @@ function unapproveExpenseReport(expenseReport: OnyxEntry) { const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionData, optimisticNextStepData]; - if (expenseReport.parentReportID && expenseReport.parentReportActionID) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, - value: { - [expenseReport.parentReportActionID]: { - childStateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - childStatusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, - }, - }, - }); - } - const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -7134,6 +7121,30 @@ function unapproveExpenseReport(expenseReport: OnyxEntry) { }, ]; + if (expenseReport.parentReportID && expenseReport.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, + value: { + [expenseReport.parentReportActionID]: { + childStateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + childStatusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + }, + }, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, + value: { + [expenseReport.parentReportActionID]: { + childStateNum: expenseReport.stateNum, + childStatusNum: expenseReport.statusNum, + }, + }, + }); + } + const parameters: UnapproveExpenseReportParams = { reportID: expenseReport.reportID, reportActionID: optimisticUnapprovedReportAction.reportActionID, @@ -7307,19 +7318,6 @@ function cancelPayment(expenseReport: OnyxEntry, chatReport: O }, ]; - if (expenseReport.parentReportID && expenseReport.parentReportActionID) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, - value: { - [expenseReport.parentReportActionID]: { - childStateNum: stateNum, - childStatusNum: statusNum, - }, - }, - }); - } - optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, @@ -7357,6 +7355,30 @@ function cancelPayment(expenseReport: OnyxEntry, chatReport: O }, ]; + if (expenseReport.parentReportID && expenseReport.parentReportActionID) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, + value: { + [expenseReport.parentReportActionID]: { + childStateNum: stateNum, + childStatusNum: statusNum, + }, + }, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.parentReportID}`, + value: { + [expenseReport.parentReportActionID]: { + childStateNum: expenseReport.stateNum, + childStatusNum: expenseReport.statusNum, + }, + }, + }); + } + if (chatReport?.reportID) { failureData.push({ onyxMethod: Onyx.METHOD.MERGE, From 8bd9c6cabf7b471e4b67e34276bf8de41e90379c Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 19 Nov 2024 09:24:19 -0700 Subject: [PATCH 28/73] Update tests/README.md --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index cd6cdefd0b40..87aa9068013b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,7 +59,7 @@ expect(onyxData).toBe(expectedOnyxData); ## Documenting Tests -Comments are just as criticle in tests as the tests themselves. They provide context behind why the test was written and what the expected behavior is supposed to be which will benefit the future generations of engineers looking at them. Think about it. When was the last time you saw a unit test and wondered why it was written that way and then you didn't want to touch it because you didn't know if changing the behavior of the test was appropriate or not? It was probably pretty recent :D +Comments are just as critical in tests as the tests themselves. They provide context behind why the test was written and what the expected behavior is supposed to be which will benefit the future generations of engineers looking at them. Think about it. When was the last time you saw a unit test and wondered why it was written that way and then you didn't want to touch it because you didn't know if changing the behavior of the test was appropriate or not? It was probably pretty recent :D In order to give future engineers the best context for a unit test, follow this guide: From a73f1421afeba523a992457861c676bc70935afe Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Tue, 19 Nov 2024 09:25:55 -0700 Subject: [PATCH 29/73] Be even more clear --- tests/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/README.md b/tests/README.md index 87aa9068013b..08df0b89794b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -68,8 +68,9 @@ In order to give future engineers the best context for a unit test, follow this - "When" - Describes what action is being done and the thing that is being tested - "Then" - Describes what is expected to happen -2. DO explain **WHY** the test is doing what it is doing in every comment. -3. DO NOT explain **WHAT** the code is doing in comments. This information should be self-evident. +2. DO begin each comment section with the literal words "Given", "When", and "Then", just like the examples below. +3. DO explain **WHY** the test is doing what it is doing in every comment. +4. DO NOT explain **WHAT** the code is doing in comments. This information should be self-evident. The format looks like this: From d53ec6516a08e1e4e3b36b107945750fa25cd9d5 Mon Sep 17 00:00:00 2001 From: abzokhattab Date: Wed, 20 Nov 2024 17:18:23 +0100 Subject: [PATCH 30/73] minor --- src/libs/actions/IOU.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8856c6b775ae..0a893ad7a2a8 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1,7 +1,6 @@ -import { format } from 'date-fns'; -import { fastMerge, Str } from 'expensify-common'; -import { InteractionManager } from 'react-native'; - +import {format} from 'date-fns'; +import {fastMerge, Str} from 'expensify-common'; +import {InteractionManager} from 'react-native'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxInputValue, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {PartialDeep, SetRequired, ValueOf} from 'type-fest'; From 057d2cae183fdb40842c222d0e8f0a57fd270b0c Mon Sep 17 00:00:00 2001 From: Dysto coder Date: Thu, 21 Nov 2024 05:48:40 +0300 Subject: [PATCH 31/73] navigate to switcher when clicking back button after creating a workspace from swithcer --- .../WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx b/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx index ecfdb0d83ace..4a6b6a473188 100644 --- a/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx +++ b/src/pages/WorkspaceSwitcherPage/WorkspaceCardCreateAWorkspace.tsx @@ -4,6 +4,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Section, {CARD_LAYOUT} from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; import * as App from '@userActions/App'; function WorkspaceCardCreateAWorkspace() { @@ -21,7 +22,8 @@ function WorkspaceCardCreateAWorkspace() { >