diff --git a/src/CONST.ts b/src/CONST.ts index d52185fa4b3d..c3b29ec66501 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6997,6 +6997,7 @@ const CONST = { SCAN_TEST_TOOLTIP: 'scanTestTooltip', SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager', SCAN_TEST_CONFIRMATION: 'scanTestConfirmation', + EXPENSE_REPORTS_FILTER: 'expenseReportsFilter', }, CHANGE_POLICY_TRAINING_MODAL: 'changePolicyModal', SMART_BANNER_HEIGHT: 152, diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index 0256c7f67988..3cabc8cbdc07 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -12,12 +12,15 @@ const { SCAN_TEST_TOOLTIP, SCAN_TEST_TOOLTIP_MANAGER, SCAN_TEST_CONFIRMATION, + EXPENSE_REPORTS_FILTER, } = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; type ProductTrainingTooltipName = ValueOf; type ShouldShowConditionProps = { - shouldUseNarrowLayout?: boolean; + shouldUseNarrowLayout: boolean; + isUserPolicyAdmin: boolean; + hasBeenAddedToNudgeMigration: boolean; }; type TooltipData = { @@ -65,14 +68,15 @@ const TOOLTIPS: Record = { }, [BOTTOM_NAV_INBOX_TOOLTIP]: { content: [ - {text: 'productTrainingTooltip.bottomNavInboxTooltip.part1', isBold: true}, - {text: 'productTrainingTooltip.bottomNavInboxTooltip.part2', isBold: false}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part1', isBold: false}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part2', isBold: true}, {text: 'productTrainingTooltip.bottomNavInboxTooltip.part3', isBold: false}, + {text: 'productTrainingTooltip.bottomNavInboxTooltip.part4', isBold: true}, ], onHideTooltip: (isDismissedUsingCloseButton = false) => dismissProductTraining(BOTTOM_NAV_INBOX_TOOLTIP, isDismissedUsingCloseButton), name: BOTTOM_NAV_INBOX_TOOLTIP, - priority: 900, - shouldShow: () => true, + priority: 1700, + shouldShow: ({hasBeenAddedToNudgeMigration}) => hasBeenAddedToNudgeMigration, }, [LHN_WORKSPACE_CHAT_TOOLTIP]: { content: [ @@ -85,6 +89,18 @@ const TOOLTIPS: Record = { priority: 800, shouldShow: () => true, }, + [EXPENSE_REPORTS_FILTER]: { + content: [ + {text: 'productTrainingTooltip.expenseReportsFilter.part1', isBold: false}, + {text: 'productTrainingTooltip.expenseReportsFilter.part2', isBold: true}, + {text: 'productTrainingTooltip.expenseReportsFilter.part3', isBold: false}, + ], + onHideTooltip: () => dismissProductTraining(EXPENSE_REPORTS_FILTER), + name: EXPENSE_REPORTS_FILTER, + priority: 2000, + shouldShow: ({shouldUseNarrowLayout, isUserPolicyAdmin, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => + !shouldUseNarrowLayout && isUserPolicyAdmin && hasBeenAddedToNudgeMigration, + }, [SCAN_TEST_TOOLTIP]: { content: [ {text: 'productTrainingTooltip.scanTestTooltip.part1', isBold: false}, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 9b44460a2a4f..1a6f7642d387 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -14,6 +14,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {parseFSAttributes} from '@libs/Fullstory'; import getPlatform from '@libs/getPlatform'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import {getActiveAdminWorkspaces} from '@libs/PolicyUtils'; import isProductTrainingElementDismissed from '@libs/TooltipUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -48,17 +49,28 @@ const ProductTrainingContext = createContext({ }); function ProductTrainingContextProvider({children}: ChildrenProps) { - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true}); - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP, {initialValue: true, canBeMissing: true}); + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {canBeMissing: true}); const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; const [isOnboardingCompleted = true, isOnboardingCompletedMetadata] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { selector: hasCompletedGuidedSetupFlowSelector, + canBeMissing: true, }); - const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const [allPolicies, allPoliciesMetadata] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const [currentUserLogin, currentUserLoginMetadata] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.email, canBeMissing: true}); + + 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, {canBeMissing: true}); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const [modal] = useOnyx(ONYXKEYS.MODAL); + const [modal] = useOnyx(ONYXKEYS.MODAL, {canBeMissing: true}); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const isModalVisible = modal?.isVisible || modal?.willAlertModalBecomeVisible; @@ -129,9 +141,20 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return tooltipConfig.shouldShow({ shouldUseNarrowLayout, + isUserPolicyAdmin, + hasBeenAddedToNudgeMigration, }); }, - [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 3923014d5bae..9ea0e68a3764 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6218,9 +6218,10 @@ const translations = { part2: ' here!', }, bottomNavInboxTooltip: { - part1: 'Your to-do list', - part2: '\n🟢 = ready for you', - part3: ' 🔴 = needs review', + part1: 'Check what ', + part2: 'needs your attention', + part3: '\nand ', + part4: 'chat about expenses.', }, workspaceChatTooltip: { part1: 'Submit expenses', @@ -6232,6 +6233,11 @@ const translations = { part2: ', start chatting,', part3: '\nand more!', }, + expenseReportsFilter: { + part1: 'Welcome! Find all of your', + part2: "\ncompany's reports", + part3: ' here.', + }, scanTestTooltip: { part1: 'Want to see how Scan works?', part2: ' Try a test receipt!', diff --git a/src/languages/es.ts b/src/languages/es.ts index a493fb2441ca..dba7f2ed1720 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6743,9 +6743,10 @@ const translations = { part2: ' aquí', }, bottomNavInboxTooltip: { - part1: 'Tu lista de tareas', - part2: '\n🟢 = listo para ti', - part3: ' 🔴 = necesita revisión', + part1: 'Revisa lo que ', + part2: 'necesita tu atención', + part3: '\ny ', + part4: 'chatea sobre los gastos.', }, workspaceChatTooltip: { part1: 'Envía gastos', @@ -6757,6 +6758,11 @@ const translations = { part2: ', comienza a chatear,', part3: '\ny mucho más!', }, + expenseReportsFilter: { + part1: '¡Bienvenido! Aquí encontrarás todos los', + part2: '\ninformes de tu empresa', + part3: '.', + }, scanTestTooltip: { part1: '¿Quieres ver cómo funciona Escanear?', part2: '¡Prueba con un recibo de prueba!', diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 6fc2a61961e4..c6283c23f187 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -56,10 +56,16 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { const {isOffline} = useNetwork(); const shouldShowSavedSearchesMenuItemTitle = Object.values(savedSearches ?? {}).filter((s) => s.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length > 0; const isFocused = useIsFocused(); - const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( - CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH, - shouldShowSavedSearchesMenuItemTitle && isFocused, - ); + const { + shouldShowProductTrainingTooltip: shouldShowSavedSearchTooltip, + renderProductTrainingTooltip: renderSavedSearchTooltip, + hideProductTrainingTooltip: hideSavedSearchTooltip, + } = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH, shouldShowSavedSearchesMenuItemTitle && isFocused); + const { + shouldShowProductTrainingTooltip: shouldShowExpenseReportsTypeTooltip, + renderProductTrainingTooltip: renderExpenseReportsTypeTooltip, + hideProductTrainingTooltip: hideExpenseReportsTypeTooltip, + } = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.EXPENSE_REPORTS_FILTER, true); const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); @@ -102,9 +108,9 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { ), style: [styles.alignItemsCenter], @@ -115,23 +121,23 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { tooltipShiftHorizontal: variables.savedSearchShiftHorizontal, tooltipShiftVertical: variables.savedSearchShiftVertical, tooltipWrapperStyle: [styles.mh4, styles.pv2, styles.productTrainingTooltipWrapper], - renderTooltipContent: renderProductTrainingTooltip, + renderTooltipContent: renderSavedSearchTooltip, }; }, [ allCards, hash, getOverflowMenu, + shouldShowSavedSearchTooltip, + hideSavedSearchTooltip, styles.alignItemsCenter, styles.mh4, styles.pv2, styles.productTrainingTooltipWrapper, + renderSavedSearchTooltip, personalDetails, reports, taxRates, - shouldShowProductTrainingTooltip, - hideProductTrainingTooltip, - renderProductTrainingTooltip, cardFeedNamesWithType, allPolicies, canUseLeftHandBar, @@ -211,7 +217,12 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { > {typeMenuItems.map((item, index) => { + const shouldShowTooltip = item.translationPath === 'common.expenseReports' && index !== activeItemIndex && shouldShowExpenseReportsTypeTooltip; + const onPress = singleExecution(() => { + if (shouldShowTooltip) { + hideExpenseReportsTypeTooltip(); + } clearAllFilters(); clearSelectedTransactions(); Navigation.navigate(item.getRoute()); @@ -230,6 +241,15 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { focused={index === activeItemIndex} onPress={onPress} shouldIconUseAutoWidthStyle + shouldRenderTooltip={shouldShowTooltip} + renderTooltipContent={renderExpenseReportsTypeTooltip} + tooltipAnchorAlignment={{ + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, + }} + tooltipShiftHorizontal={variables.expenseReportsTypeTooltipShiftHorizontal} + tooltipWrapperStyle={styles.productTrainingTooltipWrapper} + onEducationTooltipPress={onPress} /> ); })} diff --git a/src/styles/variables.ts b/src/styles/variables.ts index f30205afa64a..f2577c0bb80b 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -277,6 +277,7 @@ export default { savedSearchShiftHorizontal: -10, savedSearchShiftVertical: 6, navigationTabBarInboxTooltipShiftHorizontal: 36, + expenseReportsTypeTooltipShiftHorizontal: 10, inlineImagePreviewMinSize: 64, inlineImagePreviewMaxSize: 148, diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index 0c224fae8547..28a19a9e4292 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -9,6 +9,7 @@ const { SCAN_TEST_TOOLTIP, SCAN_TEST_TOOLTIP_MANAGER, SCAN_TEST_CONFIRMATION, + EXPENSE_REPORTS_FILTER, } = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; /** @@ -72,6 +73,11 @@ type DismissedProductTraining = { */ [SCAN_TEST_CONFIRMATION]: DismissedProductTrainingElement; + /** + * When user dismisses the expenseReportsFilter product training tooltip, we store the timestamp here. + */ + [EXPENSE_REPORTS_FILTER]: DismissedProductTrainingElement; + /** * When user dismisses the ChangeReportPolicy feature training modal, we store the timestamp here. */ diff --git a/tests/ui/components/ProductTrainingContextProvider.tsx b/tests/ui/components/ProductTrainingContextProvider.tsx index 2dbd7300b552..ff57894f6c73 100644 --- a/tests/ui/components/ProductTrainingContextProvider.tsx +++ b/tests/ui/components/ProductTrainingContextProvider.tsx @@ -260,27 +260,6 @@ describe('ProductTrainingContextProvider', () => { }); describe('Layout Specific Behavior', () => { - it('should handle narrow layout specific tooltips based on screen width', async () => { - // When narrow layout is false - mockUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: false}); - - Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: true}); - await waitForBatchedUpdatesWithAct(); - - // TODO: To be replaced by expense reports search tooltip - const testTooltip = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR; - const {result, rerender} = renderHook(() => useProductTrainingContext(testTooltip), {wrapper}); - // Then narrow layout tooltip should not show - expect(result.current.shouldShowProductTrainingTooltip).toBe(false); - - // When narrow layout changes to true - mockUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: true}); - rerender({}); - await waitForBatchedUpdatesWithAct(); - - // Then narrow layout tooltip should show - expect(result.current.shouldShowProductTrainingTooltip).toBe(false); - }); it('should handle wide layout specific tooltips based on screen width', async () => { // When narrow layout is true mockUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, shouldUseNarrowLayout: true});