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,