diff --git a/src/App.tsx b/src/App.tsx
index 52904e0a06c4..cc824b78fa4c 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';
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 740ee2206876..cf9e5d8a2886 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -6444,6 +6444,17 @@ const CONST = {
},
MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal',
+
+ PRODUCT_TRAINING_TOOLTIP_NAMES: {
+ CONCEIRGE_LHN_GBR: 'conciergeLHNGBR',
+ RENAME_SAVED_SEARCH: 'renameSavedSearch',
+ QUICK_ACTION_BUTTON: 'quickActionButton',
+ WORKSAPCE_CHAT_CREATE: 'workspaceChatCreate',
+ SEARCH_FILTER_BUTTON_TOOLTIP: 'filterButtonTooltip',
+ BOTTOM_NAV_INBOX_TOOLTIP: 'bottomNavInboxTooltip',
+ LHN_WORKSPACE_CHAT_TOOLTIP: 'workspaceChatLHNTooltip',
+ GLOBAL_CREATE_TOOLTIP: 'globalCreateTooltip',
+ },
} as const;
type Country = keyof typeof CONST.ALL_COUNTRIES;
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 86d4c90d67ed..026ab2310622 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -117,9 +117,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',
@@ -222,18 +219,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 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',
-
/** Does this user have push notifications enabled for this device? */
PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled',
@@ -888,7 +876,6 @@ type OnyxCollectionValuesMapping = {
type OnyxValuesMapping = {
[ONYXKEYS.ACCOUNT]: OnyxTypes.Account;
[ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string;
- [ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER]: boolean;
[ONYXKEYS.NVP_ONBOARDING]: Onboarding;
@@ -1030,9 +1017,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;
@@ -1040,7 +1025,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.PRESERVED_USER_SESSION]: OnyxTypes.Session;
diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx
index 3c831301db8b..8ba640956bf3 100644
--- a/src/components/FloatingActionButton.tsx
+++ b/src/components/FloatingActionButton.tsx
@@ -3,12 +3,20 @@ import React, {forwardRef, useEffect, useRef} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {GestureResponderEvent, Role, Text, View} from 'react-native';
import {Platform} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import Svg, {Path} from 'react-native-svg';
+import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import getPlatform from '@libs/getPlatform';
import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
import {PressableWithoutFeedback} from './Pressable';
+import {useProductTrainingContext} from './ProductTrainingContext';
+import EducationalTooltip from './Tooltip/EducationalTooltip';
const AnimatedPath = Animated.createAnimatedComponent(Path);
AnimatedPath.displayName = 'AnimatedPath';
@@ -56,6 +64,15 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
const styles = useThemeStyles();
const borderRadius = styles.floatingActionButton.borderRadius;
const fabPressable = useRef(null);
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const platform = getPlatform();
+ const isNarrowScreenOnWeb = shouldUseNarrowLayout && platform === CONST.PLATFORM.WEB;
+ const isFocused = useBottomTabIsFocused();
+ const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {initialValue: false});
+ const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GLOBAL_CREATE_TOOLTIP,
+ isFocused && isSidebarLoaded,
+ );
const sharedValue = useSharedValue(isActive ? 1 : 0);
const buttonRef = ref;
@@ -97,32 +114,45 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo
};
return (
- {
- fabPressable.current = el ?? null;
- if (buttonRef && 'current' in buttonRef) {
- buttonRef.current = el ?? null;
- }
+ {}}
- role={role}
- shouldUseHapticsOnLongPress={false}
+ shouldUseOverlay
+ shiftHorizontal={isNarrowScreenOnWeb ? 0 : variables.fabTooltipShiftHorizontal}
+ renderTooltipContent={renderProductTrainingTooltip}
+ wrapperStyle={styles.productTrainingTooltipWrapper}
+ onHideTooltip={hideProductTrainingTooltip}
>
-
-
-
-
+ {
+ fabPressable.current = el ?? null;
+ if (buttonRef && 'current' in buttonRef) {
+ buttonRef.current = el ?? null;
+ }
+ }}
+ style={[styles.h100, styles.bottomTabBarItem]}
+ accessibilityLabel={accessibilityLabel}
+ onPress={toggleFabAction}
+ onLongPress={() => {}}
+ role={role}
+ shouldUseHapticsOnLongPress={false}
+ >
+
+
+
+
+
);
}
diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index c423d3101d92..efdd9659c845 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -1,5 +1,5 @@
import {useFocusEffect} from '@react-navigation/native';
-import React, {useCallback, useRef, useState} from 'react';
+import React, {useCallback, useMemo, useRef, useState} from 'react';
import type {GestureResponderEvent, ViewStyle} from 'react-native';
import {StyleSheet, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
@@ -11,6 +11,7 @@ import MultipleAvatars from '@components/MultipleAvatars';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {useSession} from '@components/OnyxProvider';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
+import {useProductTrainingContext} from '@components/ProductTrainingContext';
import SubscriptAvatar from '@components/SubscriptAvatar';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
@@ -22,7 +23,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';
@@ -32,7 +32,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';
@@ -48,18 +47,21 @@ 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 [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
+ const isActiveWorkspaceChat = ReportUtils.isPolicyExpenseChat(report) && report?.isOwnPolicyExpenseChat && activePolicyID === report?.policyID;
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const session = useSession();
-
- // 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 shouldShowToooltipOnThisReport = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report);
- const [shouldHideGBRTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, {initialValue: true});
+ const shouldShowGetStartedTooltip = isOnboardingGuideAssigned ? ReportUtils.isAdminRoom(report) : ReportUtils.isConciergeChatReport(report);
+
+ const {tooltipToRender, shouldShowTooltip} = useMemo(() => {
+ const tooltip = shouldShowGetStartedTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR : CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP;
+
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ return {tooltipToRender: tooltip, shouldShowTooltip: shouldUseNarrowLayout ? isScreenFocused : true};
+ }, [shouldShowGetStartedTooltip, isScreenFocused, shouldUseNarrowLayout]);
+ const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender, shouldShowTooltip);
const {translate} = useLocalize();
const [isContextMenuActive, setIsContextMenuActive] = useState(false);
@@ -72,30 +74,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
@@ -180,17 +158,18 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
needsOffscreenAlphaCompositing
>
diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts
new file mode 100644
index 000000000000..dc2a761a4903
--- /dev/null
+++ b/src/components/ProductTrainingContext/TOOLTIPS.ts
@@ -0,0 +1,118 @@
+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,
+ SEARCH_FILTER_BUTTON_TOOLTIP,
+ BOTTOM_NAV_INBOX_TOOLTIP,
+ LHN_WORKSPACE_CHAT_TOOLTIP,
+ GLOBAL_CREATE_TOOLTIP,
+} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES;
+
+type ProductTrainingTooltipName = ValueOf;
+
+type ShouldShowConditionProps = {
+ shouldUseNarrowLayout?: boolean;
+};
+
+type TooltipData = {
+ content: Array<{text: TranslationPaths; isBold: boolean}>;
+ onHideTooltip: () => void;
+ name: ProductTrainingTooltipName;
+ priority: number;
+ shouldShow: (props: ShouldShowConditionProps) => boolean;
+};
+
+const TOOLTIPS: Record = {
+ [CONCEIRGE_LHN_GBR]: {
+ 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: [
+ {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,
+ },
+ [GLOBAL_CREATE_TOOLTIP]: {
+ content: [
+ {text: 'productTrainingTooltip.globalCreateTooltip.part1', isBold: true},
+ {text: 'productTrainingTooltip.globalCreateTooltip.part2', isBold: false},
+ {text: 'productTrainingTooltip.globalCreateTooltip.part3', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(GLOBAL_CREATE_TOOLTIP),
+ name: GLOBAL_CREATE_TOOLTIP,
+ priority: 1200,
+ shouldShow: () => true,
+ },
+ [QUICK_ACTION_BUTTON]: {
+ content: [
+ {text: 'productTrainingTooltip.quickActionButton.part1', isBold: true},
+ {text: 'productTrainingTooltip.quickActionButton.part2', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(QUICK_ACTION_BUTTON),
+ name: QUICK_ACTION_BUTTON,
+ priority: 1150,
+ shouldShow: () => true,
+ },
+ [WORKSAPCE_CHAT_CREATE]: {
+ content: [
+ {text: 'productTrainingTooltip.workspaceChatCreate.part1', isBold: true},
+ {text: 'productTrainingTooltip.workspaceChatCreate.part2', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(WORKSAPCE_CHAT_CREATE),
+ name: WORKSAPCE_CHAT_CREATE,
+ priority: 1100,
+ shouldShow: () => true,
+ },
+ [SEARCH_FILTER_BUTTON_TOOLTIP]: {
+ content: [
+ {text: 'productTrainingTooltip.searchFilterButtonTooltip.part1', isBold: true},
+ {text: 'productTrainingTooltip.searchFilterButtonTooltip.part2', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(SEARCH_FILTER_BUTTON_TOOLTIP),
+ name: SEARCH_FILTER_BUTTON_TOOLTIP,
+ priority: 1000,
+ shouldShow: () => true,
+ },
+ [BOTTOM_NAV_INBOX_TOOLTIP]: {
+ content: [
+ {text: 'productTrainingTooltip.bottomNavInboxTooltip.part1', isBold: true},
+ {text: 'productTrainingTooltip.bottomNavInboxTooltip.part2', isBold: false},
+ {text: 'productTrainingTooltip.bottomNavInboxTooltip.part3', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(BOTTOM_NAV_INBOX_TOOLTIP),
+ name: BOTTOM_NAV_INBOX_TOOLTIP,
+ priority: 900,
+ shouldShow: () => true,
+ },
+ [LHN_WORKSPACE_CHAT_TOOLTIP]: {
+ content: [
+ {text: 'productTrainingTooltip.workspaceChatTooltip.part1', isBold: true},
+ {text: 'productTrainingTooltip.workspaceChatTooltip.part2', isBold: false},
+ {text: 'productTrainingTooltip.workspaceChatTooltip.part3', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(LHN_WORKSPACE_CHAT_TOOLTIP),
+ name: LHN_WORKSPACE_CHAT_TOOLTIP,
+ priority: 800,
+ shouldShow: () => true,
+ },
+};
+
+export default TOOLTIPS;
+export type {ProductTrainingTooltipName};
diff --git a/src/components/ProductTrainingContext/index.tsx b/src/components/ProductTrainingContext/index.tsx
new file mode 100644
index 000000000000..7cfcf4d3bfa7
--- /dev/null
+++ b/src/components/ProductTrainingContext/index.tsx
@@ -0,0 +1,224 @@
+import React, {createContext, useCallback, useContext, useEffect, useMemo, useState} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+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 CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
+import type {ProductTrainingTooltipName} from './TOOLTIPS';
+import TOOLTIPS from './TOOLTIPS';
+
+type ProductTrainingContextType = {
+ shouldRenderTooltip: (tooltipName: ProductTrainingTooltipName) => boolean;
+ registerTooltip: (tooltipName: ProductTrainingTooltipName) => void;
+ unregisterTooltip: (tooltipName: ProductTrainingTooltipName) => void;
+};
+
+const ProductTrainingContext = createContext({
+ shouldRenderTooltip: () => false,
+ registerTooltip: () => {},
+ unregisterTooltip: () => {},
+});
+
+function ProductTrainingContextProvider({children}: ChildrenProps) {
+ const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT);
+ const hasBeenAddedToNudgeMigration = !!tryNewDot?.nudgeMigration?.timestamp;
+ const [isOnboardingCompleted = true, isOnboardingCompletedMetadata] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {
+ selector: hasCompletedGuidedSetupFlowSelector,
+ });
+ const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING);
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
+
+ const [activeTooltips, setActiveTooltips] = useState>(new Set());
+
+ const unregisterTooltip = useCallback(
+ (tooltipName: ProductTrainingTooltipName) => {
+ setActiveTooltips((prev) => {
+ const next = new Set(prev);
+ next.delete(tooltipName);
+ return next;
+ });
+ },
+ [setActiveTooltips],
+ );
+
+ const determineVisibleTooltip = useCallback(() => {
+ if (activeTooltips.size === 0) {
+ return null;
+ }
+
+ const sortedTooltips = Array.from(activeTooltips)
+ .map((name) => ({
+ name,
+ priority: TOOLTIPS[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(
+ (tooltipName: ProductTrainingTooltipName) => {
+ if (isLoadingOnyxValue(isOnboardingCompletedMetadata)) {
+ return false;
+ }
+
+ const isDismissed = !!dismissedProductTraining?.[tooltipName];
+
+ if (isDismissed) {
+ return false;
+ }
+ const tooltipConfig = TOOLTIPS[tooltipName];
+
+ // if hasBeenAddedToNudgeMigration is true, and welcome modal is not dismissed, don't show tooltip
+ if (hasBeenAddedToNudgeMigration && !dismissedProductTraining?.[CONST.MIGRATED_USER_WELCOME_MODAL]) {
+ return false;
+ }
+ if (isOnboardingCompleted === false) {
+ return false;
+ }
+
+ return tooltipConfig.shouldShow({
+ shouldUseNarrowLayout,
+ });
+ },
+ [dismissedProductTraining, hasBeenAddedToNudgeMigration, isOnboardingCompleted, isOnboardingCompletedMetadata, shouldUseNarrowLayout],
+ );
+
+ const registerTooltip = useCallback(
+ (tooltipName: ProductTrainingTooltipName) => {
+ const shouldRegister = shouldTooltipBeVisible(tooltipName);
+ if (!shouldRegister) {
+ return;
+ }
+ setActiveTooltips((prev) => new Set([...prev, tooltipName]));
+ },
+ [shouldTooltipBeVisible],
+ );
+
+ const shouldRenderTooltip = useCallback(
+ (tooltipName: ProductTrainingTooltipName) => {
+ // First check base conditions
+ const shouldShow = shouldTooltipBeVisible(tooltipName);
+ if (!shouldShow) {
+ return false;
+ }
+ const visibleTooltip = determineVisibleTooltip();
+
+ // If this is the highest priority visible tooltip, show it
+ if (tooltipName === visibleTooltip) {
+ return true;
+ }
+
+ return false;
+ },
+ [shouldTooltipBeVisible, determineVisibleTooltip],
+ );
+
+ const contextValue = useMemo(
+ () => ({
+ shouldRenderTooltip,
+ registerTooltip,
+ unregisterTooltip,
+ }),
+ [shouldRenderTooltip, registerTooltip, unregisterTooltip],
+ );
+
+ return {children};
+}
+
+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');
+ }
+
+ const {shouldRenderTooltip, registerTooltip, unregisterTooltip} = context;
+
+ useEffect(() => {
+ if (shouldShow) {
+ registerTooltip(tooltipName);
+ return () => {
+ unregisterTooltip(tooltipName);
+ };
+ }
+ return () => {};
+ }, [tooltipName, registerTooltip, unregisterTooltip, shouldShow]);
+
+ const renderProductTrainingTooltip = useCallback(() => {
+ const tooltip = TOOLTIPS[tooltipName];
+ return (
+
+
+
+ {tooltip.content.map(({text, isBold}) => {
+ const translatedText = translate(text);
+ return (
+
+ {translatedText}
+
+ );
+ })}
+
+
+ );
+ }, [
+ styles.alignItemsCenter,
+ styles.flexRow,
+ styles.flexWrap,
+ styles.gap3,
+ styles.justifyContentCenter,
+ styles.mw100,
+ styles.p2,
+ styles.productTrainingTooltipText,
+ styles.textAlignCenter,
+ styles.textBold,
+ styles.textWrap,
+ theme.tooltipHighlightText,
+ tooltipName,
+ translate,
+ ]);
+
+ const shouldShowProductTrainingTooltip = useMemo(() => {
+ return shouldRenderTooltip(tooltipName);
+ }, [shouldRenderTooltip, tooltipName]);
+
+ const hideProductTrainingTooltip = useCallback(() => {
+ const tooltip = TOOLTIPS[tooltipName];
+ tooltip.onHideTooltip();
+ unregisterTooltip(tooltipName);
+ }, [tooltipName, unregisterTooltip]);
+
+ return {
+ renderProductTrainingTooltip,
+ hideProductTrainingTooltip,
+ shouldShowProductTrainingTooltip,
+ };
+};
+
+export {ProductTrainingContextProvider, useProductTrainingContext};
diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx
index a78845f126d2..21a5832052c0 100644
--- a/src/components/Search/SearchPageHeader.tsx
+++ b/src/components/Search/SearchPageHeader.tsx
@@ -1,3 +1,4 @@
+import {useIsFocused} from '@react-navigation/native';
import React, {useMemo, useState} from 'react';
import {InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
@@ -8,6 +9,8 @@ import ConfirmModal from '@components/ConfirmModal';
import DecisionModal from '@components/DecisionModal';
import * as Expensicons from '@components/Icon/Expensicons';
import {usePersonalDetails} from '@components/OnyxProvider';
+import {useProductTrainingContext} from '@components/ProductTrainingContext';
+import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
@@ -55,6 +58,11 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
const [isDeleteExpensesConfirmModalVisible, setIsDeleteExpensesConfirmModalVisible] = useState(false);
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+ const isFocused = useIsFocused();
+ const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP,
+ isFocused,
+ );
const {status, hash} = queryJSON;
@@ -348,12 +356,25 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
shouldUseStyleUtilityForAnchorPosition
/>
) : (
-
+
+
+
)}
void>();
const [shouldMeasure, setShouldMeasure] = useState(false);
@@ -21,6 +21,13 @@ function BaseEducationalTooltip({children, onHideTooltip, shouldRender = false,
const didShow = useRef(false);
+ const onHideTooltip = useCallback(() => {
+ if (!shouldRender) {
+ return;
+ }
+ onHideTooltipProp?.();
+ }, [onHideTooltipProp, shouldRender]);
+
const closeTooltip = useCallback(() => {
if (!didShow.current) {
return;
diff --git a/src/hooks/useBottomTabIsFocused.ts b/src/hooks/useBottomTabIsFocused.ts
new file mode 100644
index 000000000000..60817f194628
--- /dev/null
+++ b/src/hooks/useBottomTabIsFocused.ts
@@ -0,0 +1,26 @@
+import {useIsFocused, useNavigationState} from '@react-navigation/native';
+import CENTRAL_PANE_SCREENS from '@libs/Navigation/AppNavigator/CENTRAL_PANE_SCREENS';
+import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute';
+import getTopmostFullScreenRoute from '@libs/Navigation/getTopmostFullScreenRoute';
+import type {CentralPaneName, FullScreenName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types';
+import SCREENS from '@src/SCREENS';
+import useResponsiveLayout from './useResponsiveLayout';
+
+const useBottomTabIsFocused = () => {
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const isFocused = useIsFocused();
+ const topmostFullScreenName = useNavigationState | undefined>(getTopmostFullScreenRoute);
+ const topmostCentralPane = useNavigationState | undefined>(getTopmostCentralPaneRoute);
+ // If there is a full screen view such as Workspace Settings or Not Found screen, the bottom tab should not be considered focused
+ if (topmostFullScreenName) {
+ return false;
+ }
+ // On the Search screen, isFocused returns false, but it is actually focused
+ if (shouldUseNarrowLayout) {
+ return isFocused || topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE;
+ }
+ // On desktop screen sizes, isFocused always returns false, so we cannot rely on it alone to determine if the bottom tab is focused
+ return isFocused || Object.keys(CENTRAL_PANE_SCREENS).includes(topmostCentralPane?.name ?? '');
+};
+
+export default useBottomTabIsFocused;
diff --git a/src/hooks/useOnboardingFlow.ts b/src/hooks/useOnboardingFlow.ts
index d322a4d52703..e1e659fd4eb1 100644
--- a/src/hooks/useOnboardingFlow.ts
+++ b/src/hooks/useOnboardingFlow.ts
@@ -3,7 +3,6 @@ import {InteractionManager, NativeModules} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Navigation from '@libs/Navigation/Navigation';
import {hasCompletedGuidedSetupFlowSelector, tryNewDotOnyxSelector} from '@libs/onboardingSelectors';
-import Permissions from '@libs/Permissions';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import * as Session from '@userActions/Session';
import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow';
@@ -30,11 +29,10 @@ function useOnboardingFlowRouter() {
const isPrivateDomain = Session.isUserOnPrivateDomain();
const [isSingleNewDotEntry, isSingleNewDotEntryMetadata] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY);
- const [allBetas, allBetasMetadata] = useOnyx(ONYXKEYS.BETAS);
useEffect(() => {
// This should delay opening the onboarding modal so it does not interfere with the ongoing ReportScreen params changes
InteractionManager.runAfterInteractions(() => {
- if (isLoadingOnyxValue(isOnboardingCompletedMetadata, tryNewDotdMetadata, dismissedProductTrainingMetadata, allBetasMetadata)) {
+ if (isLoadingOnyxValue(isOnboardingCompletedMetadata, tryNewDotdMetadata, dismissedProductTrainingMetadata)) {
return;
}
@@ -42,7 +40,7 @@ function useOnboardingFlowRouter() {
return;
}
- if (hasBeenAddedToNudgeMigration && !dismissedProductTraining?.migratedUserWelcomeModal && Permissions.shouldShowProductTrainingElements(allBetas)) {
+ if (hasBeenAddedToNudgeMigration && !dismissedProductTraining?.migratedUserWelcomeModal) {
const defaultCannedQuery = SearchQueryUtils.buildCannedSearchQuery();
const query = defaultCannedQuery;
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
@@ -85,8 +83,6 @@ function useOnboardingFlowRouter() {
dismissedProductTraining?.migratedUserWelcomeModal,
dismissedProductTraining,
isPrivateDomain,
- allBetas,
- allBetasMetadata,
]);
return {isOnboardingCompleted, isHybridAppOnboardingCompleted};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 66b89b810d1b..3d35fed3e0a4 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -657,10 +657,6 @@ const translations = {
emoji: 'Emoji',
collapse: 'Collapse',
expand: 'Expand',
- tooltip: {
- title: 'Get started!',
- subtitle: ' Submit your first expense',
- },
},
reportActionContextMenu: {
copyToClipboard: 'Copy to clipboard',
@@ -847,10 +843,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',
@@ -4673,7 +4665,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',
@@ -5575,6 +5566,44 @@ const translations = {
crossPlatform: 'Do everything from your phone or browser',
},
},
+ productTrainingTooltip: {
+ 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!',
+ },
+ searchFilterButtonTooltip: {
+ part1: 'Customize your search',
+ part2: ' here!',
+ },
+ bottomNavInboxTooltip: {
+ part1: 'Your to-do list',
+ part2: '\n🟢 = ready for you',
+ part3: ' 🔴 = needs review',
+ },
+ workspaceChatTooltip: {
+ part1: 'Submit expenses',
+ part2: ' and chat with',
+ part3: '\napprovers here!',
+ },
+ globalCreateTooltip: {
+ part1: 'Create expenses',
+ part2: ', start chatting,',
+ part3: '\nand more!',
+ },
+ },
};
export default translations satisfies TranslationDeepObject;
diff --git a/src/languages/es.ts b/src/languages/es.ts
index b763c391dcc6..cb7f53424958 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -649,10 +649,6 @@ const translations = {
emoji: 'Emoji',
collapse: 'Colapsar',
expand: 'Expandir',
- tooltip: {
- title: '¡Empecemos!',
- subtitle: ' Presenta tu primer gasto',
- },
},
reportActionContextMenu: {
copyToClipboard: 'Copiar al portapapeles',
@@ -842,10 +838,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',
@@ -4721,7 +4713,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',
@@ -6094,6 +6085,44 @@ const translations = {
crossPlatform: 'Haz todo desde tu teléfono o navegador',
},
},
+ productTrainingTooltip: {
+ conciergeLHNGBR: {
+ part1: '¡Comienza',
+ 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Ã',
+ },
+ searchFilterButtonTooltip: {
+ part1: 'Personaliza tu búsqueda',
+ part2: ' aquÃ!',
+ },
+ bottomNavInboxTooltip: {
+ part1: 'Tu lista de tareas',
+ part2: '\n🟢 = listo para ti',
+ part3: ' 🔴 = necesita revisión',
+ },
+ workspaceChatTooltip: {
+ part1: 'EnvÃa gastos',
+ part2: ' y chatea con',
+ part3: '\naprobadores aquÃ!',
+ },
+ globalCreateTooltip: {
+ part1: 'Crea gastos',
+ part2: ', comienza a chatear,',
+ part3: '\ny mucho más!',
+ },
+ },
};
export default translations satisfies TranslationDeepObject;
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx
index b478f09c2e01..5647c31c6604 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx
@@ -4,13 +4,17 @@ import {useOnyx} from 'react-native-onyx';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithFeedback} from '@components/Pressable';
+import {useProductTrainingContext} from '@components/ProductTrainingContext';
import type {SearchQueryString} from '@components/Search/types';
import Text from '@components/Text';
+import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
+import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused';
import useCurrentReportID from '@hooks/useCurrentReportID';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import getPlatform from '@libs/getPlatform';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import Navigation from '@libs/Navigation/Navigation';
import type {AuthScreensParamList, RootStackParamList, State} from '@libs/Navigation/types';
@@ -77,7 +81,13 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
const [chatTabBrickRoad, setChatTabBrickRoad] = useState(() =>
getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations),
);
-
+ const isFocused = useBottomTabIsFocused();
+ const platform = getPlatform();
+ const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP;
+ const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.BOTTOM_NAV_INBOX_TOOLTIP,
+ selectedTab !== SCREENS.HOME && isFocused,
+ );
useEffect(() => {
setChatTabBrickRoad(getChatTabBrickRoad(activeWorkspaceID, currentReportID, reports, betas, policies, priorityMode, transactionViolations));
// We need to get a new brick road state when report actions are updated, otherwise we'll be showing an outdated brick road.
@@ -136,30 +146,49 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) {
/>
)}
-
-
-
- {!!chatTabBrickRoad && (
-
- )}
-
-
- {translate('common.inbox')}
-
-
+
+
+ {!!chatTabBrickRoad && (
+
+ )}
+
+
+ {translate('common.inbox')}
+
+
+
): boolean {
return !!betas?.includes(CONST.BETAS.PER_DIEM) || canUseAllBetas(betas);
}
-// TEMPORARY BETA TO HIDE PRODUCT TRAINING TOOLTIP AND MIGRATE USER WELCOME MODAL
-function shouldShowProductTrainingElements(betas: OnyxEntry): boolean {
- return !!betas?.includes(CONST.BETAS.PRODUCT_TRAINING) || canUseAllBetas(betas);
-}
-
/**
* Link previews are temporarily disabled.
*/
@@ -55,5 +50,4 @@ export default {
canUseCombinedTrackSubmit,
canUseCategoryAndTagApprovers,
canUsePerDiem,
- shouldShowProductTrainingElements,
};
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index b5c76a6009ef..ff0b644bdaee 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -394,14 +394,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,
@@ -414,8 +406,6 @@ export {
clearAllFilters,
clearAdvancedFilters,
deleteSavedSearch,
- dismissSavedSearchRenameTooltip,
- showSavedSearchRenameTooltip,
payMoneyRequestOnSearch,
approveMoneyRequestOnSearch,
handleActionButtonPress,
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index fd9d5f1820e6..71973a5adbc3 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -1370,14 +1370,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);
}
@@ -1398,7 +1390,6 @@ export {
closeAccount,
dismissReferralBanner,
dismissTrackTrainingModal,
- dismissWorkspaceTooltip,
resendValidateCode,
requestContactMethodValidateCode,
updateNewsletterSubscription,
@@ -1432,7 +1423,6 @@ export {
addPendingContactMethod,
clearValidateCodeActionError,
subscribeToActiveGuides,
- dismissGBRTooltip,
setIsDebugModeEnabled,
resetValidateActionCodeSent,
};
diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts
index fcb3916256d8..585f5a59937c 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 {
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,
diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx
index 02eca4b9fbbc..10c8401b98aa 100644
--- a/src/pages/Search/AdvancedSearchFilters.tsx
+++ b/src/pages/Search/AdvancedSearchFilters.tsx
@@ -439,11 +439,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,
});
diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx
index 5c93a3877ff6..6d6543554869 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';
import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider';
import ScrollView from '@components/ScrollView';
import type {SearchQueryJSON} from '@components/Search/types';
@@ -62,14 +63,18 @@ 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 {isOffline} = useNetwork();
+ const shouldShowSavedSearchesMenuItemTitle = Object.values(savedSearches ?? {}).filter((s) => s.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length > 0;
+ const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH,
+ shouldShowSavedSearchesMenuItemTitle,
+ );
const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch();
const [session] = useOnyx(ONYXKEYS.SESSION);
const personalDetails = usePersonalDetails();
const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);
const taxRates = getAllTaxRates();
- const {isOffline} = useNetwork();
const typeMenuItems: SearchTypeMenuItem[] = [
{
@@ -118,65 +123,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 && shouldShowSavedSearchRenameTooltip === true,
- 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: SearchActions.dismissSavedSearchRenameTooltip,
- renderTooltipContent: () => {
- return (
-
-
- {translate('search.saveSearchTooltipText')}
-
- );
+ 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,
};
- }
- return baseMenuItem;
- };
+ if (!isNarrow) {
+ return {
+ ...baseMenuItem,
+ shouldRenderTooltip: index === 0 && shouldShowProductTrainingTooltip,
+ 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: hideProductTrainingTooltip,
+ renderTooltipContent: renderProductTrainingTooltip,
+ };
+ }
+ return baseMenuItem;
+ },
+ [
+ hash,
+ getOverflowMenu,
+ styles.alignItemsCenter,
+ styles.bgPaleGreen,
+ styles.mh4,
+ styles.pv2,
+ personalDetails,
+ reports,
+ taxRates,
+ shouldShowProductTrainingTooltip,
+ hideProductTrainingTooltip,
+ renderProductTrainingTooltip,
+ ],
+ );
const route = useRoute();
const scrollViewRef = useRef(null);
@@ -201,12 +210,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[]) => (
@@ -240,7 +249,6 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
/>
);
}
- const shouldShowSavedSearchesMenuItemTitle = Object.values(savedSearches ?? {}).filter((s) => s.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length > 0;
return (
(null);
+ const platform = getPlatform();
+ const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP;
+
const openMenu = useCallback(() => setIsPopoverVisible(true), []);
const closeMenu = useCallback(() => setIsPopoverVisible(false), []);
const onPress = () => {
@@ -75,6 +81,9 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title,
SearchActions.updateAdvancedFilters(values);
Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS);
};
+ const {renderProductTrainingTooltip, shouldShowProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.SEARCH_FILTER_BUTTON_TOOLTIP,
+ );
const currentSavedSearch = savedSearchesMenuItems.find((item) => Number(item.hash) === hash);
@@ -200,10 +209,24 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title,
)}
-
+
+
+
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);
@@ -859,7 +858,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro
lastReportAction={lastReportAction}
showSoftInputOnFocus={showSoftInputOnFocus}
setShowSoftInputOnFocus={setShowSoftInputOnFocus}
- workspaceTooltip={workspaceTooltip}
/>
) : null}
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 3659a638fca9..c3caaf16a3ef 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -12,14 +12,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';
@@ -28,7 +26,6 @@ import useHandleExceedMaxTaskTitleLength from '@hooks/useHandleExceedMaxTaskTitl
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';
@@ -127,7 +124,6 @@ function ReportActionCompose({
setShowSoftInputOnFocus,
didHideComposerInput,
}: ReportActionComposeProps) {
- const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
@@ -140,6 +136,11 @@ function ReportActionCompose({
const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE);
const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT);
+ const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.WORKSAPCE_CHAT_CREATE,
+ shouldShowEducationalTooltip,
+ );
+
/**
* Updates the Highlight state of the composer
*/
@@ -391,34 +392,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 validateMaxLength = useCallback(
(value: string) => {
const taskCommentMatch = value?.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION);
@@ -459,10 +432,10 @@ function ReportActionCompose({
contentContainerStyle={isComposerFullSize ? styles.flex1 : {}}
>
;
- /** Whether to show educational tooltip in workspace chat for first-time user */
- workspaceTooltip: OnyxEntry;
-
/** The pending action when we are adding a chat */
pendingAction?: PendingAction;
@@ -79,7 +76,6 @@ function ReportFooter({
isReportReadyForDisplay = true,
isComposerFullSize = false,
showSoftInputOnFocus,
- workspaceTooltip,
onComposerBlur,
onComposerFocus,
setShowSoftInputOnFocus,
@@ -122,7 +118,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 = ReportUtils.isPolicyExpenseChat(report) && !!report.isOwnPolicyExpenseChat && !isUserPolicyAdmin;
const allPersonalDetails = usePersonalDetails();
@@ -253,7 +249,6 @@ export default memo(
prevProps.lastReportAction === nextProps.lastReportAction &&
prevProps.isReportReadyForDisplay === nextProps.isReportReadyForDisplay &&
prevProps.showSoftInputOnFocus === nextProps.showSoftInputOnFocus &&
- 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),
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
index f5cba2cb0edd..35c0bc5e0b1c 100644
--- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
+++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
@@ -11,7 +11,7 @@ import FloatingActionButton from '@components/FloatingActionButton';
import * as Expensicons from '@components/Icon/Expensicons';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import PopoverMenu from '@components/PopoverMenu';
-import Text from '@components/Text';
+import {useProductTrainingContext} from '@components/ProductTrainingContext';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
@@ -207,6 +207,12 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
const [hasSeenTour = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {
selector: hasSeenTourSelector,
});
+
+ const {renderProductTrainingTooltip, hideProductTrainingTooltip, shouldShowProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.QUICK_ACTION_BUTTON,
+ isCreateMenuActive && (!shouldUseNarrowLayout || isFocused),
+ );
+
/**
* 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:
* 1. on at least one group policy
@@ -233,16 +239,6 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [personalDetails, session?.accountID, quickActionReport, quickActionPolicy, policyChatForActivePolicy]);
- const renderQuickActionTooltip = useCallback(
- () => (
-
- {translate('quickAction.tooltip.title')}
- {translate('quickAction.tooltip.subtitle')}
-
- ),
- [styles.quickActionTooltipTitle, styles.quickActionTooltipSubtitle, translate],
- );
-
const quickActionTitle = useMemo(() => {
if (isEmptyObject(quickActionReport)) {
return '';
@@ -381,8 +377,10 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
},
tooltipShiftHorizontal: styles.popoverMenuItem.paddingHorizontal,
tooltipShiftVertical: styles.popoverMenuItem.paddingVertical / 2,
- renderTooltipContent: renderQuickActionTooltip,
- tooltipWrapperStyle: styles.quickActionTooltipWrapper,
+ renderTooltipContent: renderProductTrainingTooltip,
+ tooltipWrapperStyle: styles.productTrainingTooltipWrapper,
+ onHideTooltip: hideProductTrainingTooltip,
+ shouldRenderTooltip: shouldShowProductTrainingTooltip,
};
if (quickAction?.action) {
@@ -401,7 +399,6 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
QuickActionNavigation.navigateToQuickAction(isValidReport, `${quickActionReport?.reportID ?? CONST.DEFAULT_NUMBER_ID}`, quickAction, selectOption),
),
shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport),
- shouldRenderTooltip: quickAction.isFirstQuickAction,
},
];
}
@@ -420,7 +417,6 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
}, true);
}),
shouldShowSubscriptRightAvatar: true,
- shouldRenderTooltip: false,
},
];
}
@@ -434,13 +430,15 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
styles.popoverMenuItem.paddingVertical,
styles.pt3,
styles.pb2,
+ styles.productTrainingTooltipWrapper,
+ renderProductTrainingTooltip,
+ hideProductTrainingTooltip,
quickAction,
- styles.quickActionTooltipWrapper,
- renderQuickActionTooltip,
policyChatForActivePolicy,
quickActionTitle,
hideQABSubtitle,
quickActionReport,
+ shouldShowProductTrainingTooltip,
selectOption,
quickActionPolicy,
]);
diff --git a/src/styles/index.ts b/src/styles/index.ts
index a6c4a39f3a56..139207835685 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -4006,19 +4006,15 @@ const styles = (theme: ThemeColors) =>
borderRadius: variables.componentBorderRadiusMedium,
},
- quickActionTooltipWrapper: {
+ productTrainingTooltipWrapper: {
backgroundColor: theme.tooltipHighlightBG,
+ borderRadius: variables.componentBorderRadiusNormal,
},
- quickActionTooltipTitle: {
- ...FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
- fontSize: variables.fontSizeLabel,
- color: theme.tooltipHighlightText,
- },
-
- quickActionTooltipSubtitle: {
+ productTrainingTooltipText: {
fontSize: variables.fontSizeLabel,
color: theme.textDark,
+ lineHeight: variables.lineHeightLarge,
},
quickReactionsContainer: {
diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts
index 223fc1c56818..68132de5fcad 100644
--- a/src/styles/theme/themes/dark.ts
+++ b/src/styles/theme/themes/dark.ts
@@ -81,7 +81,7 @@ const darkTheme = {
ourMentionText: colors.green100,
ourMentionBG: colors.green600,
tooltipHighlightBG: colors.green100,
- tooltipHighlightText: colors.green500,
+ tooltipHighlightText: colors.green400,
tooltipSupportingText: colors.productLight800,
tooltipPrimaryText: colors.productLight900,
trialBannerBackgroundColor: colors.green700,
diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts
index 151388e77136..7be69e5461d1 100644
--- a/src/styles/theme/themes/light.ts
+++ b/src/styles/theme/themes/light.ts
@@ -81,7 +81,7 @@ const lightTheme = {
ourMentionText: colors.green600,
ourMentionBG: colors.green100,
tooltipHighlightBG: colors.green100,
- tooltipHighlightText: colors.green500,
+ tooltipHighlightText: colors.green400,
tooltipSupportingText: colors.productDark800,
tooltipPrimaryText: colors.productDark900,
trialBannerBackgroundColor: colors.green100,
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index b49d5784a905..174e23fe64a9 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -258,6 +258,12 @@ export default {
composerTooltipShiftHorizontal: 10,
composerTooltipShiftVertical: -10,
gbrTooltipShiftHorizontal: -20,
+ fabTooltipShiftHorizontal: -15,
+ workspaceLHNtooltipShiftHorizontal: 26,
+ searchFiltersTooltipShiftHorizontal: -20,
+ searchFiltersTooltipShiftHorizontalNarrow: -10,
+ searchFiltersTooltipShiftVerticalNarrow: 5,
+ bottomTabInboxTooltipShiftHorizontal: 36,
inlineImagePreviewMinSize: 64,
inlineImagePreviewMaxSize: 148,
diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts
index 9539bc9f0187..aba386448c09 100644
--- a/src/types/onyx/DismissedProductTraining.ts
+++ b/src/types/onyx/DismissedProductTraining.ts
@@ -1,3 +1,15 @@
+import CONST from '@src/CONST';
+
+const {
+ CONCEIRGE_LHN_GBR,
+ RENAME_SAVED_SEARCH,
+ WORKSAPCE_CHAT_CREATE,
+ QUICK_ACTION_BUTTON,
+ SEARCH_FILTER_BUTTON_TOOLTIP,
+ BOTTOM_NAV_INBOX_TOOLTIP,
+ LHN_WORKSPACE_CHAT_TOOLTIP,
+ GLOBAL_CREATE_TOOLTIP,
+} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES;
/**
* This type is used to store the timestamp of when the user dismisses a product training ui elements.
*/
@@ -5,7 +17,47 @@ type DismissedProductTraining = {
/**
* When user dismisses the nudgeMigration Welcome Modal, we store the timestamp here.
*/
- migratedUserWelcomeModal: Date;
+ [CONST.MIGRATED_USER_WELCOME_MODAL]: string;
+
+ /**
+ * When user dismisses the conciergeLHNGBR product training tooltip, we store the timestamp here.
+ */
+ [CONCEIRGE_LHN_GBR]: string;
+
+ /**
+ * When user dismisses the renameSavedSearch product training tooltip, we store the timestamp here.
+ */
+ [RENAME_SAVED_SEARCH]: string;
+
+ /**
+ * When user dismisses the workspaceChatCreate product training tooltip, we store the timestamp here.
+ */
+ [WORKSAPCE_CHAT_CREATE]: string;
+
+ /**
+ * When user dismisses the quickActionButton product training tooltip, we store the timestamp here.
+ */
+ [QUICK_ACTION_BUTTON]: string;
+
+ /**
+ * When user dismisses the searchFilterButtonTooltip product training tooltip, we store the timestamp here.
+ */
+ [SEARCH_FILTER_BUTTON_TOOLTIP]: string;
+
+ /**
+ * When user dismisses the bottomNavInboxTooltip product training tooltip, we store the timestamp here.
+ */
+ [BOTTOM_NAV_INBOX_TOOLTIP]: string;
+
+ /**
+ * When user dismisses the lhnWorkspaceChatTooltip product training tooltip, we store the timestamp here.
+ */
+ [LHN_WORKSPACE_CHAT_TOOLTIP]: string;
+
+ /**
+ * When user dismisses the globalCreateTooltip product training tooltip, we store the timestamp here.
+ */
+ [GLOBAL_CREATE_TOOLTIP]: string;
};
export default DismissedProductTraining;
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 a6fd6d418c90..9d4d319d05e8 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -111,7 +111,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,
@@ -235,7 +234,6 @@ export type {
CancellationDetails,
ApprovalWorkflowOnyx,
MobileSelectionMode,
- WorkspaceTooltip,
CardFeeds,
SaveSearch,
RecentSearchItem,