From 8f09752207ee5e07cf8216c1bf14abb918a18ffd Mon Sep 17 00:00:00 2001 From: Rayane <77965000+rayane-d@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:13:10 +0100 Subject: [PATCH 01/16] Add new tooltips --- src/CONST.ts | 3 ++ .../ProductTrainingContext/TOOLTIPS.ts | 36 ++++++++++++++++++- .../ProductTrainingContext/index.tsx | 14 +++++++- src/languages/en.ts | 12 +++++++ src/languages/es.ts | 12 +++++++ 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8ac06a52a8a5..a6016d9de418 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6883,6 +6883,9 @@ const CONST = { SCAN_TEST_TOOLTIP: 'scanTestTooltip', SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager', SCAN_TEST_CONFIRMATION: 'scanTestConfirmation', + OUTSANDING_FILTER: 'outstandingFilter', + SETTINGS_TAB: 'settingsTab', + WORKSPACES_SETTINGS: 'workspacesSettings', }, CHANGE_POLICY_TRAINING_MODAL: 'changePolicyModal', SMART_BANNER_HEIGHT: 152, diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index b0bed79b96e3..4e99672143bf 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -12,12 +12,16 @@ const { SCAN_TEST_TOOLTIP, SCAN_TEST_TOOLTIP_MANAGER, SCAN_TEST_CONFIRMATION, + OUTSANDING_FILTER, + SETTINGS_TAB, + WORKSPACES_SETTINGS, } = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; type ProductTrainingTooltipName = ValueOf; type ShouldShowConditionProps = { - shouldUseNarrowLayout?: boolean; + shouldUseNarrowLayout: boolean; + isUserPolicyAdmin: boolean; }; type TooltipData = { @@ -118,6 +122,36 @@ const TOOLTIPS: Record = { priority: 1100, shouldShow: () => true, }, + [OUTSANDING_FILTER]: { + content: [ + {text: 'productTrainingTooltip.outstandingFilter.part1', isBold: false}, + {text: 'productTrainingTooltip.outstandingFilter.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(OUTSANDING_FILTER), + name: OUTSANDING_FILTER, + priority: 1925, + shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, + }, + [SETTINGS_TAB]: { + content: [ + {text: 'productTrainingTooltip.settingsTab.part1', isBold: false}, + {text: 'productTrainingTooltip.settingsTab.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(SETTINGS_TAB), + name: SETTINGS_TAB, + priority: 1750, + shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, + }, + [WORKSPACES_SETTINGS]: { + content: [ + {text: 'productTrainingTooltip.workspacesSettings.part1', isBold: false}, + {text: 'productTrainingTooltip.workspacesSettings.part2', isBold: true}, + ], + onHideTooltip: () => dismissProductTraining(WORKSPACES_SETTINGS), + name: WORKSPACES_SETTINGS, + priority: 1550, + shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, + }, }; export default TOOLTIPS; diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index c877e1d45fbc..4130d3a3f42b 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -18,6 +18,7 @@ import type ChildrenProps from '@src/types/utils/ChildrenProps'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type {ProductTrainingTooltipName} from './TOOLTIPS'; import TOOLTIPS from './TOOLTIPS'; +import { getActiveAdminWorkspaces } from '@libs/PolicyUtils'; type ProductTrainingContextType = { shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; @@ -51,6 +52,16 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { selector: hasCompletedGuidedSetupFlowSelector, }); + const [allPolicies, allPoliciesMetadata] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const [currentUserLogin, currentUserLoginMetadata] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email}); + + const isUserPolicyAdmin = useMemo(() => { + if (!allPolicies || !currentUserLogin || isLoadingOnyxValue(allPoliciesMetadata, currentUserLoginMetadata)) { + return false; + } + return getActiveAdminWorkspaces(allPolicies, currentUserLogin).length > 0; + }, [allPolicies, currentUserLogin, allPoliciesMetadata, currentUserLoginMetadata]); + const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -125,9 +136,10 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return tooltipConfig.shouldShow({ shouldUseNarrowLayout, + isUserPolicyAdmin, }); }, - [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout, isModalVisible, isLoadingApp], + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout, isModalVisible, isLoadingApp, isUserPolicyAdmin], ); const registerTooltip = useCallback( diff --git a/src/languages/en.ts b/src/languages/en.ts index 66f9fc933f74..83d96e0009cf 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6102,6 +6102,18 @@ const translations = { tryItOut: 'Try it out', noThanks: 'No thanks', }, + outstandingFilter: { + part1: 'Filter for expenses that ', + part2: 'need approval', + }, + settingsTab: { + part1: 'View your ', + part2: 'workspace and account settings', + }, + workspacesSettings: { + part1: 'Explore your ', + part2: 'workspaces', + }, }, discardChangesConfirmation: { title: 'Discard changes?', diff --git a/src/languages/es.ts b/src/languages/es.ts index a6388799c309..6a04edb43c62 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6623,6 +6623,18 @@ const translations = { tryItOut: 'Prueba esto', noThanks: 'No, gracias', }, + outstandingFilter: { + part1: 'Filter for expenses that ', + part2: 'need approval', + }, + settingsTab: { + part1: 'Explore your ', + part2: 'workspace and account settings', + }, + workspacesSettings: { + part1: 'Explore your ', + part2: 'workspaces', + }, }, discardChangesConfirmation: { title: '¿Descartar cambios?', From a402e56357f0f0162e5e0e984c054fc42d5404c2 Mon Sep 17 00:00:00 2001 From: Rayane <77965000+rayane-d@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:19:22 +0100 Subject: [PATCH 02/16] add types --- src/types/onyx/DismissedProductTraining.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index d0bcdaebdc3e..6a45d01377de 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -9,6 +9,9 @@ const { SCAN_TEST_TOOLTIP, SCAN_TEST_TOOLTIP_MANAGER, SCAN_TEST_CONFIRMATION, + OUTSANDING_FILTER, + SETTINGS_TAB, + WORKSPACES_SETTINGS, } = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; /** * This type is used to store the timestamp of when the user dismisses a product training ui elements. @@ -61,6 +64,20 @@ type DismissedProductTraining = { */ [SCAN_TEST_CONFIRMATION]: string; + /** + * When user dismisses the outstanding filter product training tooltip, we store the timestamp here. + */ + [OUTSANDING_FILTER]: string; + + /** + * When user dismisses the settings tab product training tooltip, we store the timestamp here. + */ + [SETTINGS_TAB]: string; + /** + * When user dismisses the workspaces settings product training tooltip, we store the timestamp here. + */ + [WORKSPACES_SETTINGS]: string; + /** * When user dismisses the ChangeReportPolicy feature training modal, we store the timestamp here. */ From 357ec91860a9f1058bee7740bd941403d739ee35 Mon Sep 17 00:00:00 2001 From: Rayane <77965000+rayane-d@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:19:30 +0100 Subject: [PATCH 03/16] prettier --- src/components/ProductTrainingContext/TOOLTIPS.ts | 2 +- src/components/ProductTrainingContext/index.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index 4e99672143bf..db6b3c012ca8 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -151,7 +151,7 @@ const TOOLTIPS: Record = { name: WORKSPACES_SETTINGS, priority: 1550, shouldShow: ({isUserPolicyAdmin}) => isUserPolicyAdmin, - }, + }, }; export default TOOLTIPS; diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 4130d3a3f42b..96cf65ea65a4 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -12,13 +12,13 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {parseFSAttributes} from '@libs/Fullstory'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import {getActiveAdminWorkspaces} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type {ProductTrainingTooltipName} from './TOOLTIPS'; import TOOLTIPS from './TOOLTIPS'; -import { getActiveAdminWorkspaces } from '@libs/PolicyUtils'; type ProductTrainingContextType = { shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; @@ -139,7 +139,16 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { isUserPolicyAdmin, }); }, - [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout, isModalVisible, isLoadingApp, isUserPolicyAdmin], + [ + dismissedProductTraining, + hasBeenAddedToNudgeMigration, + isOnboardingCompleted, + isOnboardingCompletedMetadata, + shouldUseNarrowLayout, + isModalVisible, + isLoadingApp, + isUserPolicyAdmin, + ], ); const registerTooltip = useCallback( From 1cfa4827ae1de109e66329b4eae5df9b9250133f Mon Sep 17 00:00:00 2001 From: Rayane <77965000+rayane-d@users.noreply.github.com> Date: Tue, 15 Apr 2025 18:45:02 +0100 Subject: [PATCH 04/16] update type --- src/types/onyx/DismissedProductTraining.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index 5018edf13802..465d7aee641b 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -78,16 +78,16 @@ type DismissedProductTraining = { /** * When user dismisses the outstanding filter product training tooltip, we store the timestamp here. */ - [OUTSANDING_FILTER]: string; + [OUTSANDING_FILTER]: DismissedProductTrainingElement; /** * When user dismisses the settings tab product training tooltip, we store the timestamp here. */ - [SETTINGS_TAB]: string; + [SETTINGS_TAB]: DismissedProductTrainingElement; /** * When user dismisses the workspaces settings product training tooltip, we store the timestamp here. */ - [WORKSPACES_SETTINGS]: string; + [WORKSPACES_SETTINGS]: DismissedProductTrainingElement; /** * When user dismisses the ChangeReportPolicy feature training modal, we store the timestamp here. From 88dd247096e8eec026f7a92f82b4d06d84010ceb Mon Sep 17 00:00:00 2001 From: Rayane <77965000+rayane-d@users.noreply.github.com> Date: Thu, 1 May 2025 11:42:06 +0100 Subject: [PATCH 05/16] update translations --- src/languages/en.ts | 2 +- src/languages/es.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c0a498c6a4ca..512d6d2329fb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6245,7 +6245,7 @@ const translations = { noThanks: 'No thanks', }, outstandingFilter: { - part1: 'Filter for expenses that ', + part1: 'Filter for expenses\nthat ', part2: 'need approval', }, settingsTab: { diff --git a/src/languages/es.ts b/src/languages/es.ts index cbc4f0cceeeb..3968d74c0adc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6770,16 +6770,16 @@ const translations = { noThanks: 'No, gracias', }, outstandingFilter: { - part1: 'Filter for expenses that ', - part2: 'need approval', + part1: 'Filtra los gastos\nque ', + part2: 'necesitan aprobación', }, settingsTab: { - part1: 'Explore your ', - part2: 'workspace and account settings', + part1: 'Explora ', + part2: 'tu espacio de trabajo y la configuración de tu cuenta', }, workspacesSettings: { - part1: 'Explore your ', - part2: 'workspaces', + part1: 'Ver tus ', + part2: 'espacios de trabajo', }, }, discardChangesConfirmation: { From 0671876d72c3dabe7923a6ed34f07ff6d4bf0d58 Mon Sep 17 00:00:00 2001 From: Rayane <77965000+rayane-d@users.noreply.github.com> Date: Thu, 1 May 2025 12:09:33 +0100 Subject: [PATCH 06/16] Add Outstanding filter tooltip --- .../SearchPageHeader/SearchStatusBar.tsx | 83 ++++++++++++++----- .../BaseEducationalTooltip.tsx | 17 +++- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchStatusBar.tsx b/src/components/Search/SearchPageHeader/SearchStatusBar.tsx index c5334dac2b35..b6f3ccec4261 100644 --- a/src/components/Search/SearchPageHeader/SearchStatusBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchStatusBar.tsx @@ -1,4 +1,5 @@ -import React, {useMemo, useRef} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; // eslint-disable-next-line no-restricted-imports import type {ScrollView as RNScrollView, ViewStyle} from 'react-native'; @@ -7,13 +8,16 @@ import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import * as Expensicons from '@components/Icon/Expensicons'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import ScrollView from '@components/ScrollView'; import {useSearchContext} from '@components/Search/SearchContext'; import type {ChatSearchStatus, ExpenseSearchStatus, InvoiceSearchStatus, SearchGroupBy, SearchQueryJSON, TripSearchStatus} from '@components/Search/types'; import SearchStatusSkeleton from '@components/Skeletons/SearchStatusSkeleton'; +import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; import useSingleExecution from '@hooks/useSingleExecution'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -229,6 +233,24 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: false}); const {isOffline} = useNetwork(); + const [isScreenFocused, setIsScreenFocused] = useState(false); + + useFocusEffect( + useCallback(() => { + setIsScreenFocused(true); + return () => { + setIsScreenFocused(false); + }; + }, []), + ); + const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.OUTSANDING_FILTER, + isScreenFocused, + ); + // Controls the visibility of the educational tooltip based on user scrolling. + // Hides the tooltip when the user is scrolling and displays it once scrolling stops. + const triggerScrollEvent = useScrollEventEmitter(); + const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions ?? {}), [selectedTransactions]); const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || (!!selectionMode && selectionMode.isEnabled)); @@ -279,9 +301,16 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear ref={scrollRef} horizontal showsHorizontalScrollIndicator={false} + onScroll={() => { + triggerScrollEvent(); + }} > {options.map((item, index) => { + const isOutstanding = item.status === CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING; const onPress = singleExecution(() => { + if (isOutstanding) { + hideProductTrainingTooltip(); + } onStatusChange?.(); const query = buildSearchQueryString({...queryJSON, status: item.status}); Navigation.setParams({q: query}); @@ -291,27 +320,41 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear const isLastItem = index === options.length - 1; return ( -