diff --git a/android/app/build.gradle b/android/app/build.gradle
index 43851deff95e..1f5d09b62f00 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -110,8 +110,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009007609
- versionName "9.0.76-9"
+ versionCode 1009007610
+ versionName "9.0.76-10"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 953ffb928a4f..4be40aa649ac 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.76.9
+ 9.0.76.10
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 07a4ed95f3fe..e34521b9561a 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 9.0.76.9
+ 9.0.76.10
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 9189028b43a9..76fb08c2fdb9 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
9.0.76
CFBundleVersion
- 9.0.76.9
+ 9.0.76.10
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 4aa25442aff5..5f8ac08f09fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.76-9",
+ "version": "9.0.76-10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.76-9",
+ "version": "9.0.76-10",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 0b65f2438d51..d8a343883a6a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.76-9",
+ "version": "9.0.76-10",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/App.tsx b/src/App.tsx
index cc824b78fa4c..52904e0a06c4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -18,7 +18,6 @@ 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';
@@ -96,7 +95,6 @@ function App({url}: AppProps) {
VideoPopoverMenuContextProvider,
KeyboardProvider,
SearchRouterContextProvider,
- ProductTrainingContextProvider,
]}
>
diff --git a/src/CONST.ts b/src/CONST.ts
index e7d9411b619c..8ed8b0a84468 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -6408,13 +6408,6 @@ 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',
- },
} as const;
type Country = keyof typeof CONST.ALL_COUNTRIES;
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 2e65b5f372b4..45d636c0b1df 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -111,6 +111,9 @@ 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',
@@ -213,9 +216,18 @@ 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',
@@ -864,6 +876,7 @@ 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 | [];
@@ -1004,7 +1017,9 @@ 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;
@@ -1012,6 +1027,7 @@ 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/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index 6b8cf173b0fd..c423d3101d92 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -11,7 +11,6 @@ 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';
@@ -23,6 +22,7 @@ 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,6 +32,7 @@ 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,19 +48,18 @@ 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 [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 = shouldShowToooltipOnThisReport && isScreenFocused;
- const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
- CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCEIRGE_LHN_GBR,
- shouldShowGetStartedTooltip,
- );
const {translate} = useLocalize();
const [isContextMenuActive, setIsContextMenuActive] = useState(false);
@@ -72,6 +72,30 @@ 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
@@ -156,17 +180,17 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
needsOffscreenAlphaCompositing
>
diff --git a/src/hooks/useOnboardingFlow.ts b/src/hooks/useOnboardingFlow.ts
index 84ac886cf88b..e4063469519b 100644
--- a/src/hooks/useOnboardingFlow.ts
+++ b/src/hooks/useOnboardingFlow.ts
@@ -78,8 +78,8 @@ function useOnboardingFlowRouter() {
dismissedProductTrainingMetadata,
dismissedProductTraining?.migratedUserWelcomeModal,
dismissedProductTraining,
- allBetasMetadata,
allBetas,
+ allBetasMetadata,
]);
return {isOnboardingCompleted, isHybridAppOnboardingCompleted};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index d8499eea0dd4..08c3dadef38f 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -647,6 +647,10 @@ const translations = {
emoji: 'Emoji',
collapse: 'Collapse',
expand: 'Expand',
+ tooltip: {
+ title: 'Get started!',
+ subtitle: ' Submit your first expense',
+ },
},
reportActionContextMenu: {
copyToClipboard: 'Copy to clipboard',
@@ -833,6 +837,10 @@ 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',
@@ -4546,6 +4554,7 @@ 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',
@@ -5447,25 +5456,6 @@ 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!',
- },
- },
};
export default translations satisfies TranslationDeepObject;
diff --git a/src/languages/es.ts b/src/languages/es.ts
index bc7ef7f1dd84..fca148f7c833 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -639,6 +639,10 @@ const translations = {
emoji: 'Emoji',
collapse: 'Colapsar',
expand: 'Expandir',
+ tooltip: {
+ title: '¡Empecemos!',
+ subtitle: ' Presenta tu primer gasto',
+ },
},
reportActionContextMenu: {
copyToClipboard: 'Copiar al portapapeles',
@@ -828,6 +832,10 @@ 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',
@@ -4595,6 +4603,7 @@ 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',
@@ -5967,25 +5976,6 @@ 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í',
- },
- },
};
export default translations satisfies TranslationDeepObject;
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 50e37ba6afe5..8ef5802b80dc 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -380,6 +380,14 @@ 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,
@@ -392,6 +400,8 @@ export {
clearAllFilters,
clearAdvancedFilters,
deleteSavedSearch,
+ dismissSavedSearchRenameTooltip,
+ showSavedSearchRenameTooltip,
payMoneyRequestOnSearch,
approveMoneyRequestOnSearch,
handleActionButtonPress,
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 4aa8b1e7ec1f..e23422d083ca 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -1362,6 +1362,14 @@ 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);
}
@@ -1382,6 +1390,7 @@ export {
closeAccount,
dismissReferralBanner,
dismissTrackTrainingModal,
+ dismissWorkspaceTooltip,
resendValidateCode,
requestContactMethodValidateCode,
updateNewsletterSubscription,
@@ -1415,5 +1424,6 @@ export {
addPendingContactMethod,
clearValidateCodeActionError,
subscribeToActiveGuides,
+ dismissGBRTooltip,
setIsDebugModeEnabled,
};
diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts
index f91ab98f7b1d..b306daf444ba 100644
--- a/src/libs/actions/Welcome/index.ts
+++ b/src/libs/actions/Welcome/index.ts
@@ -207,16 +207,20 @@ 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});
+ // 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()),
+ });
}
export {
diff --git a/src/libs/migrations/NVPMigration.ts b/src/libs/migrations/NVPMigration.ts
index 8c743e66e79f..20f3d0a86495 100644
--- a/src/libs/migrations/NVPMigration.ts
+++ b/src/libs/migrations/NVPMigration.ts
@@ -9,6 +9,7 @@ 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 10c8401b98aa..02eca4b9fbbc 100644
--- a/src/pages/Search/AdvancedSearchFilters.tsx
+++ b/src/pages/Search/AdvancedSearchFilters.tsx
@@ -439,6 +439,11 @@ 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 4a3c0881b5a6..5c93a3877ff6 100644
--- a/src/pages/Search/SearchTypeMenu.tsx
+++ b/src/pages/Search/SearchTypeMenu.tsx
@@ -8,7 +8,6 @@ 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';
@@ -63,7 +62,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
const {singleExecution} = useSingleExecution();
const {translate} = useLocalize();
const [savedSearches] = useOnyx(ONYXKEYS.SAVED_SEARCHES);
- const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.RENAME_SAVED_SEARCH);
+ const [shouldShowSavedSearchRenameTooltip] = useOnyx(ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP);
const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch();
const [session] = useOnyx(ONYXKEYS.SESSION);
@@ -119,69 +118,65 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
[showDeleteModal],
);
- 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);
- }
+ 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 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}));
+ 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')}
+
+ );
},
- rightComponent: (
-
- ),
- styles: [styles.alignItemsCenter],
- pendingAction: item.pendingAction,
- disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- shouldIconUseAutoWidthStyle: true,
};
+ }
- 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,
- ],
- );
+ return baseMenuItem;
+ };
const route = useRoute();
const scrollViewRef = useRef(null);
@@ -206,12 +201,12 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) {
scrollViewRef.current.scrollTo({y: scrollOffset, animated: false});
}, [getScrollOffset, route]);
- const savedSearchesMenuItems = useCallback(() => {
+ const savedSearchesMenuItems = () => {
if (!savedSearches) {
return [];
}
return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout, index));
- }, [createSavedSearchMenuItem, savedSearches, shouldUseNarrowLayout]);
+ };
const renderSavedSearchesSection = useCallback(
(menuItems: MenuItemWithLink[]) => (
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 47315b604f0b..97582f75b7b1 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -131,6 +131,7 @@ 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);
@@ -864,6 +865,7 @@ 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 78d3288d05f0..893d2b3060d9 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -12,12 +12,14 @@ 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 {useProductTrainingContext} from '@components/ProductTrainingContext';
+import Text from '@components/Text';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebounce from '@hooks/useDebounce';
@@ -26,6 +28,7 @@ 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';
@@ -113,6 +116,7 @@ function ReportActionCompose({
onComposerFocus,
onComposerBlur,
}: ReportActionComposeProps) {
+ const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
@@ -125,11 +129,6 @@ 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
*/
@@ -381,6 +380,34 @@ 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);
@@ -421,10 +448,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;
@@ -73,6 +76,7 @@ function ReportFooter({
isEmptyChat = true,
isReportReadyForDisplay = true,
isComposerFullSize = false,
+ workspaceTooltip,
onComposerBlur,
onComposerFocus,
}: ReportFooterProps) {
@@ -114,7 +118,7 @@ function ReportFooter({
const isSystemChat = ReportUtils.isSystemChat(report);
const isAdminsOnlyPostingRoom = ReportUtils.isAdminsOnlyPostingRoom(report);
const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy);
- const shouldShowEducationalTooltip = ReportUtils.isPolicyExpenseChat(report) && !!report.isOwnPolicyExpenseChat && !isUserPolicyAdmin;
+ const shouldShowEducationalTooltip = !!workspaceTooltip?.shouldShow && !isUserPolicyAdmin;
const allPersonalDetails = usePersonalDetails();
@@ -234,6 +238,7 @@ 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),
diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx
index 6d74a910f455..10ca79a9d8e7 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 {useProductTrainingContext} from '@components/ProductTrainingContext';
+import Text from '@components/Text';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
@@ -206,12 +206,6 @@ 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
@@ -238,6 +232,16 @@ 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 '';
@@ -404,10 +408,8 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
},
tooltipShiftHorizontal: styles.popoverMenuItem.paddingHorizontal,
tooltipShiftVertical: styles.popoverMenuItem.paddingVertical / 2,
- renderTooltipContent: renderProductTrainingTooltip,
+ renderTooltipContent: renderQuickActionTooltip,
tooltipWrapperStyle: styles.quickActionTooltipWrapper,
- onHideTooltip: hideProductTrainingTooltip,
- shouldRenderTooltip: shouldShowProductTrainingTooltip,
};
if (quickAction?.action) {
@@ -423,6 +425,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
description: !hideQABSubtitle ? ReportUtils.getReportName(quickActionReport) ?? translate('quickAction.updateDestination') : '',
onSelected: () => interceptAnonymousUser(() => navigateToQuickAction()),
shouldShowSubscriptRightAvatar: ReportUtils.isPolicyExpenseChat(quickActionReport),
+ shouldRenderTooltip: quickAction.isFirstQuickAction,
},
];
}
@@ -441,6 +444,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
}, true);
}),
shouldShowSubscriptRightAvatar: true,
+ shouldRenderTooltip: false,
},
];
}
@@ -452,14 +456,13 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl
styles.popoverMenuItem.paddingHorizontal,
styles.popoverMenuItem.paddingVertical,
styles.quickActionTooltipWrapper,
- renderProductTrainingTooltip,
- hideProductTrainingTooltip,
+ renderQuickActionTooltip,
quickAction?.action,
+ quickAction?.isFirstQuickAction,
policyChatForActivePolicy,
quickActionTitle,
hideQABSubtitle,
quickActionReport,
- shouldShowProductTrainingTooltip,
navigateToQuickAction,
selectOption,
isValidReport,
diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts
index fe24dc061aee..9539bc9f0187 100644
--- a/src/types/onyx/DismissedProductTraining.ts
+++ b/src/types/onyx/DismissedProductTraining.ts
@@ -1,6 +1,3 @@
-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.
*/
@@ -8,27 +5,7 @@ type DismissedProductTraining = {
/**
* When user dismisses the nudgeMigration Welcome Modal, we store the timestamp here.
*/
- [CONST.MIGRATED_USER_WELCOME_MODAL]: Date;
-
- /**
- * When user dismisses the conciergeLHNGBR product training tooltip, we store the timestamp here.
- */
- [CONCEIRGE_LHN_GBR]: Date;
-
- /**
- * When user dismisses the renameSavedSearch product training tooltip, we store the timestamp here.
- */
- [RENAME_SAVED_SEARCH]: Date;
-
- /**
- * When user dismisses the workspaceChatCreate product training tooltip, we store the timestamp here.
- */
- [WORKSAPCE_CHAT_CREATE]: Date;
-
- /**
- * When user dismisses the quickActionButton product training tooltip, we store the timestamp here.
- */
- [QUICK_ACTION_BUTTON]: Date;
+ migratedUserWelcomeModal: Date;
};
export default DismissedProductTraining;
diff --git a/src/types/onyx/WorkspaceTooltip.ts b/src/types/onyx/WorkspaceTooltip.ts
new file mode 100644
index 000000000000..4371ac6533d8
--- /dev/null
+++ b/src/types/onyx/WorkspaceTooltip.ts
@@ -0,0 +1,9 @@
+/**
+ * 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 eeda322f6205..20b7d047a092 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -110,6 +110,7 @@ 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,
@@ -233,6 +234,7 @@ export type {
CancellationDetails,
ApprovalWorkflowOnyx,
MobileSelectionMode,
+ WorkspaceTooltip,
CardFeeds,
SaveSearch,
RecentSearchItem,