From cc36d8f10fef8e8be0d2d59fa1a697688f026ebd Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Nov 2024 02:56:40 +0530 Subject: [PATCH 01/28] feat: add product training tooltips and context management --- src/App.tsx | 2 + src/CONST.ts | 6 + .../LHNOptionsList/OptionRowLHN.tsx | 70 +++--- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 84 +++++++ .../ProductTrainingContext.tsx | 233 ++++++++++++++++++ src/languages/en.ts | 3 + src/pages/Search/SearchTypeMenu.tsx | 20 +- src/types/onyx/DismissedProductTraining.ts | 20 ++ 8 files changed, 387 insertions(+), 51 deletions(-) create mode 100644 src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts create mode 100644 src/components/ProductTrainingContext/ProductTrainingContext.tsx diff --git a/src/App.tsx b/src/App.tsx index 52904e0a06c4..78b185dc9fe4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; +import {ProductTrainingContextProvider} from './components/ProductTrainingContext/ProductTrainingContext'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext'; @@ -95,6 +96,7 @@ function App({url}: AppProps) { VideoPopoverMenuContextProvider, KeyboardProvider, SearchRouterContextProvider, + ProductTrainingContextProvider, ]} > diff --git a/src/CONST.ts b/src/CONST.ts index b57df98b486d..940cdc30789c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6372,6 +6372,12 @@ const CONST = { HYBRID_APP: { REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT: 'reorderingReactNativeActivityToFront', }, + PRODUCT_TRAINING_TOOLTIP_NAMES: { + CONCEIRGE_LHN_GBR: 'conciergeLHNGBR', + RENAME_SAVED_SEARCH: 'renameSavedSearch', + QUICK_ACTION_BUTTON: 'quickActionButton', + WORKSAPCE_CHAT_CREATE: 'workspaceChatCreate', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 3e3f4d1b8e5d..92ee4f25b367 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -10,6 +10,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -21,7 +22,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; -import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; @@ -31,7 +31,6 @@ import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportA import FreeTrial from '@pages/settings/Subscription/FreeTrial'; import variables from '@styles/variables'; import Timing from '@userActions/Timing'; -import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -47,11 +46,11 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); - const [isFirstTimeNewExpensifyUser] = useOnyx(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER); - const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); - const [shouldHideGBRTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, {initialValue: true}); + + const isConciergeChatReport = ReportUtils.isConciergeChatReport(report); + const tooltipToRender = isConciergeChatReport && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; + + const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(tooltipToRender); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -65,29 +64,29 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti }, []), ); - const renderGBRTooltip = useCallback( - () => ( - - - {translate('sidebarScreen.tooltip')} - - ), - [ - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentCenter, - styles.flexWrap, - styles.textAlignCenter, - styles.gap1, - styles.quickActionTooltipSubtitle, - theme.tooltipHighlightText, - translate, - ], - ); + // const renderGBRTooltip = useCallback( + // () => ( + // + // + // {translate('sidebarScreen.tooltip')} + // + // ), + // [ + // styles.alignItemsCenter, + // styles.flexRow, + // styles.justifyContentCenter, + // styles.flexWrap, + // styles.textAlignCenter, + // styles.gap1, + // styles.quickActionTooltipSubtitle, + // theme.tooltipHighlightText, + // translate, + // ], + // ); const isInFocusMode = viewMode === CONST.OPTION_MODE.COMPACT; const sidebarInnerRowStyle = StyleSheet.flatten( @@ -173,19 +172,15 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti needsOffscreenAlphaCompositing > @@ -195,6 +190,9 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti onPress={(event) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); Timing.start(CONST.TIMING.OPEN_REPORT); + if (shouldShowProductTrainingElement) { + hideElement(); + } event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts new file mode 100644 index 000000000000..274761e81228 --- /dev/null +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -0,0 +1,84 @@ +import {dismissProductTrainingElement} from '@libs/actions/Welcome'; +import CONST from '@src/CONST'; + +const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; + +type ShouldShowConditionProps = { + isDismissed: boolean; + isOnboardingCompleted: boolean; + hasBeenAddedToNudgeMigration: boolean; + shouldUseNarrowLayout: boolean; +}; + +const PRODUCT_TRAINING_TOOLTIP_DATA = { + [CONCEIRGE_LHN_GBR]: { + content: 'productTrainingTooltip.conciergeLHNGBR', + onHideElement: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), + name: CONCEIRGE_LHN_GBR, + priority: 1300, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { + if (isDismissed || !shouldUseNarrowLayout) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, + [RENAME_SAVED_SEARCH]: { + content: 'search.saveSearchTooltipText', + onHideElement: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), + name: RENAME_SAVED_SEARCH, + priority: 1250, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { + if (isDismissed) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, + [QUICK_ACTION_BUTTON]: { + content: '', + onHideElement: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), + name: QUICK_ACTION_BUTTON, + priority: 1200, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { + if (isDismissed) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, + [WORKSAPCE_CHAT_CREATE]: { + content: '', + onHideElement: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), + name: WORKSAPCE_CHAT_CREATE, + priority: 1100, + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { + if (isDismissed) { + return false; + } + + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + + return true; + }, + }, +}; + +export default PRODUCT_TRAINING_TOOLTIP_DATA; diff --git a/src/components/ProductTrainingContext/ProductTrainingContext.tsx b/src/components/ProductTrainingContext/ProductTrainingContext.tsx new file mode 100644 index 000000000000..0364af640142 --- /dev/null +++ b/src/components/ProductTrainingContext/ProductTrainingContext.tsx @@ -0,0 +1,233 @@ +import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import convertToLTR from '@libs/convertToLTR'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; + +type ProductTrainingElementName = ValueOf; + +type ProductTrainingContextType = { + shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; + renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; + registerTooltip: (elementName: ProductTrainingElementName) => void; + unregisterTooltip: (elementName: ProductTrainingElementName) => void; +}; + +const ProductTrainingContext = createContext({ + shouldRenderElement: () => false, + renderProductTrainingElement: () => null, + registerTooltip: () => {}, + unregisterTooltip: () => {}, +}); + +function ProductTrainingContextProvider({children}: ChildrenProps) { + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); + const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; + const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasCompletedGuidedSetupFlowSelector, + }); + const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const styles = useThemeStyles(); + const theme = useTheme(); + + // Track active tooltips + const [activeTooltips, setActiveTooltips] = useState>(new Set()); + + const unregisterTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + setActiveTooltips((prev) => { + const next = new Set(prev); + next.delete(elementName); + return next; + }); + }, + [setActiveTooltips], + ); + + const determineVisibleTooltip = useCallback(() => { + if (activeTooltips.size === 0) { + return null; + } + + const sortedTooltips = Array.from(activeTooltips) + .map((name) => ({ + name, + priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, + })) + .sort((a, b) => b.priority - a.priority); + + const highestPriorityTooltip = sortedTooltips.at(0); + + if (!highestPriorityTooltip) { + return null; + } + + return highestPriorityTooltip.name; + }, [activeTooltips]); + + const shouldTooltipBeVisible = useCallback( + (elementName: ProductTrainingElementName) => { + const isDismissed = !!dismissedProductTraining?.[elementName]; + + const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + + return tooltipConfig.shouldShow({ + isDismissed, + isOnboardingCompleted, + hasBeenAddedToNudgeMigration, + shouldUseNarrowLayout, + }); + }, + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], + ); + + const registerTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + const shouldRegister = shouldTooltipBeVisible(elementName); + if (!shouldRegister) { + return; + } + setActiveTooltips((prev) => new Set([...prev, elementName])); + }, + [shouldTooltipBeVisible], + ); + + const shouldRenderElement = useCallback( + (elementName: ProductTrainingElementName) => { + // First check base conditions + const shouldShow = shouldTooltipBeVisible(elementName); + if (!shouldShow) { + return false; + } + const visibleTooltip = determineVisibleTooltip(); + + // If this is the highest priority visible tooltip, show it + if (elementName === visibleTooltip) { + return true; + } + + return false; + }, + [shouldTooltipBeVisible, determineVisibleTooltip], + ); + + const renderProductTourElement = useCallback( + (elementName: ProductTrainingElementName) => { + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (!element) { + return null; + } + const processedContent = () => { + const content = convertToLTR(translate(element.content as TranslationPaths)); + + return content ? `${content}` : ''; + }; + return ( + + + + + + + ); + }, + [ + styles.alignItemsCenter, + styles.flexRow, + styles.flexWrap, + styles.gap1, + styles.justifyContentCenter, + styles.p2, + styles.renderHTMLTitle, + styles.textAlignCenter, + theme.tooltipHighlightText, + translate, + ], + ); + + const contextValue = useMemo( + () => ({ + renderProductTrainingElement: renderProductTourElement, + shouldRenderElement, + registerTooltip, + unregisterTooltip, + }), + [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], + ); + + return {children}; +} + +const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { + const context = useContext(ProductTrainingContext); + if (!context) { + throw new Error('useProductTourContext must be used within a ProductTourProvider'); + } + + const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; + + // Register this tooltip when the component mounts and unregister when it unmounts + useEffect(() => { + if (elementName) { + registerTooltip(elementName); + return () => { + unregisterTooltip(elementName); + }; + } + return undefined; + }, [elementName, registerTooltip, unregisterTooltip]); + + const shouldShowProductTrainingElement = useMemo(() => { + if (!elementName) { + return false; + } + return shouldRenderElement(elementName); + }, [elementName, shouldRenderElement]); + + const hideElement = useCallback(() => { + if (!elementName) { + return; + } + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (element?.onHideElement) { + element.onHideElement(); + } + unregisterTooltip(elementName); + }, [elementName, unregisterTooltip]); + + if (!elementName) { + return { + renderProductTourElement: () => null, + hideElement: () => {}, + shouldShowProductTrainingElement: false, + }; + } + + return { + renderProductTourElement: () => renderProductTrainingElement(elementName), + hideElement, + shouldShowProductTrainingElement, + }; +}; + +export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/languages/en.ts b/src/languages/en.ts index 0bfc26408d05..8e427f131b39 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5428,6 +5428,9 @@ const translations = { crossPlatform: 'Do everything from your phone or browser', }, }, + productTrainingTooltip: { + conciergeLHNGBR: 'Get started here!', + }, }; export default translations satisfies TranslationDeepObject; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 5c93a3877ff6..0b617b6bf6fb 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -8,6 +8,7 @@ import MenuItem from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import type {MenuItemWithLink} from '@components/MenuItemList'; import {usePersonalDetails} from '@components/OnyxProvider'; +import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; import ScrollView from '@components/ScrollView'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -62,7 +63,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { const {singleExecution} = useSingleExecution(); const {translate} = useLocalize(); const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES); - const [shouldShowSavedSearchRenameTooltip] = useOnyx(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP); + const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH); const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); const [session] = useOnyx(ONYXKEYS.SESSION); @@ -151,7 +152,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { if (!isNarrow) { return { ...baseMenuItem, - shouldRenderTooltip: index === 0 && shouldShowSavedSearchRenameTooltip === true, + shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, tooltipAnchorAlignment: { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, @@ -159,19 +160,8 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { tooltipShiftHorizontal: -32, tooltipShiftVertical: 15, tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], - onHideTooltip: SearchActions.dismissSavedSearchRenameTooltip, - renderTooltipContent: () => { - return ( - - - {translate('search.saveSearchTooltipText')} - - ); - }, + onHideTooltip: hideElement, + renderTooltipContent: renderProductTourElement, }; } diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index 644ba9db8f9b..3e55b3d9e9c4 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -6,6 +6,26 @@ type DismissedProductTraining = { * When user dismisses the nudgeMigration Welcome Modal, we store the timestamp here. */ nudgeMigrationWelcomeModal: Date; + + /** + * When user dismisses the conciergeLHNGBR product training tooltip, we store the timestamp here. + */ + conciergeLHNGBR: Date; + + /** + * When user dismisses the renameSavedSearch product training tooltip, we store the timestamp here. + */ + renameSavedSearch: Date; + + /** + * When user dismisses the workspaceChatCreate product training tooltip, we store the timestamp here. + */ + workspaceChatCreate: Date; + + /** + * When user dismisses the quickActionButton product training tooltip, we store the timestamp here. + */ + quickActionButton: Date; }; export default DismissedProductTraining; From 7c500dabcee2d85b8886b798e6b34b3028ef2676 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Nov 2024 23:34:07 +0530 Subject: [PATCH 02/28] feat: update product training tooltips and refactor context imports --- src/App.tsx | 2 +- .../LHNOptionsList/OptionRowLHN.tsx | 28 +-- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 2 +- .../ProductTrainingContext/index.tsx | 233 ++++++++++++++++++ src/pages/Search/SearchTypeMenu.tsx | 109 ++++---- 5 files changed, 300 insertions(+), 74 deletions(-) create mode 100644 src/components/ProductTrainingContext/index.tsx diff --git a/src/App.tsx b/src/App.tsx index 78b185dc9fe4..cc824b78fa4c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,7 +18,7 @@ import KeyboardProvider from './components/KeyboardProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; -import {ProductTrainingContextProvider} from './components/ProductTrainingContext/ProductTrainingContext'; +import {ProductTrainingContextProvider} from './components/ProductTrainingContext'; import SafeArea from './components/SafeArea'; import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider'; import {SearchRouterContextProvider} from './components/Search/SearchRouter/SearchRouterContext'; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 92ee4f25b367..6aeb6085bfcc 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -10,7 +10,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -64,30 +64,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti }, []), ); - // const renderGBRTooltip = useCallback( - // () => ( - // - // - // {translate('sidebarScreen.tooltip')} - // - // ), - // [ - // styles.alignItemsCenter, - // styles.flexRow, - // styles.justifyContentCenter, - // styles.flexWrap, - // styles.textAlignCenter, - // styles.gap1, - // styles.quickActionTooltipSubtitle, - // theme.tooltipHighlightText, - // translate, - // ], - // ); - const isInFocusMode = viewMode === CONST.OPTION_MODE.COMPACT; const sidebarInnerRowStyle = StyleSheet.flatten( isInFocusMode @@ -178,7 +154,9 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }} + shouldUseOverlay shiftHorizontal={variables.gbrTooltipShiftHorizontal} + onHideTooltip={hideElement} shiftVertical={variables.composerTooltipShiftVertical} wrapperStyle={styles.quickActionTooltipWrapper} > diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index 274761e81228..915c8c136e53 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -63,7 +63,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [WORKSAPCE_CHAT_CREATE]: { - content: '', + content: 'reportActionCompose.tooltip.subtitle', onHideElement: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx new file mode 100644 index 000000000000..0364af640142 --- /dev/null +++ b/src/components/ProductTrainingContext/index.tsx @@ -0,0 +1,233 @@ +import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import RenderHTML from '@components/RenderHTML'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import convertToLTR from '@libs/convertToLTR'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; + +type ProductTrainingElementName = ValueOf; + +type ProductTrainingContextType = { + shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; + renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; + registerTooltip: (elementName: ProductTrainingElementName) => void; + unregisterTooltip: (elementName: ProductTrainingElementName) => void; +}; + +const ProductTrainingContext = createContext({ + shouldRenderElement: () => false, + renderProductTrainingElement: () => null, + registerTooltip: () => {}, + unregisterTooltip: () => {}, +}); + +function ProductTrainingContextProvider({children}: ChildrenProps) { + const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); + const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; + const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasCompletedGuidedSetupFlowSelector, + }); + const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const styles = useThemeStyles(); + const theme = useTheme(); + + // Track active tooltips + const [activeTooltips, setActiveTooltips] = useState>(new Set()); + + const unregisterTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + setActiveTooltips((prev) => { + const next = new Set(prev); + next.delete(elementName); + return next; + }); + }, + [setActiveTooltips], + ); + + const determineVisibleTooltip = useCallback(() => { + if (activeTooltips.size === 0) { + return null; + } + + const sortedTooltips = Array.from(activeTooltips) + .map((name) => ({ + name, + priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, + })) + .sort((a, b) => b.priority - a.priority); + + const highestPriorityTooltip = sortedTooltips.at(0); + + if (!highestPriorityTooltip) { + return null; + } + + return highestPriorityTooltip.name; + }, [activeTooltips]); + + const shouldTooltipBeVisible = useCallback( + (elementName: ProductTrainingElementName) => { + const isDismissed = !!dismissedProductTraining?.[elementName]; + + const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + + return tooltipConfig.shouldShow({ + isDismissed, + isOnboardingCompleted, + hasBeenAddedToNudgeMigration, + shouldUseNarrowLayout, + }); + }, + [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], + ); + + const registerTooltip = useCallback( + (elementName: ProductTrainingElementName) => { + const shouldRegister = shouldTooltipBeVisible(elementName); + if (!shouldRegister) { + return; + } + setActiveTooltips((prev) => new Set([...prev, elementName])); + }, + [shouldTooltipBeVisible], + ); + + const shouldRenderElement = useCallback( + (elementName: ProductTrainingElementName) => { + // First check base conditions + const shouldShow = shouldTooltipBeVisible(elementName); + if (!shouldShow) { + return false; + } + const visibleTooltip = determineVisibleTooltip(); + + // If this is the highest priority visible tooltip, show it + if (elementName === visibleTooltip) { + return true; + } + + return false; + }, + [shouldTooltipBeVisible, determineVisibleTooltip], + ); + + const renderProductTourElement = useCallback( + (elementName: ProductTrainingElementName) => { + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (!element) { + return null; + } + const processedContent = () => { + const content = convertToLTR(translate(element.content as TranslationPaths)); + + return content ? `${content}` : ''; + }; + return ( + + + + + + + ); + }, + [ + styles.alignItemsCenter, + styles.flexRow, + styles.flexWrap, + styles.gap1, + styles.justifyContentCenter, + styles.p2, + styles.renderHTMLTitle, + styles.textAlignCenter, + theme.tooltipHighlightText, + translate, + ], + ); + + const contextValue = useMemo( + () => ({ + renderProductTrainingElement: renderProductTourElement, + shouldRenderElement, + registerTooltip, + unregisterTooltip, + }), + [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], + ); + + return {children}; +} + +const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { + const context = useContext(ProductTrainingContext); + if (!context) { + throw new Error('useProductTourContext must be used within a ProductTourProvider'); + } + + const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; + + // Register this tooltip when the component mounts and unregister when it unmounts + useEffect(() => { + if (elementName) { + registerTooltip(elementName); + return () => { + unregisterTooltip(elementName); + }; + } + return undefined; + }, [elementName, registerTooltip, unregisterTooltip]); + + const shouldShowProductTrainingElement = useMemo(() => { + if (!elementName) { + return false; + } + return shouldRenderElement(elementName); + }, [elementName, shouldRenderElement]); + + const hideElement = useCallback(() => { + if (!elementName) { + return; + } + const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + if (element?.onHideElement) { + element.onHideElement(); + } + unregisterTooltip(elementName); + }, [elementName, unregisterTooltip]); + + if (!elementName) { + return { + renderProductTourElement: () => null, + hideElement: () => {}, + shouldShowProductTrainingElement: false, + }; + } + + return { + renderProductTourElement: () => renderProductTrainingElement(elementName), + hideElement, + shouldShowProductTrainingElement, + }; +}; + +export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 0b617b6bf6fb..bd42c3dc8ab6 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -8,7 +8,7 @@ import MenuItem from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import type {MenuItemWithLink} from '@components/MenuItemList'; import {usePersonalDetails} from '@components/OnyxProvider'; -import {useProductTrainingContext} from '@components/ProductTrainingContext/ProductTrainingContext'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; import ScrollView from '@components/ScrollView'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -119,54 +119,69 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { [showDeleteModal], ); - const createSavedSearchMenuItem = (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => { - let title = item.name; - if (title === item.query) { - const jsonQuery = SearchQueryUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON); - title = SearchQueryUtils.buildUserReadableQueryString(jsonQuery, personalDetails, reports, taxRates); - } - - const baseMenuItem: SavedSearchMenuItem = { - key, - title, - hash: key, - query: item.query, - shouldShowRightComponent: true, - focused: Number(key) === hash, - onPress: () => { - SearchActions.clearAllFilters(); - Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? '', name: item?.name})); - }, - rightComponent: ( - - ), - styles: [styles.alignItemsCenter], - pendingAction: item.pendingAction, - disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - shouldIconUseAutoWidthStyle: true, - }; + const createSavedSearchMenuItem = useCallback( + (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => { + let title = item.name; + if (title === item.query) { + const jsonQuery = SearchQueryUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON); + title = SearchQueryUtils.buildUserReadableQueryString(jsonQuery, personalDetails, reports, taxRates); + } - if (!isNarrow) { - return { - ...baseMenuItem, - shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, - tooltipAnchorAlignment: { - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + const baseMenuItem: SavedSearchMenuItem = { + key, + title, + hash: key, + query: item.query, + shouldShowRightComponent: true, + focused: Number(key) === hash, + onPress: () => { + SearchActions.clearAllFilters(); + Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: item?.query ?? '', name: item?.name})); }, - tooltipShiftHorizontal: -32, - tooltipShiftVertical: 15, - tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], - onHideTooltip: hideElement, - renderTooltipContent: renderProductTourElement, + rightComponent: ( + + ), + styles: [styles.alignItemsCenter], + pendingAction: item.pendingAction, + disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + shouldIconUseAutoWidthStyle: true, }; - } - return baseMenuItem; - }; + if (!isNarrow) { + return { + ...baseMenuItem, + shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, + tooltipAnchorAlignment: { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }, + tooltipShiftHorizontal: -32, + tooltipShiftVertical: 15, + tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], + onHideTooltip: hideElement, + renderTooltipContent: renderProductTourElement, + }; + } + return baseMenuItem; + }, + [ + hash, + getOverflowMenu, + styles.alignItemsCenter, + styles.bgPaleGreen, + styles.mh4, + styles.pv2, + personalDetails, + reports, + taxRates, + shouldShowProductTrainingElement, + hideElement, + renderProductTourElement, + ], + ); const route = useRoute(); const scrollViewRef = useRef(null); @@ -191,12 +206,12 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { scrollViewRef.current.scrollTo({y: scrollOffset, animated: false}); }, [getScrollOffset, route]); - const savedSearchesMenuItems = () => { + const savedSearchesMenuItems = useCallback(() => { if (!savedSearches) { return []; } return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout, index)); - }; + }, [createSavedSearchMenuItem, savedSearches, shouldUseNarrowLayout]); const renderSavedSearchesSection = useCallback( (menuItems: MenuItemWithLink[]) => ( From 9de9bc7dc4a53ecd0bfc9b06fe00ee25a29128e5 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 30 Nov 2024 23:34:38 +0530 Subject: [PATCH 03/28] feat: remove workspace tooltip from ReportScreen and ReportFooter, integrate product training context in ReportActionCompose --- .../ProductTrainingContext.tsx | 233 ------------------ src/pages/home/ReportScreen.tsx | 2 - .../ReportActionCompose.tsx | 44 +--- src/pages/home/report/ReportFooter.tsx | 7 +- 4 files changed, 9 insertions(+), 277 deletions(-) delete mode 100644 src/components/ProductTrainingContext/ProductTrainingContext.tsx diff --git a/src/components/ProductTrainingContext/ProductTrainingContext.tsx b/src/components/ProductTrainingContext/ProductTrainingContext.tsx deleted file mode 100644 index 0364af640142..000000000000 --- a/src/components/ProductTrainingContext/ProductTrainingContext.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import RenderHTML from '@components/RenderHTML'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import convertToLTR from '@libs/convertToLTR'; -import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; -import type CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; - -type ProductTrainingElementName = ValueOf; - -type ProductTrainingContextType = { - shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; - renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; - registerTooltip: (elementName: ProductTrainingElementName) => void; - unregisterTooltip: (elementName: ProductTrainingElementName) => void; -}; - -const ProductTrainingContext = createContext({ - shouldRenderElement: () => false, - renderProductTrainingElement: () => null, - registerTooltip: () => {}, - unregisterTooltip: () => {}, -}); - -function ProductTrainingContextProvider({children}: ChildrenProps) { - const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); - const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp; - const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); - const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); - const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const styles = useThemeStyles(); - const theme = useTheme(); - - // Track active tooltips - const [activeTooltips, setActiveTooltips] = useState>(new Set()); - - const unregisterTooltip = useCallback( - (elementName: ProductTrainingElementName) => { - setActiveTooltips((prev) => { - const next = new Set(prev); - next.delete(elementName); - return next; - }); - }, - [setActiveTooltips], - ); - - const determineVisibleTooltip = useCallback(() => { - if (activeTooltips.size === 0) { - return null; - } - - const sortedTooltips = Array.from(activeTooltips) - .map((name) => ({ - name, - priority: PRODUCT_TRAINING_TOOLTIP_DATA[name]?.priority ?? 0, - })) - .sort((a, b) => b.priority - a.priority); - - const highestPriorityTooltip = sortedTooltips.at(0); - - if (!highestPriorityTooltip) { - return null; - } - - return highestPriorityTooltip.name; - }, [activeTooltips]); - - const shouldTooltipBeVisible = useCallback( - (elementName: ProductTrainingElementName) => { - const isDismissed = !!dismissedProductTraining?.[elementName]; - - const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - - return tooltipConfig.shouldShow({ - isDismissed, - isOnboardingCompleted, - hasBeenAddedToNudgeMigration, - shouldUseNarrowLayout, - }); - }, - [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], - ); - - const registerTooltip = useCallback( - (elementName: ProductTrainingElementName) => { - const shouldRegister = shouldTooltipBeVisible(elementName); - if (!shouldRegister) { - return; - } - setActiveTooltips((prev) => new Set([...prev, elementName])); - }, - [shouldTooltipBeVisible], - ); - - const shouldRenderElement = useCallback( - (elementName: ProductTrainingElementName) => { - // First check base conditions - const shouldShow = shouldTooltipBeVisible(elementName); - if (!shouldShow) { - return false; - } - const visibleTooltip = determineVisibleTooltip(); - - // If this is the highest priority visible tooltip, show it - if (elementName === visibleTooltip) { - return true; - } - - return false; - }, - [shouldTooltipBeVisible, determineVisibleTooltip], - ); - - const renderProductTourElement = useCallback( - (elementName: ProductTrainingElementName) => { - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (!element) { - return null; - } - const processedContent = () => { - const content = convertToLTR(translate(element.content as TranslationPaths)); - - return content ? `${content}` : ''; - }; - return ( - - - - - - - ); - }, - [ - styles.alignItemsCenter, - styles.flexRow, - styles.flexWrap, - styles.gap1, - styles.justifyContentCenter, - styles.p2, - styles.renderHTMLTitle, - styles.textAlignCenter, - theme.tooltipHighlightText, - translate, - ], - ); - - const contextValue = useMemo( - () => ({ - renderProductTrainingElement: renderProductTourElement, - shouldRenderElement, - registerTooltip, - unregisterTooltip, - }), - [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], - ); - - return {children}; -} - -const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { - const context = useContext(ProductTrainingContext); - if (!context) { - throw new Error('useProductTourContext must be used within a ProductTourProvider'); - } - - const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; - - // Register this tooltip when the component mounts and unregister when it unmounts - useEffect(() => { - if (elementName) { - registerTooltip(elementName); - return () => { - unregisterTooltip(elementName); - }; - } - return undefined; - }, [elementName, registerTooltip, unregisterTooltip]); - - const shouldShowProductTrainingElement = useMemo(() => { - if (!elementName) { - return false; - } - return shouldRenderElement(elementName); - }, [elementName, shouldRenderElement]); - - const hideElement = useCallback(() => { - if (!elementName) { - return; - } - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (element?.onHideElement) { - element.onHideElement(); - } - unregisterTooltip(elementName); - }, [elementName, unregisterTooltip]); - - if (!elementName) { - return { - renderProductTourElement: () => null, - hideElement: () => {}, - shouldShowProductTrainingElement: false, - }; - } - - return { - renderProductTourElement: () => renderProductTrainingElement(elementName), - hideElement, - shouldShowProductTrainingElement, - }; -}; - -export {ProductTrainingContextProvider, useProductTrainingContext}; diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index cd0433da7353..9bf5db34fbb9 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -130,7 +130,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro selector: (parentReportActions) => getParentReportAction(parentReportActions, reportOnyx?.parentReportActionID ?? ''), }); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const [workspaceTooltip] = useOnyx(ONYXKEYS.NVP_WORKSPACE_TOOLTIP); const wasLoadingApp = usePrevious(isLoadingApp); const finishedLoadingApp = wasLoadingApp && !isLoadingApp; const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); @@ -848,7 +847,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro isComposerFullSize={!!isComposerFullSize} isEmptyChat={isEmptyChat} lastReportAction={lastReportAction} - workspaceTooltip={workspaceTooltip} /> ) : null} diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 59d1b4c00683..33b6f5b7e8f3 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -11,14 +11,12 @@ import type {FileObject} from '@components/AttachmentModal'; import AttachmentModal from '@components/AttachmentModal'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import ImportedStateIndicator from '@components/ImportedStateIndicator'; import type {Mention} from '@components/MentionSuggestions'; import OfflineIndicator from '@components/OfflineIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxProvider'; -import Text from '@components/Text'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebounce from '@hooks/useDebounce'; @@ -26,7 +24,6 @@ import useHandleExceedMaxCommentLength from '@hooks/useHandleExceedMaxCommentLen import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -114,7 +111,6 @@ function ReportActionCompose({ onComposerFocus, onComposerBlur, }: ReportActionComposeProps) { - const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -127,6 +123,10 @@ function ReportActionCompose({ const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); + const {renderProductTourElement, hideElement, shouldShowProductTrainingElement} = useProductTrainingContext( + shouldShowEducationalTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE : undefined, + ); + /** * Updates the Highlight state of the composer */ @@ -366,34 +366,6 @@ function ReportActionCompose({ return reportActionComposeHeight - emojiOffsetWithComposeBox - CONST.MENU_POSITION_REPORT_ACTION_COMPOSE_BOTTOM; }, [styles]); - const renderWorkspaceChatTooltip = useCallback( - () => ( - - - - {translate('reportActionCompose.tooltip.title')} - {translate('reportActionCompose.tooltip.subtitle')} - - - ), - [ - styles.alignItemsCenter, - styles.flexRow, - styles.justifyContentCenter, - styles.flexWrap, - styles.textAlignCenter, - styles.gap1, - styles.quickActionTooltipTitle, - styles.quickActionTooltipSubtitle, - theme.tooltipHighlightText, - translate, - ], - ); - const onValueChange = useCallback( (value: string) => { if (value.length === 0 && isComposerFullSize) { @@ -417,10 +389,10 @@ function ReportActionCompose({ contentContainerStyle={isComposerFullSize ? styles.flex1 : {}} > ; - /** Whether to show educational tooltip in workspace chat for first-time user */ - workspaceTooltip: OnyxEntry; - /** Whether the chat is empty */ isEmptyChat?: boolean; @@ -76,7 +73,6 @@ function ReportFooter({ isEmptyChat = true, isReportReadyForDisplay = true, isComposerFullSize = false, - workspaceTooltip, onComposerBlur, onComposerFocus, }: ReportFooterProps) { @@ -118,7 +114,7 @@ function ReportFooter({ const isSystemChat = ReportUtils.isSystemChat(report); const isAdminsOnlyPostingRoom = ReportUtils.isAdminsOnlyPostingRoom(report); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); - const shouldShowEducationalTooltip = !!workspaceTooltip?.shouldShow && !isUserPolicyAdmin; + const shouldShowEducationalTooltip = PolicyUtils.isPaidGroupPolicy(policy) && !isUserPolicyAdmin; const allPersonalDetails = usePersonalDetails(); @@ -249,7 +245,6 @@ export default memo( prevProps.isEmptyChat === nextProps.isEmptyChat && prevProps.lastReportAction === nextProps.lastReportAction && prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay && - prevProps.workspaceTooltip?.shouldShow === nextProps.workspaceTooltip?.shouldShow && lodashIsEqual(prevProps.reportMetadata, nextProps.reportMetadata) && lodashIsEqual(prevProps.policy?.employeeList, nextProps.policy?.employeeList) && lodashIsEqual(prevProps.policy?.role, nextProps.policy?.role), From 3fa81f23b96c67f2740e3e95b6ba9eb044c30caa Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 1 Dec 2024 00:51:49 +0530 Subject: [PATCH 04/28] tooltip for QAB --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 6 ++--- .../ProductTrainingContext/index.tsx | 3 +-- src/pages/home/report/ReportFooter.tsx | 2 +- .../FloatingActionButtonAndPopover.tsx | 25 ++++++++----------- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index 915c8c136e53..e30b78c73b8c 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -33,8 +33,8 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideElement: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { - if (isDismissed) { + shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { + if (isDismissed || shouldUseNarrowLayout) { return false; } @@ -46,7 +46,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [QUICK_ACTION_BUTTON]: { - content: '', + content: 'quickAction.tooltip.subtitle', onHideElement: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 0364af640142..9b827ced556b 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -139,7 +139,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return content ? `${content}` : ''; }; return ( - + ( - - {translate('quickAction.tooltip.title')} - {translate('quickAction.tooltip.subtitle')} - - ), - [styles.quickActionTooltipTitle, styles.quickActionTooltipSubtitle, translate], - ); - const quickActionTitle = useMemo(() => { if (isEmptyObject(quickActionReport)) { return ''; @@ -446,8 +440,9 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, tooltipShiftHorizontal: styles.popoverMenuItem.paddingHorizontal, tooltipShiftVertical: styles.popoverMenuItem.paddingVertical / 2, - renderTooltipContent: renderQuickActionTooltip, + renderTooltipContent: renderProductTourElement, tooltipWrapperStyle: styles.quickActionTooltipWrapper, + onHideTooltip: hideElement, }; if (quickAction?.action) { @@ -459,7 +454,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl description: !hideQABSubtitle ? ReportUtils.getReportName(quickActionReport) ?? translate('quickAction.updateDestination') : '', onSelected: () => interceptAnonymousUser(() => navigateToQuickAction()), shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport), - shouldRenderTooltip: quickAction.isFirstQuickAction, + shouldRenderTooltip: shouldShowProductTrainingElement, }, ]; } @@ -478,7 +473,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, true); }), shouldShowSubscriptRightAvatar: true, - shouldRenderTooltip: false, + shouldRenderTooltip: shouldShowProductTrainingElement, }, ]; } @@ -490,13 +485,13 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl styles.popoverMenuItem.paddingHorizontal, styles.popoverMenuItem.paddingVertical, styles.quickActionTooltipWrapper, - renderQuickActionTooltip, + renderProductTourElement, quickAction?.action, - quickAction?.isFirstQuickAction, policyChatForActivePolicy, quickActionTitle, hideQABSubtitle, quickActionReport, + shouldShowProductTrainingElement, navigateToQuickAction, selectOption, isValidReport, From cb8b8f628c8e6bf17958d0d3df728efc4021366a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sun, 1 Dec 2024 01:24:35 +0530 Subject: [PATCH 05/28] remove unused import --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 6400978aad29..1a5298672546 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -12,7 +12,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import PopoverMenu from '@components/PopoverMenu'; import {useProductTrainingContext} from '@components/ProductTrainingContext'; -import Text from '@components/Text'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; @@ -486,6 +485,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl styles.popoverMenuItem.paddingVertical, styles.quickActionTooltipWrapper, renderProductTourElement, + hideElement, quickAction?.action, policyChatForActivePolicy, quickActionTitle, From ee9a380128d7879a242865478d85c8ed56572876 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:05:04 +0530 Subject: [PATCH 06/28] format --- .../LHNOptionsList/OptionRowLHN.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index dfc80ef0e775..9defa10c04a8 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,17 +1,17 @@ -import { useFocusEffect } from '@react-navigation/native'; -import React, { useCallback, useRef, useState } from 'react'; -import type { GestureResponderEvent, ViewStyle } from 'react-native'; -import { StyleSheet, View } from 'react-native'; -import { useOnyx } from 'react-native-onyx'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useRef, useState} from 'react'; +import type {GestureResponderEvent, ViewStyle} from 'react-native'; +import {StyleSheet, View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import { useSession } from '@components/OnyxProvider'; +import {useSession} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import { useProductTrainingContext } from '@components/ProductTrainingContext'; +import {useProductTrainingContext} from '@components/ProductTrainingContext'; import SubscriptAvatar from '@components/SubscriptAvatar'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; @@ -34,16 +34,16 @@ import variables from '@styles/variables'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import { isEmptyObject } from '@src/types/utils/EmptyObject'; -import type { OptionRowLHNProps } from './types'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import type {OptionRowLHNProps} from './types'; -function OptionRowLHN({ reportID, isFocused = false, onSelectRow = () => { }, optionItem, viewMode = 'default', style, onLayout = () => { }, hasDraftComment }: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); const StyleUtils = useStyleUtils(); const [isScreenFocused, setIsScreenFocused] = useState(false); - const { shouldUseNarrowLayout } = useResponsiveLayout(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); @@ -53,10 +53,10 @@ function OptionRowLHN({ reportID, isFocused = false, onSelectRow = () => { }, op // Guides are assigned for the MANAGE_TEAM onboarding action, except for emails that have a '+'. const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+'); const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); - const tooltipToRender = (shouldShowGetStartedTooltip && isScreenFocused) ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; + const tooltipToRender = shouldShowGetStartedTooltip && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; - const { shouldShowProductTrainingElement, renderProductTourElement, hideElement } = useProductTrainingContext(tooltipToRender); - const { translate } = useLocalize(); + const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(tooltipToRender); + const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); useFocusEffect( @@ -125,7 +125,7 @@ function OptionRowLHN({ reportID, isFocused = false, onSelectRow = () => { }, op '-1', reportID, undefined, - () => { }, + () => {}, () => setIsContextMenuActive(false), false, false, From 128411e3d1a190a8bd93e372887ce29fcab02e6c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:28:31 +0530 Subject: [PATCH 07/28] clean variable namings --- .../LHNOptionsList/OptionRowLHN.tsx | 12 +- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 8 +- .../ProductTrainingContext/index.tsx | 104 +++++++++--------- src/pages/Search/SearchTypeMenu.tsx | 14 +-- .../ReportActionCompose.tsx | 8 +- .../FloatingActionButtonAndPopover.tsx | 16 +-- 6 files changed, 80 insertions(+), 82 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 9defa10c04a8..18306b4484d7 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -55,7 +55,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report); const tooltipToRender = shouldShowGetStartedTooltip && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; - const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(tooltipToRender); + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); @@ -152,15 +152,15 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti needsOffscreenAlphaCompositing > @@ -172,8 +172,8 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti onPress={(event) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); Timing.start(CONST.TIMING.OPEN_REPORT); - if (shouldShowProductTrainingElement) { - hideElement(); + if (shouldShowProductTrainingTooltip) { + hideProductTrainingTooltip(); } event?.preventDefault(); diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index e30b78c73b8c..c2f896233a48 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -13,7 +13,7 @@ type ShouldShowConditionProps = { const PRODUCT_TRAINING_TOOLTIP_DATA = { [CONCEIRGE_LHN_GBR]: { content: 'productTrainingTooltip.conciergeLHNGBR', - onHideElement: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), + onHideTooltip: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { @@ -30,7 +30,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [RENAME_SAVED_SEARCH]: { content: 'search.saveSearchTooltipText', - onHideElement: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), + onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { @@ -47,7 +47,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [QUICK_ACTION_BUTTON]: { content: 'quickAction.tooltip.subtitle', - onHideElement: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), + onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { @@ -64,7 +64,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [WORKSAPCE_CHAT_CREATE]: { content: 'reportActionCompose.tooltip.subtitle', - onHideElement: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), + onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 9b827ced556b..42cd1d8e19db 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -18,18 +18,18 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; -type ProductTrainingElementName = ValueOf; +type ProductTrainingTooltipName = ValueOf; type ProductTrainingContextType = { - shouldRenderElement: (elementName: ProductTrainingElementName) => boolean; - renderProductTrainingElement: (elementName: ProductTrainingElementName) => React.ReactNode | null; - registerTooltip: (elementName: ProductTrainingElementName) => void; - unregisterTooltip: (elementName: ProductTrainingElementName) => void; + shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; + renderProductTrainingTooltip: (tooltipName: ProductTrainingTooltipName) => React.ReactNode | null; + registerTooltip: (tooltipName: ProductTrainingTooltipName) => void; + unregisterTooltip: (tooltipName: ProductTrainingTooltipName) => void; }; const ProductTrainingContext = createContext({ - shouldRenderElement: () => false, - renderProductTrainingElement: () => null, + shouldRenderTooltip: () => false, + renderProductTrainingTooltip: () => null, registerTooltip: () => {}, unregisterTooltip: () => {}, }); @@ -47,13 +47,13 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const theme = useTheme(); // Track active tooltips - const [activeTooltips, setActiveTooltips] = useState>(new Set()); + const [activeTooltips, setActiveTooltips] = useState>(new Set()); const unregisterTooltip = useCallback( - (elementName: ProductTrainingElementName) => { + (tooltipName: ProductTrainingTooltipName) => { setActiveTooltips((prev) => { const next = new Set(prev); - next.delete(elementName); + next.delete(tooltipName); return next; }); }, @@ -82,10 +82,10 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { }, [activeTooltips]); const shouldTooltipBeVisible = useCallback( - (elementName: ProductTrainingElementName) => { - const isDismissed = !!dismissedProductTraining?.[elementName]; + (tooltipName: ProductTrainingTooltipName) => { + const isDismissed = !!dismissedProductTraining?.[tooltipName]; - const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; + const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; return tooltipConfig.shouldShow({ isDismissed, @@ -98,27 +98,27 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { ); const registerTooltip = useCallback( - (elementName: ProductTrainingElementName) => { - const shouldRegister = shouldTooltipBeVisible(elementName); + (tooltipName: ProductTrainingTooltipName) => { + const shouldRegister = shouldTooltipBeVisible(tooltipName); if (!shouldRegister) { return; } - setActiveTooltips((prev) => new Set([...prev, elementName])); + setActiveTooltips((prev) => new Set([...prev, tooltipName])); }, [shouldTooltipBeVisible], ); - const shouldRenderElement = useCallback( - (elementName: ProductTrainingElementName) => { + const shouldRenderTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { // First check base conditions - const shouldShow = shouldTooltipBeVisible(elementName); + const shouldShow = shouldTooltipBeVisible(tooltipName); if (!shouldShow) { return false; } const visibleTooltip = determineVisibleTooltip(); // If this is the highest priority visible tooltip, show it - if (elementName === visibleTooltip) { + if (tooltipName === visibleTooltip) { return true; } @@ -127,14 +127,14 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { [shouldTooltipBeVisible, determineVisibleTooltip], ); - const renderProductTourElement = useCallback( - (elementName: ProductTrainingElementName) => { - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (!element) { + const renderProductTrainingTooltip = useCallback( + (tooltipName: ProductTrainingTooltipName) => { + const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + if (!tooltip) { return null; } const processedContent = () => { - const content = convertToLTR(translate(element.content as TranslationPaths)); + const content = convertToLTR(translate(tooltip.content as TranslationPaths)); return content ? `${content}` : ''; }; @@ -166,66 +166,64 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const contextValue = useMemo( () => ({ - renderProductTrainingElement: renderProductTourElement, - shouldRenderElement, + renderProductTrainingTooltip, + shouldRenderTooltip, registerTooltip, unregisterTooltip, }), - [shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTourElement], + [shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip], ); return {children}; } -const useProductTrainingContext = (elementName?: ProductTrainingElementName) => { +const useProductTrainingContext = (tooltipName?: ProductTrainingTooltipName) => { const context = useContext(ProductTrainingContext); if (!context) { throw new Error('useProductTourContext must be used within a ProductTourProvider'); } - const {shouldRenderElement, registerTooltip, unregisterTooltip, renderProductTrainingElement} = context; + const {shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip} = context; // Register this tooltip when the component mounts and unregister when it unmounts useEffect(() => { - if (elementName) { - registerTooltip(elementName); + if (tooltipName) { + registerTooltip(tooltipName); return () => { - unregisterTooltip(elementName); + unregisterTooltip(tooltipName); }; } return undefined; - }, [elementName, registerTooltip, unregisterTooltip]); + }, [tooltipName, registerTooltip, unregisterTooltip]); - const shouldShowProductTrainingElement = useMemo(() => { - if (!elementName) { + const shouldShowProductTrainingTooltip = useMemo(() => { + if (!tooltipName) { return false; } - return shouldRenderElement(elementName); - }, [elementName, shouldRenderElement]); + return shouldRenderTooltip(tooltipName); + }, [tooltipName, shouldRenderTooltip]); - const hideElement = useCallback(() => { - if (!elementName) { + const hideProductTrainingTooltip = useCallback(() => { + if (!tooltipName) { return; } - const element = PRODUCT_TRAINING_TOOLTIP_DATA[elementName]; - if (element?.onHideElement) { - element.onHideElement(); - } - unregisterTooltip(elementName); - }, [elementName, unregisterTooltip]); + const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + tooltip.onHideTooltip(); + unregisterTooltip(tooltipName); + }, [tooltipName, unregisterTooltip]); - if (!elementName) { + if (!tooltipName) { return { - renderProductTourElement: () => null, - hideElement: () => {}, - shouldShowProductTrainingElement: false, + renderProductTrainingTooltip: () => null, + hideProductTrainingTooltip: () => {}, + shouldShowProductTrainingTooltip: false, }; } return { - renderProductTourElement: () => renderProductTrainingElement(elementName), - hideElement, - shouldShowProductTrainingElement, + renderProductTrainingTooltip: () => renderProductTrainingTooltip(tooltipName), + hideProductTrainingTooltip, + shouldShowProductTrainingTooltip, }; }; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index bd42c3dc8ab6..4a3c0881b5a6 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -63,7 +63,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { const {singleExecution} = useSingleExecution(); const {translate} = useLocalize(); const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES); - const {shouldShowProductTrainingElement, renderProductTourElement, hideElement} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH); + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH); const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); const [session] = useOnyx(ONYXKEYS.SESSION); @@ -153,7 +153,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { if (!isNarrow) { return { ...baseMenuItem, - shouldRenderTooltip: index === 0 && shouldShowProductTrainingElement, + shouldRenderTooltip: index === 0 && shouldShowProductTrainingTooltip, tooltipAnchorAlignment: { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, @@ -161,8 +161,8 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { tooltipShiftHorizontal: -32, tooltipShiftVertical: 15, tooltipWrapperStyle: [styles.bgPaleGreen, styles.mh4, styles.pv2], - onHideTooltip: hideElement, - renderTooltipContent: renderProductTourElement, + onHideTooltip: hideProductTrainingTooltip, + renderTooltipContent: renderProductTrainingTooltip, }; } return baseMenuItem; @@ -177,9 +177,9 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { personalDetails, reports, taxRates, - shouldShowProductTrainingElement, - hideElement, - renderProductTourElement, + shouldShowProductTrainingTooltip, + hideProductTrainingTooltip, + renderProductTrainingTooltip, ], ); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 33b6f5b7e8f3..ec5bd556cd72 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -123,7 +123,7 @@ function ReportActionCompose({ const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); - const {renderProductTourElement, hideElement, shouldShowProductTrainingElement} = useProductTrainingContext( + const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext( shouldShowEducationalTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE : undefined, ); @@ -389,10 +389,10 @@ function ReportActionCompose({ contentContainerStyle={isComposerFullSize ? styles.flex1 : {}} > interceptAnonymousUser(() => navigateToQuickAction()), shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport), - shouldRenderTooltip: shouldShowProductTrainingElement, + shouldRenderTooltip: shouldShowProductTrainingTooltip, }, ]; } @@ -470,7 +470,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, true); }), shouldShowSubscriptRightAvatar: true, - shouldRenderTooltip: shouldShowProductTrainingElement, + shouldRenderTooltip: shouldShowProductTrainingTooltip, }, ]; } @@ -482,14 +482,14 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl styles.popoverMenuItem.paddingHorizontal, styles.popoverMenuItem.paddingVertical, styles.quickActionTooltipWrapper, - renderProductTourElement, - hideElement, + renderProductTrainingTooltip, + hideProductTrainingTooltip, quickAction?.action, policyChatForActivePolicy, quickActionTitle, hideQABSubtitle, quickActionReport, - shouldShowProductTrainingElement, + shouldShowProductTrainingTooltip, navigateToQuickAction, selectOption, isValidReport, From 1df261f35d18a9ae8ed34af6fb7331a027a9efec Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:30:16 +0530 Subject: [PATCH 08/28] remove unneccessary comment --- src/components/ProductTrainingContext/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 42cd1d8e19db..2df640e44bf6 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -46,7 +46,6 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const styles = useThemeStyles(); const theme = useTheme(); - // Track active tooltips const [activeTooltips, setActiveTooltips] = useState>(new Set()); const unregisterTooltip = useCallback( From 01226982948c7b719c6e2406f56deaaadc7b5aca Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 02:52:05 +0530 Subject: [PATCH 09/28] fix translations --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 6 +++--- src/languages/en.ts | 12 +++--------- src/languages/es.ts | 15 ++++++--------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index c2f896233a48..d2c8acb28e9e 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -29,7 +29,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [RENAME_SAVED_SEARCH]: { - content: 'search.saveSearchTooltipText', + content: 'productTrainingTooltip.saveSearchTooltipText', onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, @@ -46,7 +46,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [QUICK_ACTION_BUTTON]: { - content: 'quickAction.tooltip.subtitle', + content: 'productTrainingTooltip.quickActionButton', onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, @@ -63,7 +63,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, }, [WORKSAPCE_CHAT_CREATE]: { - content: 'reportActionCompose.tooltip.subtitle', + content: 'productTrainingTooltip.workspaceChatCreate', onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, diff --git a/src/languages/en.ts b/src/languages/en.ts index 18b624c09041..8a92f0f55e71 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -645,10 +645,6 @@ const translations = { emoji: 'Emoji', collapse: 'Collapse', expand: 'Expand', - tooltip: { - title: 'Get started!', - subtitle: ' Submit your first expense', - }, }, reportActionContextMenu: { copyToClipboard: 'Copy to clipboard', @@ -834,10 +830,6 @@ const translations = { trackDistance: 'Track distance', noLongerHaveReportAccess: 'You no longer have access to your previous quick action destination. Pick a new one below.', updateDestination: 'Update destination', - tooltip: { - title: 'Quick action! ', - subtitle: 'Just a tap away.', - }, }, iou: { amount: 'Amount', @@ -4542,7 +4534,6 @@ const translations = { }, }, saveSearch: 'Save search', - saveSearchTooltipText: 'You can rename your saved search', deleteSavedSearch: 'Delete saved search', deleteSavedSearchConfirm: 'Are you sure you want to delete this search?', searchName: 'Search name', @@ -5439,6 +5430,9 @@ const translations = { }, productTrainingTooltip: { conciergeLHNGBR: 'Get started here!', + saveSearchTooltipText: 'You can rename your saved search here!', + quickActionButton: 'Quick action! Just a tap away', + workspaceChatCreate: 'Get started! Submit your expenses here!', }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 0d3426d21eda..06e8d6426bbd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -637,10 +637,6 @@ const translations = { emoji: 'Emoji', collapse: 'Colapsar', expand: 'Expandir', - tooltip: { - title: '¡Empecemos!', - subtitle: ' Presenta tu primer gasto', - }, }, reportActionContextMenu: { copyToClipboard: 'Copiar al portapapeles', @@ -829,10 +825,6 @@ const translations = { trackDistance: 'Crear gasto por desplazamiento', noLongerHaveReportAccess: 'Ya no tienes acceso al destino previo de esta acción rápida. Escoge uno nuevo a continuación.', updateDestination: 'Actualiza el destino', - tooltip: { - title: '¡Acción rápida! ', - subtitle: 'A un click.', - }, }, iou: { amount: 'Importe', @@ -4591,7 +4583,6 @@ const translations = { }, }, saveSearch: 'Guardar búsqueda', - saveSearchTooltipText: 'Puedes cambiar el nombre de tu búsqueda guardada', savedSearchesMenuItemTitle: 'Guardadas', searchName: 'Nombre de la búsqueda', deleteSavedSearch: 'Eliminar búsqueda guardada', @@ -5957,6 +5948,12 @@ const translations = { crossPlatform: 'Do everything from your phone or browser', }, }, + productTrainingTooltip: { + conciergeLHNGBR: 'Get started here!', + saveSearchTooltipText: 'You can rename your saved search here!', + quickActionButton: 'Quick action! Just a tap away', + workspaceChatCreate: 'Get started! Submit your expenses here!', + }, }; export default translations satisfies TranslationDeepObject; From d1b92d3d35c937e143df12934ef1be02436c9f32 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 5 Dec 2024 23:55:18 +0530 Subject: [PATCH 10/28] refactor ProductTrainingContextProvider styles and dependencies --- src/components/ProductTrainingContext/index.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 2df640e44bf6..49b3c975c4d2 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -138,7 +138,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return content ? `${content}` : ''; }; return ( - + ); }, - [ - styles.alignItemsCenter, - styles.flexRow, - styles.flexWrap, - styles.gap1, - styles.justifyContentCenter, - styles.renderHTMLTitle, - styles.textAlignCenter, - theme.tooltipHighlightText, - translate, - ], + [styles, theme.tooltipHighlightText, translate], ); const contextValue = useMemo( From 9684fdafd43ce4f6bf268fe230f3316d9a3829b7 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 6 Dec 2024 02:25:15 +0530 Subject: [PATCH 11/28] refactor tooltip visibility logic by removing unnecessary conditions --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 47 +++---------------- .../ProductTrainingContext/index.tsx | 10 ++-- 2 files changed, 13 insertions(+), 44 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index d2c8acb28e9e..6aecda294c8a 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -4,9 +4,6 @@ import CONST from '@src/CONST'; const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; type ShouldShowConditionProps = { - isDismissed: boolean; - isOnboardingCompleted: boolean; - hasBeenAddedToNudgeMigration: boolean; shouldUseNarrowLayout: boolean; }; @@ -16,16 +13,8 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { - if (isDismissed || !shouldUseNarrowLayout) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - - return true; + shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { + return shouldUseNarrowLayout; }, }, [RENAME_SAVED_SEARCH]: { @@ -33,16 +22,8 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration, shouldUseNarrowLayout}: ShouldShowConditionProps) => { - if (isDismissed || shouldUseNarrowLayout) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - - return true; + shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { + return !shouldUseNarrowLayout; }, }, [QUICK_ACTION_BUTTON]: { @@ -50,15 +31,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { - if (isDismissed) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - + shouldShow: () => { return true; }, }, @@ -67,15 +40,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, - shouldShow: ({isDismissed, isOnboardingCompleted, hasBeenAddedToNudgeMigration}: ShouldShowConditionProps) => { - if (isDismissed) { - return false; - } - - if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { - return false; - } - + shouldShow: () => { return true; }, }, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 49b3c975c4d2..6d254a17d6da 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -84,12 +84,16 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { (tooltipName: ProductTrainingTooltipName) => { const isDismissed = !!dismissedProductTraining?.[tooltipName]; + if (isDismissed) { + return false; + } const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + if (!isOnboardingCompleted && !hasBeenAddedToNudgeMigration) { + return false; + } + return tooltipConfig.shouldShow({ - isDismissed, - isOnboardingCompleted, - hasBeenAddedToNudgeMigration, shouldUseNarrowLayout, }); }, From 0451ea15958c8a5f46a1c758333c6d16604ee3b2 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 6 Dec 2024 02:26:02 +0530 Subject: [PATCH 12/28] depreaciate saved search ONYXKEY --- src/ONYXKEYS.ts | 4 ---- src/libs/actions/Search.ts | 10 ---------- src/pages/Search/AdvancedSearchFilters.tsx | 5 ----- 3 files changed, 19 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 2de7ff5f1a0a..b582f5ffad6e 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -222,9 +222,6 @@ const ONYXKEYS = { /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', - /** Whether to show save search rename tooltip */ - SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP: 'shouldShowSavedSearchRenameTooltip', - /** Whether to hide gbr tooltip */ NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip', @@ -1027,7 +1024,6 @@ type OnyxValuesMapping = { [ONYXKEYS.LAST_ROUTE]: string; [ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined; [ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean; - [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; [ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record; [ONYXKEYS.CONCIERGE_REPORT_ID]: string; [ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING]: OnyxTypes.DismissedProductTraining; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index bb64fe10db26..ec4688cbc080 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -351,14 +351,6 @@ function clearAdvancedFilters() { Onyx.merge(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, values); } -function showSavedSearchRenameTooltip() { - Onyx.set(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP, true); -} - -function dismissSavedSearchRenameTooltip() { - Onyx.set(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP, false); -} - export { saveSearch, search, @@ -371,8 +363,6 @@ export { clearAllFilters, clearAdvancedFilters, deleteSavedSearch, - dismissSavedSearchRenameTooltip, - showSavedSearchRenameTooltip, payMoneyRequestOnSearch, approveMoneyRequestOnSearch, handleActionButtonPress, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 60c01e6f75f0..c4f58133be7d 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -339,11 +339,6 @@ function AdvancedSearchFilters() { return; } - // We only want to show the tooltip once, the NVP will not be set if the user has not saved a search yet - if (!savedSearches) { - SearchActions.showSavedSearchRenameTooltip(); - } - SearchActions.saveSearch({ queryJSON, }); From 5fe13b32b8d97997f45cc404698e73b9c3b0530c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 6 Dec 2024 02:35:58 +0530 Subject: [PATCH 13/28] remove unused onyx keys --- src/ONYXKEYS.ts | 8 -------- src/libs/actions/User.ts | 10 ---------- src/types/onyx/WorkspaceTooltip.ts | 9 --------- src/types/onyx/index.ts | 2 -- 4 files changed, 29 deletions(-) delete mode 100644 src/types/onyx/WorkspaceTooltip.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b582f5ffad6e..1a8bbe057312 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -216,15 +216,9 @@ const ONYXKEYS = { /** The end date (epoch timestamp) of the workspace owner’s grace period after the free trial ends. */ NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END: 'nvp_private_billingGracePeriodEnd', - /** The NVP containing all information related to educational tooltip in workspace chat */ - NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip', - /** The NVP containing the target url to navigate to when deleting a transaction */ NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL: 'nvp_deleteTransactionNavigateBackURL', - /** Whether to hide gbr tooltip */ - NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip', - /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', @@ -1014,9 +1008,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_BILLING_FUND_ID]: number; [ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED]: number; [ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END]: number; - [ONYXKEYS.NVP_WORKSPACE_TOOLTIP]: OnyxTypes.WorkspaceTooltip; [ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL]: string | undefined; - [ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP]: boolean; [ONYXKEYS.NVP_PRIVATE_CANCELLATION_DETAILS]: OnyxTypes.CancellationDetails[]; [ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE]: string; [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 20bca969468a..8f97ef22bb31 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -1361,14 +1361,6 @@ function dismissTrackTrainingModal() { }); } -function dismissWorkspaceTooltip() { - Onyx.merge(ONYXKEYS.NVP_WORKSPACE_TOOLTIP, {shouldShow: false}); -} - -function dismissGBRTooltip() { - Onyx.merge(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, true); -} - function requestRefund() { API.write(WRITE_COMMANDS.REQUEST_REFUND, null); } @@ -1389,7 +1381,6 @@ export { closeAccount, dismissReferralBanner, dismissTrackTrainingModal, - dismissWorkspaceTooltip, resendValidateCode, requestContactMethodValidateCode, updateNewsletterSubscription, @@ -1423,6 +1414,5 @@ export { addPendingContactMethod, clearValidateCodeActionError, subscribeToActiveGuides, - dismissGBRTooltip, setIsDebugModeEnabled, }; diff --git a/src/types/onyx/WorkspaceTooltip.ts b/src/types/onyx/WorkspaceTooltip.ts deleted file mode 100644 index 4371ac6533d8..000000000000 --- a/src/types/onyx/WorkspaceTooltip.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * The NVP containing all information related to educational tooltip in workspace chat. - */ -type WorkspaceTooltip = { - /** Should show educational tooltip in workspace chat for first-time user */ - shouldShow: boolean; -}; - -export default WorkspaceTooltip; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 20b7d047a092..eeda322f6205 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -110,7 +110,6 @@ import type WalletOnfido from './WalletOnfido'; import type WalletStatement from './WalletStatement'; import type WalletTerms from './WalletTerms'; import type WalletTransfer from './WalletTransfer'; -import type WorkspaceTooltip from './WorkspaceTooltip'; export type { TryNewDot, @@ -234,7 +233,6 @@ export type { CancellationDetails, ApprovalWorkflowOnyx, MobileSelectionMode, - WorkspaceTooltip, CardFeeds, SaveSearch, RecentSearchItem, From 3c2ed79b0e91985d94a11b4ac8b9039f78c57677 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 9 Dec 2024 01:18:31 +0530 Subject: [PATCH 14/28] resolve conflcts --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index 6aecda294c8a..e29675580a76 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -1,4 +1,4 @@ -import {dismissProductTrainingElement} from '@libs/actions/Welcome'; +import {dismissProductTraining} from '@libs/actions/Welcome'; import CONST from '@src/CONST'; const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; @@ -10,7 +10,7 @@ type ShouldShowConditionProps = { const PRODUCT_TRAINING_TOOLTIP_DATA = { [CONCEIRGE_LHN_GBR]: { content: 'productTrainingTooltip.conciergeLHNGBR', - onHideTooltip: () => dismissProductTrainingElement(CONCEIRGE_LHN_GBR), + onHideTooltip: () => dismissProductTraining(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { @@ -19,7 +19,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [RENAME_SAVED_SEARCH]: { content: 'productTrainingTooltip.saveSearchTooltipText', - onHideTooltip: () => dismissProductTrainingElement(RENAME_SAVED_SEARCH), + onHideTooltip: () => dismissProductTraining(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { @@ -28,7 +28,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [QUICK_ACTION_BUTTON]: { content: 'productTrainingTooltip.quickActionButton', - onHideTooltip: () => dismissProductTrainingElement(QUICK_ACTION_BUTTON), + onHideTooltip: () => dismissProductTraining(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, shouldShow: () => { @@ -37,7 +37,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { }, [WORKSAPCE_CHAT_CREATE]: { content: 'productTrainingTooltip.workspaceChatCreate', - onHideTooltip: () => dismissProductTrainingElement(WORKSAPCE_CHAT_CREATE), + onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, shouldShow: () => { From 903e30d34ab8c24c38a91b3976f1942e8c86e12d Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 9 Dec 2024 01:46:55 +0530 Subject: [PATCH 15/28] Remove unused NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER key and related logic --- src/ONYXKEYS.ts | 4 ---- src/components/LHNOptionsList/OptionRowLHN.tsx | 6 +----- src/components/ProductTrainingContext/index.tsx | 1 - src/libs/migrations/NVPMigration.ts | 1 - 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1ed9ec4821c6..2e65b5f372b4 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -111,9 +111,6 @@ const ONYXKEYS = { /** NVP keys */ - /** Boolean flag only true when first set */ - NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'nvp_isFirstTimeNewExpensifyUser', - /** This NVP contains list of at most 5 recent attendees */ NVP_RECENT_ATTENDEES: 'nvp_expensify_recentAttendees', @@ -867,7 +864,6 @@ type OnyxCollectionValuesMapping = { type OnyxValuesMapping = { [ONYXKEYS.ACCOUNT]: OnyxTypes.Account; [ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string; - [ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER]: boolean; // NVP_ONBOARDING is an array for old users. [ONYXKEYS.NVP_ONBOARDING]: Onboarding | []; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index dfdb35b356d1..379dea352c6b 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -47,8 +47,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); - const [isFirstTimeNewExpensifyUser] = useOnyx(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER); - const shouldShowGetStartedTooltip = isFirstTimeNewExpensifyUser && ReportUtils.isConciergeChatReport(report); + const shouldShowGetStartedTooltip = ReportUtils.isConciergeChatReport(report); const tooltipToRender = shouldShowGetStartedTooltip && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender); const {translate} = useLocalize(); @@ -167,9 +166,6 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti onPress={(event) => { Performance.markStart(CONST.TIMING.OPEN_REPORT); Timing.start(CONST.TIMING.OPEN_REPORT); - if (shouldShowProductTrainingTooltip) { - hideProductTrainingTooltip(); - } event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 6d254a17d6da..b263605dec39 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -178,7 +178,6 @@ const useProductTrainingContext = (tooltipName?: ProductTrainingTooltipName) => const {shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip} = context; - // Register this tooltip when the component mounts and unregister when it unmounts useEffect(() => { if (tooltipName) { registerTooltip(tooltipName); diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts index 20f3d0a86495..8c743e66e79f 100644 --- a/src/libs/migrations/NVPMigration.ts +++ b/src/libs/migrations/NVPMigration.ts @@ -9,7 +9,6 @@ import type {OnyxKey} from '@src/ONYXKEYS'; const migrations = { // eslint-disable-next-line @typescript-eslint/naming-convention nvp_lastPaymentMethod: ONYXKEYS.NVP_LAST_PAYMENT_METHOD, - isFirstTimeNewExpensifyUser: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, preferredLocale: ONYXKEYS.NVP_PREFERRED_LOCALE, preferredEmojiSkinTone: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, frequentlyUsedEmojis: ONYXKEYS.FREQUENTLY_USED_EMOJIS, From a8c9a611737ed5fb177a5034e89043672e6d19b3 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 9 Dec 2024 03:18:51 +0530 Subject: [PATCH 16/28] Refactor product training tooltip logic to improve registration and visibility conditions --- src/components/LHNOptionsList/OptionRowLHN.tsx | 8 +++++--- src/components/ProductTrainingContext/index.tsx | 12 +++--------- .../ReportActionCompose/ReportActionCompose.tsx | 3 ++- .../SidebarScreen/FloatingActionButtonAndPopover.tsx | 8 +++++--- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 379dea352c6b..650556f874a0 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -47,9 +47,11 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); - const shouldShowGetStartedTooltip = ReportUtils.isConciergeChatReport(report); - const tooltipToRender = shouldShowGetStartedTooltip && isScreenFocused ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : undefined; - const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender); + const shouldShowGetStartedTooltip = ReportUtils.isConciergeChatReport(report) && isScreenFocused; + const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR, + shouldShowGetStartedTooltip, + ); const {translate} = useLocalize(); const [isContextMenuActive, setIsContextMenuActive] = useState(false); diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index b263605dec39..811777de384d 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -170,7 +170,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return {children}; } -const useProductTrainingContext = (tooltipName?: ProductTrainingTooltipName) => { +const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shouldRegister = true) => { const context = useContext(ProductTrainingContext); if (!context) { throw new Error('useProductTourContext must be used within a ProductTourProvider'); @@ -179,26 +179,20 @@ const useProductTrainingContext = (tooltipName?: ProductTrainingTooltipName) => const {shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip} = context; useEffect(() => { - if (tooltipName) { + if (shouldRegister) { registerTooltip(tooltipName); return () => { unregisterTooltip(tooltipName); }; } return undefined; - }, [tooltipName, registerTooltip, unregisterTooltip]); + }, [tooltipName, registerTooltip, unregisterTooltip, shouldRegister]); const shouldShowProductTrainingTooltip = useMemo(() => { - if (!tooltipName) { - return false; - } return shouldRenderTooltip(tooltipName); }, [tooltipName, shouldRenderTooltip]); const hideProductTrainingTooltip = useCallback(() => { - if (!tooltipName) { - return; - } const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; tooltip.onHideTooltip(); unregisterTooltip(tooltipName); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 5a05a2ae0083..4b3e2f9d1937 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -124,7 +124,8 @@ function ReportActionCompose({ const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext( - shouldShowEducationalTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE : undefined, + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE, + shouldShowEducationalTooltip, ); /** diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index a9ca9848a991..f64a2ab4ba80 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -187,7 +187,10 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl selector: hasSeenTourSelector, }); - const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON); + const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext( + CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON, + isCreateMenuActive, + ); /** * There are scenarios where users who have not yet had their group workspace-chats in NewDot (isPolicyExpenseChatEnabled). In those scenarios, things can get confusing if they try to submit/track expenses. To address this, we block them from Creating, Tracking, Submitting expenses from NewDot if they are: @@ -440,6 +443,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl renderTooltipContent: renderProductTrainingTooltip, tooltipWrapperStyle: styles.quickActionTooltipWrapper, onHideTooltip: hideProductTrainingTooltip, + shouldRenderTooltip: shouldShowProductTrainingTooltip, }; if (quickAction?.action) { @@ -451,7 +455,6 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl description: !hideQABSubtitle ? ReportUtils.getReportName(quickActionReport) ?? translate('quickAction.updateDestination') : '', onSelected: () => interceptAnonymousUser(() => navigateToQuickAction()), shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport), - shouldRenderTooltip: shouldShowProductTrainingTooltip, }, ]; } @@ -470,7 +473,6 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl }, true); }), shouldShowSubscriptRightAvatar: true, - shouldRenderTooltip: shouldShowProductTrainingTooltip, }, ]; } From 2600133360f27e4b49c481e6039627e95f7a120b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 9 Dec 2024 18:59:39 +0530 Subject: [PATCH 17/28] Refactor useProductTrainingContext to improve tooltip visibility logic --- src/components/ProductTrainingContext/index.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 811777de384d..fa49c8c61980 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -150,6 +150,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { /> + {/* {processedContent()} */} ); @@ -170,7 +171,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { return {children}; } -const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shouldRegister = true) => { +const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shouldShow = true) => { const context = useContext(ProductTrainingContext); if (!context) { throw new Error('useProductTourContext must be used within a ProductTourProvider'); @@ -179,18 +180,18 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou const {shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip} = context; useEffect(() => { - if (shouldRegister) { + if (shouldShow) { registerTooltip(tooltipName); return () => { unregisterTooltip(tooltipName); }; } return undefined; - }, [tooltipName, registerTooltip, unregisterTooltip, shouldRegister]); + }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]); const shouldShowProductTrainingTooltip = useMemo(() => { - return shouldRenderTooltip(tooltipName); - }, [tooltipName, shouldRenderTooltip]); + return shouldShow && shouldRenderTooltip(tooltipName); + }, [shouldShow, shouldRenderTooltip, tooltipName]); const hideProductTrainingTooltip = useCallback(() => { const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; @@ -198,7 +199,7 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou unregisterTooltip(tooltipName); }, [tooltipName, unregisterTooltip]); - if (!tooltipName) { + if (!shouldShow) { return { renderProductTrainingTooltip: () => null, hideProductTrainingTooltip: () => {}, From 0c573ab591fdf65c9c6a63bf4c03bf0f20f355a5 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 9 Dec 2024 21:44:00 +0530 Subject: [PATCH 18/28] Refactor ProductTrainingContext to enhance tooltip rendering and update workspace chat translation --- src/components/ProductTrainingContext/index.tsx | 15 +++++++-------- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index fa49c8c61980..df44a401de82 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -2,10 +2,10 @@ import React, {createContext, useCallback, useContext, useEffect, useMemo, useSt import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import HTMLEngineProvider from '@components/HTMLEngineProvider'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import RenderHTML from '@components/RenderHTML'; -import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; @@ -133,14 +133,12 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const renderProductTrainingTooltip = useCallback( (tooltipName: ProductTrainingTooltipName) => { const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; - if (!tooltip) { - return null; - } const processedContent = () => { const content = convertToLTR(translate(tooltip.content as TranslationPaths)); return content ? `${content}` : ''; }; + return ( - - - {/* {processedContent()} */} - + + + + + ); }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 125576801bd0..39f37ebee1b1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5444,7 +5444,7 @@ const translations = { conciergeLHNGBR: 'Get started here!', saveSearchTooltipText: 'You can rename your saved search here!', quickActionButton: 'Quick action! Just a tap away', - workspaceChatCreate: 'Get started! Submit your expenses here!', + workspaceChatCreate: 'Submit your expenses here!', }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index ec2182e747ac..cc88fea0df85 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5964,7 +5964,7 @@ const translations = { conciergeLHNGBR: 'Get started here!', saveSearchTooltipText: 'You can rename your saved search here!', quickActionButton: 'Quick action! Just a tap away', - workspaceChatCreate: 'Get started! Submit your expenses here!', + workspaceChatCreate: 'Submit your expenses here!', }, }; From dbdf43a2d4129d0024e4ee30f1bf96abeea77c46 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 9 Dec 2024 22:43:05 +0530 Subject: [PATCH 19/28] improve tooltip visibility conditions --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index f64a2ab4ba80..03a4c1ad529a 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -189,7 +189,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext( CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON, - isCreateMenuActive, + isCreateMenuActive && (!shouldUseNarrowLayout || isFocused), ); /** From a3ea05c304f58132b44f5b4b5f5c3735044d51dc Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 10 Dec 2024 23:10:02 +0530 Subject: [PATCH 20/28] fix typing as per review --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 30 ++++++++++++------- .../ProductTrainingContext/index.tsx | 5 +--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index e29675580a76..e00c94979ba4 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -1,20 +1,31 @@ +import type {ValueOf} from 'type-fest'; import {dismissProductTraining} from '@libs/actions/Welcome'; import CONST from '@src/CONST'; const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; +type ProductTrainingTooltipName = ValueOf; + type ShouldShowConditionProps = { - shouldUseNarrowLayout: boolean; + shouldUseNarrowLayout?: boolean; +}; + +type TooltipData = { + content: string; + onHideTooltip: () => void; + name: ProductTrainingTooltipName; + priority: number; + shouldShow: (props: ShouldShowConditionProps) => boolean; }; -const PRODUCT_TRAINING_TOOLTIP_DATA = { +const PRODUCT_TRAINING_TOOLTIP_DATA: Record = { [CONCEIRGE_LHN_GBR]: { content: 'productTrainingTooltip.conciergeLHNGBR', onHideTooltip: () => dismissProductTraining(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, - shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { - return shouldUseNarrowLayout; + shouldShow: ({shouldUseNarrowLayout}) => { + return !!shouldUseNarrowLayout; }, }, [RENAME_SAVED_SEARCH]: { @@ -22,7 +33,7 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTraining(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, - shouldShow: ({shouldUseNarrowLayout}: ShouldShowConditionProps) => { + shouldShow: ({shouldUseNarrowLayout}) => { return !shouldUseNarrowLayout; }, }, @@ -31,19 +42,16 @@ const PRODUCT_TRAINING_TOOLTIP_DATA = { onHideTooltip: () => dismissProductTraining(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, - shouldShow: () => { - return true; - }, + shouldShow: () => true, }, [WORKSAPCE_CHAT_CREATE]: { content: 'productTrainingTooltip.workspaceChatCreate', onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, - shouldShow: () => { - return true; - }, + shouldShow: () => true, }, }; export default PRODUCT_TRAINING_TOOLTIP_DATA; +export type {ProductTrainingTooltipName}; diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index df44a401de82..98101153d672 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -1,7 +1,6 @@ import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; import HTMLEngineProvider from '@components/HTMLEngineProvider'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -12,14 +11,12 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import convertToLTR from '@libs/convertToLTR'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; -import type CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type {ProductTrainingTooltipName} from './PRODUCT_TRAINING_TOOLTIP_DATA'; import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; -type ProductTrainingTooltipName = ValueOf; - type ProductTrainingContextType = { shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; renderProductTrainingTooltip: (tooltipName: ProductTrainingTooltipName) => React.ReactNode | null; From f7696fa6391f4934ddfe6daa72f7c25691947906 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 10 Dec 2024 23:24:06 +0530 Subject: [PATCH 21/28] refactor: simplify shouldShow logic in PRODUCT_TRAINING_TOOLTIP_DATA --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index e00c94979ba4..a5b5e5973369 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -24,18 +24,14 @@ const PRODUCT_TRAINING_TOOLTIP_DATA: Record dismissProductTraining(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, - shouldShow: ({shouldUseNarrowLayout}) => { - return !!shouldUseNarrowLayout; - }, + shouldShow: ({shouldUseNarrowLayout}) => !!shouldUseNarrowLayout, }, [RENAME_SAVED_SEARCH]: { content: 'productTrainingTooltip.saveSearchTooltipText', onHideTooltip: () => dismissProductTraining(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, - shouldShow: ({shouldUseNarrowLayout}) => { - return !shouldUseNarrowLayout; - }, + shouldShow: ({shouldUseNarrowLayout}) => !shouldUseNarrowLayout, }, [QUICK_ACTION_BUTTON]: { content: 'productTrainingTooltip.quickActionButton', From 0eab9dd8d28e11d8ca8e1a707e37e923a15c4e97 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 10 Dec 2024 23:54:45 +0530 Subject: [PATCH 22/28] fix: update shouldShowEducationalTooltip logic to use ReportUtils for policy expense chat --- src/pages/home/report/ReportFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index a8425970abf1..c30830a4d47f 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -114,7 +114,7 @@ function ReportFooter({ const isSystemChat = ReportUtils.isSystemChat(report); const isAdminsOnlyPostingRoom = ReportUtils.isAdminsOnlyPostingRoom(report); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); - const shouldShowEducationalTooltip = PolicyUtils.isPaidGroupPolicy(policy) && !!report.isOwnPolicyExpenseChat && !isUserPolicyAdmin; + const shouldShowEducationalTooltip = ReportUtils.isPolicyExpenseChat(report) && !!report.isOwnPolicyExpenseChat && !isUserPolicyAdmin; const allPersonalDetails = usePersonalDetails(); From f9ec91d54dfe78c9d22474f14eeb7c79ad2bfb4b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Wed, 11 Dec 2024 00:45:30 +0530 Subject: [PATCH 23/28] fix: update productTrainingTooltip translations for improved clarity in English and Spanish --- src/languages/en.ts | 2 +- src/languages/es.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 59a66770af4f..a270abf12149 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5443,7 +5443,7 @@ const translations = { }, productTrainingTooltip: { conciergeLHNGBR: 'Get started here!', - saveSearchTooltipText: 'You can rename your saved search here!', + saveSearchTooltipText: 'Rename your saved searches here!', quickActionButton: 'Quick action! Just a tap away', workspaceChatCreate: 'Submit your expenses here!', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index d77e5e43b8c8..0a331e637e04 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5962,10 +5962,10 @@ const translations = { }, }, productTrainingTooltip: { - conciergeLHNGBR: 'Get started here!', - saveSearchTooltipText: 'You can rename your saved search here!', - quickActionButton: 'Quick action! Just a tap away', - workspaceChatCreate: 'Submit your expenses here!', + conciergeLHNGBR: '¡Comienza aquí!', + saveSearchTooltipText: 'Puedes renombrar tu búsqueda guardada aquí.', + quickActionButton: '¡Acción rápida! A solo un toque', + workspaceChatCreate: 'Envía tus gastos aquí.', }, }; From d8f90187b4ba6fc9f806ca439bf2d43a83ed95fa Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Wed, 11 Dec 2024 19:23:37 +0530 Subject: [PATCH 24/28] Refactor product training tooltips to support multi-part translations and add EducationalTooltipContent component --- .../PRODUCT_TRAINING_TOOLTIP_DATA.ts | 24 +++++-- .../ProductTrainingContext/index.tsx | 71 ++++--------------- .../EducationalTooltipContent.tsx | 42 +++++++++++ src/languages/en.ts | 21 ++++-- src/languages/es.ts | 21 ++++-- 5 files changed, 108 insertions(+), 71 deletions(-) create mode 100644 src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx diff --git a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts index a5b5e5973369..d7f2a27d94d2 100644 --- a/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts +++ b/src/components/ProductTrainingContext/PRODUCT_TRAINING_TOOLTIP_DATA.ts @@ -1,6 +1,7 @@ import type {ValueOf} from 'type-fest'; import {dismissProductTraining} from '@libs/actions/Welcome'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; @@ -11,7 +12,7 @@ type ShouldShowConditionProps = { }; type TooltipData = { - content: string; + content: Array<{text: TranslationPaths; isBold: boolean}>; onHideTooltip: () => void; name: ProductTrainingTooltipName; priority: number; @@ -20,28 +21,41 @@ type TooltipData = { const PRODUCT_TRAINING_TOOLTIP_DATA: Record = { [CONCEIRGE_LHN_GBR]: { - content: 'productTrainingTooltip.conciergeLHNGBR', + content: [ + {text: 'productTrainingTooltip.conciergeLHNGBR.part1', isBold: false}, + {text: 'productTrainingTooltip.conciergeLHNGBR.part2', isBold: true}, + ], onHideTooltip: () => dismissProductTraining(CONCEIRGE_LHN_GBR), name: CONCEIRGE_LHN_GBR, priority: 1300, shouldShow: ({shouldUseNarrowLayout}) => !!shouldUseNarrowLayout, }, [RENAME_SAVED_SEARCH]: { - content: 'productTrainingTooltip.saveSearchTooltipText', + content: [ + {text: 'productTrainingTooltip.saveSearchTooltip.part1', isBold: true}, + {text: 'productTrainingTooltip.saveSearchTooltip.part2', isBold: false}, + ], onHideTooltip: () => dismissProductTraining(RENAME_SAVED_SEARCH), name: RENAME_SAVED_SEARCH, priority: 1250, shouldShow: ({shouldUseNarrowLayout}) => !shouldUseNarrowLayout, }, [QUICK_ACTION_BUTTON]: { - content: 'productTrainingTooltip.quickActionButton', + content: [ + {text: 'productTrainingTooltip.quickActionButton.part1', isBold: true}, + {text: 'productTrainingTooltip.quickActionButton.part2', isBold: false}, + ], onHideTooltip: () => dismissProductTraining(QUICK_ACTION_BUTTON), name: QUICK_ACTION_BUTTON, priority: 1200, shouldShow: () => true, }, [WORKSAPCE_CHAT_CREATE]: { - content: 'productTrainingTooltip.workspaceChatCreate', + content: [ + {text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: false}, + {text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: true}, + {text: 'productTrainingTooltip.workspaceChatCreate.part3', isBold: false}, + ], onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE), name: WORKSAPCE_CHAT_CREATE, priority: 1100, diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 98101153d672..2ae9e066afe8 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -1,17 +1,8 @@ import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; -import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import HTMLEngineProvider from '@components/HTMLEngineProvider'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import RenderHTML from '@components/RenderHTML'; -import useLocalize from '@hooks/useLocalize'; +import EducationalTooltipContent from '@components/Tooltip/EducationalTooltip/EducationalTooltipContent'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import convertToLTR from '@libs/convertToLTR'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; -import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {ProductTrainingTooltipName} from './PRODUCT_TRAINING_TOOLTIP_DATA'; @@ -19,14 +10,12 @@ import PRODUCT_TRAINING_TOOLTIP_DATA from './PRODUCT_TRAINING_TOOLTIP_DATA'; type ProductTrainingContextType = { shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean; - renderProductTrainingTooltip: (tooltipName: ProductTrainingTooltipName) => React.ReactNode | null; registerTooltip: (tooltipName: ProductTrainingTooltipName) => void; unregisterTooltip: (tooltipName: ProductTrainingTooltipName) => void; }; const ProductTrainingContext = createContext({ shouldRenderTooltip: () => false, - renderProductTrainingTooltip: () => null, registerTooltip: () => {}, unregisterTooltip: () => {}, }); @@ -38,10 +27,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { selector: hasCompletedGuidedSetupFlowSelector, }); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); - const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const styles = useThemeStyles(); - const theme = useTheme(); const [activeTooltips, setActiveTooltips] = useState>(new Set()); @@ -127,41 +113,13 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { [shouldTooltipBeVisible, determineVisibleTooltip], ); - const renderProductTrainingTooltip = useCallback( - (tooltipName: ProductTrainingTooltipName) => { - const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; - const processedContent = () => { - const content = convertToLTR(translate(tooltip.content as TranslationPaths)); - - return content ? `${content}` : ''; - }; - - return ( - - - - - - - - - ); - }, - [styles, theme.tooltipHighlightText, translate], - ); - const contextValue = useMemo( () => ({ - renderProductTrainingTooltip, shouldRenderTooltip, registerTooltip, unregisterTooltip, }), - [shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip], + [shouldRenderTooltip, registerTooltip, unregisterTooltip], ); return {children}; @@ -169,11 +127,12 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shouldShow = true) => { const context = useContext(ProductTrainingContext); + if (!context) { throw new Error('useProductTourContext must be used within a ProductTourProvider'); } - const {shouldRenderTooltip, registerTooltip, unregisterTooltip, renderProductTrainingTooltip} = context; + const {shouldRenderTooltip, registerTooltip, unregisterTooltip} = context; useEffect(() => { if (shouldShow) { @@ -182,12 +141,16 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou unregisterTooltip(tooltipName); }; } - return undefined; + return () => {}; }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]); + const renderProductTrainingTooltip = useCallback(() => { + const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; + return ; + }, [tooltipName]); const shouldShowProductTrainingTooltip = useMemo(() => { - return shouldShow && shouldRenderTooltip(tooltipName); - }, [shouldShow, shouldRenderTooltip, tooltipName]); + return shouldRenderTooltip(tooltipName); + }, [shouldRenderTooltip, tooltipName]); const hideProductTrainingTooltip = useCallback(() => { const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; @@ -195,18 +158,10 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou unregisterTooltip(tooltipName); }, [tooltipName, unregisterTooltip]); - if (!shouldShow) { - return { - renderProductTrainingTooltip: () => null, - hideProductTrainingTooltip: () => {}, - shouldShowProductTrainingTooltip: false, - }; - } - return { - renderProductTrainingTooltip: () => renderProductTrainingTooltip(tooltipName), + renderProductTrainingTooltip, hideProductTrainingTooltip, - shouldShowProductTrainingTooltip, + shouldShowProductTrainingTooltip: shouldShow && shouldShowProductTrainingTooltip, }; }; diff --git a/src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx b/src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx new file mode 100644 index 000000000000..5ecd30e205ff --- /dev/null +++ b/src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {TranslationPaths} from '@src/languages/types'; + +type EducationalTooltipContentProps = { + /** The text to display in the tooltip */ + content: Array<{text: TranslationPaths; isBold: boolean}>; +}; + +function EducationalTooltipContent({content}: EducationalTooltipContentProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + + return ( + + + + {content.map(({text, isBold}) => ( + + {translate(text)} + + ))} + + + ); +} + +export default EducationalTooltipContent; diff --git a/src/languages/en.ts b/src/languages/en.ts index 8492e3de3b37..cca42e7d75db 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5447,10 +5447,23 @@ const translations = { }, }, productTrainingTooltip: { - conciergeLHNGBR: 'Get started here!', - saveSearchTooltipText: 'Rename your saved searches here!', - quickActionButton: 'Quick action! Just a tap away', - workspaceChatCreate: 'Submit your expenses here!', + conciergeLHNGBR: { + part1: 'Get started', + part2: ' here!', + }, + saveSearchTooltip: { + part1: 'Rename your saved searches', + part2: ' here!', + }, + quickActionButton: { + part1: 'Quick action!', + part2: ' Just a tap away', + }, + workspaceChatCreate: { + part1: 'Submit your', + part2: ' expenses', + part3: ' here!', + }, }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 967ddf9ae3ec..0784da08d4bc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5967,10 +5967,23 @@ const translations = { }, }, productTrainingTooltip: { - conciergeLHNGBR: '¡Comienza aquí!', - saveSearchTooltipText: 'Puedes renombrar tu búsqueda guardada aquí.', - quickActionButton: '¡Acción rápida! A solo un toque', - workspaceChatCreate: 'Envía tus gastos aquí.', + conciergeLHNGBR: { + part1: 'Empieza', + part2: ' aquí!', + }, + saveSearchTooltip: { + part1: 'Renombra tus búsquedas guardadas', + part2: ' aquí!', + }, + quickActionButton: { + part1: '¡Acción rápida!', + part2: ' a solo un toque.', + }, + workspaceChatCreate: { + part1: 'Envía tus', + part2: ' gastos', + part3: ' aquí!', + }, }, }; From 971a11a272a6825b811f069f1c9811c630eb6026 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Wed, 11 Dec 2024 23:00:41 +0530 Subject: [PATCH 25/28] Refactor product training tooltip rendering and remove EducationalTooltipContent component --- .../ProductTrainingContext/index.tsx | 50 +++++++++++++++++-- .../EducationalTooltipContent.tsx | 42 ---------------- 2 files changed, 47 insertions(+), 45 deletions(-) delete mode 100644 src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 2ae9e066afe8..090bfd1fd819 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -1,7 +1,13 @@ import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import EducationalTooltipContent from '@components/Tooltip/EducationalTooltip/EducationalTooltipContent'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -127,6 +133,9 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shouldShow = true) => { const context = useContext(ProductTrainingContext); + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); if (!context) { throw new Error('useProductTourContext must be used within a ProductTourProvider'); @@ -143,10 +152,45 @@ const useProductTrainingContext = (tooltipName: ProductTrainingTooltipName, shou } return () => {}; }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]); + const renderProductTrainingTooltip = useCallback(() => { const tooltip = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; - return ; - }, [tooltipName]); + return ( + + + + {tooltip.content.map(({text, isBold}) => { + const translatedText = translate(text); + return ( + + {translatedText} + + ); + })} + + + ); + }, [ + styles.alignItemsCenter, + styles.flexRow, + styles.flexWrap, + styles.gap1, + styles.justifyContentCenter, + styles.p2, + styles.quickActionTooltipSubtitle, + styles.textAlignCenter, + styles.textBold, + theme.tooltipHighlightText, + tooltipName, + translate, + ]); const shouldShowProductTrainingTooltip = useMemo(() => { return shouldRenderTooltip(tooltipName); diff --git a/src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx b/src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx deleted file mode 100644 index 5ecd30e205ff..000000000000 --- a/src/components/Tooltip/EducationalTooltip/EducationalTooltipContent.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type {TranslationPaths} from '@src/languages/types'; - -type EducationalTooltipContentProps = { - /** The text to display in the tooltip */ - content: Array<{text: TranslationPaths; isBold: boolean}>; -}; - -function EducationalTooltipContent({content}: EducationalTooltipContentProps) { - const styles = useThemeStyles(); - const theme = useTheme(); - const {translate} = useLocalize(); - - return ( - - - - {content.map(({text, isBold}) => ( - - {translate(text)} - - ))} - - - ); -} - -export default EducationalTooltipContent; From 39361a4c516a8de9853f25f009252c3c7cd84460 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 12 Dec 2024 00:55:58 +0530 Subject: [PATCH 26/28] add api changes --- src/libs/actions/Welcome/index.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index b306daf444ba..f91ab98f7b1d 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -207,20 +207,16 @@ function setSelfTourViewed(shouldUpdateOnyxDataOnlyLocally = false) { function dismissProductTraining(elementName: string) { const date = new Date(); - // const optimisticData = [ - // { - // onyxMethod: Onyx.METHOD.MERGE, - // key: ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, - // value: { - // [elementName]: DateUtils.getDBTime(date.valueOf()), - // }, - // }, - // ]; - // API.write(WRITE_COMMANDS.DISMISS_PRODUCT_TRAINING, {name: elementName}, {optimisticData}); - - Onyx.merge(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, { - [elementName]: DateUtils.getDBTime(date.valueOf()), - }); + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, + value: { + [elementName]: DateUtils.getDBTime(date.valueOf()), + }, + }, + ]; + API.write(WRITE_COMMANDS.DISMISS_PRODUCT_TRAINING, {name: elementName}, {optimisticData}); } export { From 615e4acf44e964a897ed2fc26ff5f45af5087a6a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 12 Dec 2024 04:23:48 +0530 Subject: [PATCH 27/28] Update to include permissions check and update Spanish translations --- src/components/ProductTrainingContext/index.tsx | 6 ++++-- src/languages/es.ts | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx index 090bfd1fd819..92997fe70af3 100644 --- a/src/components/ProductTrainingContext/index.tsx +++ b/src/components/ProductTrainingContext/index.tsx @@ -9,6 +9,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; +import Permissions from '@libs/Permissions'; import ONYXKEYS from '@src/ONYXKEYS'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {ProductTrainingTooltipName} from './PRODUCT_TRAINING_TOOLTIP_DATA'; @@ -33,6 +34,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { selector: hasCompletedGuidedSetupFlowSelector, }); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); + const [allBetas] = useOnyx(ONYXKEYS.BETAS); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeTooltips, setActiveTooltips] = useState>(new Set()); @@ -73,7 +75,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { (tooltipName: ProductTrainingTooltipName) => { const isDismissed = !!dismissedProductTraining?.[tooltipName]; - if (isDismissed) { + if (isDismissed || !Permissions.shouldShowProductTrainingElements(allBetas)) { return false; } const tooltipConfig = PRODUCT_TRAINING_TOOLTIP_DATA[tooltipName]; @@ -86,7 +88,7 @@ function ProductTrainingContextProvider({children}: ChildrenProps) { shouldUseNarrowLayout, }); }, - [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], + [allBetas, dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, shouldUseNarrowLayout], ); const registerTooltip = useCallback( diff --git a/src/languages/es.ts b/src/languages/es.ts index 0784da08d4bc..cd3a5b1ba1de 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5968,21 +5968,21 @@ const translations = { }, productTrainingTooltip: { conciergeLHNGBR: { - part1: 'Empieza', + part1: '¡Comienza', part2: ' aquí!', }, saveSearchTooltip: { part1: 'Renombra tus búsquedas guardadas', - part2: ' aquí!', + part2: ' aquí', }, quickActionButton: { part1: '¡Acción rápida!', - part2: ' a solo un toque.', + part2: ' A solo un toque', }, workspaceChatCreate: { part1: 'Envía tus', part2: ' gastos', - part3: ' aquí!', + part3: ' aquí', }, }, }; From a8ab5c645fbab74ce63fbceea3474ec7c7bfcf09 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 12 Dec 2024 16:36:35 +0530 Subject: [PATCH 28/28] changes as per review --- src/types/onyx/DismissedProductTraining.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts index a8e947eab2ad..fe24dc061aee 100644 --- a/src/types/onyx/DismissedProductTraining.ts +++ b/src/types/onyx/DismissedProductTraining.ts @@ -1,3 +1,6 @@ +import CONST from '@src/CONST'; + +const {CONCEIRGE_LHN_GBR, RENAME_SAVED_SEARCH, WORKSAPCE_CHAT_CREATE, QUICK_ACTION_BUTTON} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; /** * This type is used to store the timestamp of when the user dismisses a product training ui elements. */ @@ -5,27 +8,27 @@ type DismissedProductTraining = { /** * When user dismisses the nudgeMigration Welcome Modal, we store the timestamp here. */ - migratedUserWelcomeModal: Date; + [CONST.MIGRATED_USER_WELCOME_MODAL]: Date; /** * When user dismisses the conciergeLHNGBR product training tooltip, we store the timestamp here. */ - conciergeLHNGBR: Date; + [CONCEIRGE_LHN_GBR]: Date; /** * When user dismisses the renameSavedSearch product training tooltip, we store the timestamp here. */ - renameSavedSearch: Date; + [RENAME_SAVED_SEARCH]: Date; /** * When user dismisses the workspaceChatCreate product training tooltip, we store the timestamp here. */ - workspaceChatCreate: Date; + [WORKSAPCE_CHAT_CREATE]: Date; /** * When user dismisses the quickActionButton product training tooltip, we store the timestamp here. */ - quickActionButton: Date; + [QUICK_ACTION_BUTTON]: Date; }; export default DismissedProductTraining;