diff --git a/src/CONST.ts b/src/CONST.ts
index 789d5aa90b028..bad481f5f343f 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -6991,6 +6991,8 @@ const CONST = {
SCAN_TEST_TOOLTIP: 'scanTestTooltip',
SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager',
SCAN_TEST_CONFIRMATION: 'scanTestConfirmation',
+ GBR_RBR_CHAT: 'chatGBRRBR',
+ ACCOUNT_SWITCHER: 'accountSwitcher',
EXPENSE_REPORTS_FILTER: 'expenseReportsFilter',
},
CHANGE_POLICY_TRAINING_MODAL: 'changePolicyModal',
diff --git a/src/components/AccountSwitcher.tsx b/src/components/AccountSwitcher.tsx
index 0f762cc4598f0..a0458cf2825c0 100644
--- a/src/components/AccountSwitcher.tsx
+++ b/src/components/AccountSwitcher.tsx
@@ -26,10 +26,17 @@ import * as Expensicons from './Icon/Expensicons';
import type {PopoverMenuItem} from './PopoverMenu';
import PopoverMenu from './PopoverMenu';
import {PressableWithFeedback} from './Pressable';
+import {useProductTrainingContext} from './ProductTrainingContext';
import Text from './Text';
import Tooltip from './Tooltip';
+import EducationalTooltip from './Tooltip/EducationalTooltip';
-function AccountSwitcher() {
+type AccountSwitcherProps = {
+ /* Whether the screen is focused. Used to hide the product training tooltip */
+ isScreenFocused: boolean;
+};
+
+function AccountSwitcher({isScreenFocused}: AccountSwitcherProps) {
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const styles = useThemeStyles();
const theme = useTheme();
@@ -47,10 +54,42 @@ function AccountSwitcher() {
const [shouldShowOfflineModal, setShouldShowOfflineModal] = useState(false);
const delegators = account?.delegatedAccess?.delegators ?? [];
- const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false;
+ const isActingAsDelegate = !!account?.delegatedAccess?.delegate;
const canSwitchAccounts = delegators.length > 0 || isActingAsDelegate;
const accountSwitcherPopoverStyle = canUseLeftHandBar ? styles.accountSwitcherPopoverWithLHB : styles.accountSwitcherPopover;
+ const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
+ CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.ACCOUNT_SWITCHER,
+ isScreenFocused && canSwitchAccounts,
+ );
+
+ const onPressSwitcher = () => {
+ hideProductTrainingTooltip();
+ setShouldShowDelegatorMenu(!shouldShowDelegatorMenu);
+ };
+
+ const TooltipToRender = shouldShowProductTrainingTooltip ? EducationalTooltip : Tooltip;
+ const tooltipProps = shouldShowProductTrainingTooltip
+ ? {
+ shouldRender: shouldShowProductTrainingTooltip,
+ renderTooltipContent: renderProductTrainingTooltip,
+ anchorAlignment: {
+ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
+ vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
+ },
+ shiftVertical: variables.accountSwitcherTooltipShiftVertical,
+ shiftHorizontal: variables.accountSwitcherTooltipShiftHorizontal,
+ wrapperStyle: styles.productTrainingTooltipWrapper,
+ onTooltipPress: onPressSwitcher,
+ }
+ : {
+ text: translate('delegate.copilotAccess'),
+ shiftVertical: 8,
+ shiftHorizontal: 8,
+ anchorAlignment: {horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM},
+ shouldRender: canSwitchAccounts,
+ };
+
const createBaseMenuItem = (
personalDetails: PersonalDetails | undefined,
errors?: Errors,
@@ -133,19 +172,12 @@ function AccountSwitcher() {
return (
<>
-
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+
{
- setShouldShowDelegatorMenu(!shouldShowDelegatorMenu);
- }}
+ onPress={onPressSwitcher}
ref={buttonRef}
interactive={canSwitchAccounts}
pressDimmingValue={canSwitchAccounts ? undefined : 1}
@@ -195,7 +227,8 @@ function AccountSwitcher() {
-
+
+
{!!canSwitchAccounts && (
{
+ if (!shouldShowProductTrainingTooltip) {
+ return undefined;
+ }
+ return data.find((reportID) => {
+ const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
+ const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
+ if (!itemFullReport) {
+ return false;
+ }
+ if (hasReportErrors(itemFullReport, itemReportActions)) {
+ return true;
+ }
+ const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`];
+ const itemParentReportAction = itemFullReport?.parentReportActionID ? itemParentReportActions?.[itemFullReport?.parentReportActionID] : undefined;
+ const hasGBR = requiresAttentionFromCurrentUser(itemFullReport, itemParentReportAction);
+ return hasGBR;
+ });
+ }, [shouldShowProductTrainingTooltip, data, reportActions, reports]);
+
// When the first item renders we want to call the onFirstItemRendered callback.
// At this point in time we know that the list is actually displaying items.
const hasCalledOnLayout = React.useRef(false);
@@ -181,6 +203,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
}
const lastMessageTextFromReport = getLastMessageTextForReport(itemFullReport, lastActorDetails, itemPolicy, itemReportNameValuePairs);
+ const shouldShowRBRorGBRTooltip = firstReportIDWithGBRorRBR === reportID;
+
return (
);
},
@@ -224,6 +249,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
transactionViolations,
onLayoutItem,
isOffline,
+ firstReportIDWithGBRorRBR,
],
);
diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx
index dc88d5528704f..80223372ca807 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -12,6 +12,7 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {useSession} from '@components/OnyxProvider';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
+import type {ProductTrainingTooltipName} from '@components/ProductTrainingContext/TOOLTIPS';
import SubscriptAvatar from '@components/SubscriptAvatar';
import Text from '@components/Text';
import Tooltip from '@components/Tooltip';
@@ -49,7 +50,17 @@ import ONYXKEYS from '@src/ONYXKEYS';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {OptionRowLHNProps} from './types';
-function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment}: OptionRowLHNProps) {
+function OptionRowLHN({
+ reportID,
+ isFocused = false,
+ onSelectRow = () => {},
+ optionItem,
+ viewMode = 'default',
+ style,
+ onLayout = () => {},
+ hasDraftComment,
+ shouldShowRBRorGBRTooltip,
+}: OptionRowLHNProps) {
const theme = useTheme();
const styles = useThemeStyles();
const popoverAnchor = useRef(null);
@@ -57,10 +68,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
const [isScreenFocused, setIsScreenFocused] = useState(false);
const {shouldUseNarrowLayout} = useResponsiveLayout();
- const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID}`);
- const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
- const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
- const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY);
+ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID}`, {canBeMissing: true});
+ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
+ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
+ const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY, {canBeMissing: true});
const session = useSession();
const shouldShowWorkspaceChatTooltip = isPolicyExpenseChat(report) && !isThread(report) && activePolicyID === report?.policyID && session?.accountID === report?.ownerAccountID;
const isOnboardingGuideAssigned = introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+');
@@ -69,16 +80,25 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
const isReportsSplitNavigatorLast = useRootNavigationState((state) => state?.routes?.at(-1)?.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR);
- const {tooltipToRender, shouldShowTooltip} = useMemo(() => {
+ const {tooltipToRender, shouldShowTooltip, shouldTooltipBeLeftAligned} = useMemo(() => {
// TODO: CONCIERGE_LHN_GBR tooltip will be replaced by a tooltip in the #admins room
// https://github.com/Expensify/App/issues/57045#issuecomment-2701455668
- const tooltip = shouldShowGetStartedTooltip ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCIERGE_LHN_GBR : CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP;
- const shouldShowTooltips = shouldShowWorkspaceChatTooltip || shouldShowGetStartedTooltip;
+ let tooltip: ProductTrainingTooltipName = shouldShowGetStartedTooltip
+ ? CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.CONCIERGE_LHN_GBR
+ : CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.LHN_WORKSPACE_CHAT_TOOLTIP;
+ if (shouldShowRBRorGBRTooltip) {
+ tooltip = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GBR_RBR_CHAT;
+ }
+ const shouldShowTooltips = shouldShowRBRorGBRTooltip || shouldShowWorkspaceChatTooltip || shouldShowGetStartedTooltip;
const shouldTooltipBeVisible = shouldUseNarrowLayout ? isScreenFocused && isReportsSplitNavigatorLast : isReportsSplitNavigatorLast && !isFullscreenVisible;
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- return {tooltipToRender: tooltip, shouldShowTooltip: shouldShowTooltips && shouldTooltipBeVisible};
- }, [shouldShowGetStartedTooltip, shouldShowWorkspaceChatTooltip, isScreenFocused, shouldUseNarrowLayout, isReportsSplitNavigatorLast, isFullscreenVisible]);
+ return {
+ tooltipToRender: tooltip,
+ shouldShowTooltip: shouldShowTooltips && shouldTooltipBeVisible,
+ shouldTooltipBeLeftAligned: shouldShowWorkspaceChatTooltip && !shouldShowRBRorGBRTooltip && !shouldShowGetStartedTooltip,
+ };
+ }, [shouldShowRBRorGBRTooltip, shouldShowGetStartedTooltip, shouldShowWorkspaceChatTooltip, isScreenFocused, shouldUseNarrowLayout, isReportsSplitNavigatorLast, isFullscreenVisible]);
const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(tooltipToRender, shouldShowTooltip);
@@ -195,11 +215,11 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
shouldRender={shouldShowProductTrainingTooltip}
renderTooltipContent={renderProductTrainingTooltip}
anchorAlignment={{
- horizontal: shouldShowWorkspaceChatTooltip ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
+ horizontal: shouldTooltipBeLeftAligned ? CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT : CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
- shiftHorizontal={shouldShowWorkspaceChatTooltip ? variables.workspaceLHNTooltipShiftHorizontal : variables.gbrTooltipShiftHorizontal}
- shiftVertical={shouldShowWorkspaceChatTooltip ? 0 : variables.gbrTooltipShiftVertical}
+ shiftHorizontal={shouldTooltipBeLeftAligned ? variables.workspaceLHNTooltipShiftHorizontal : variables.gbrTooltipShiftHorizontal}
+ shiftVertical={shouldTooltipBeLeftAligned ? 0 : variables.gbrTooltipShiftVertical}
wrapperStyle={styles.productTrainingTooltipWrapper}
onTooltipPress={onOptionPress}
shouldHideOnScroll
diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts
index 6e294ff2d845e..7805ea1509d15 100644
--- a/src/components/LHNOptionsList/types.ts
+++ b/src/components/LHNOptionsList/types.ts
@@ -116,6 +116,9 @@ type OptionRowLHNDataProps = {
/** Callback to execute when the OptionList lays out */
onLayout?: (event: LayoutChangeEvent) => void;
+
+ /** Whether to show the educational tooltip for the GBR or RBR */
+ shouldShowRBRorGBRTooltip: boolean;
};
type OptionRowLHNProps = {
@@ -141,6 +144,9 @@ type OptionRowLHNProps = {
hasDraftComment: boolean;
onLayout?: (event: LayoutChangeEvent) => void;
+
+ /** Whether to show the educational tooltip on the GBR or RBR */
+ shouldShowRBRorGBRTooltip: boolean;
};
type RenderItemProps = {item: string};
diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts
index 3d8a6c7eff94e..513888c957e97 100644
--- a/src/components/ProductTrainingContext/TOOLTIPS.ts
+++ b/src/components/ProductTrainingContext/TOOLTIPS.ts
@@ -12,6 +12,8 @@ const {
SCAN_TEST_TOOLTIP,
SCAN_TEST_TOOLTIP_MANAGER,
SCAN_TEST_CONFIRMATION,
+ GBR_RBR_CHAT,
+ ACCOUNT_SWITCHER,
EXPENSE_REPORTS_FILTER,
} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES;
@@ -90,6 +92,29 @@ const TOOLTIPS: Record = {
priority: 1800,
shouldShow: ({isUserPolicyEmployee}) => isUserPolicyEmployee,
},
+ [GBR_RBR_CHAT]: {
+ content: [
+ {text: 'productTrainingTooltip.GBRRBRChat.part1', isBold: false},
+ {text: 'productTrainingTooltip.GBRRBRChat.part2', isBold: true},
+ {text: 'productTrainingTooltip.GBRRBRChat.part3', isBold: false},
+ {text: 'productTrainingTooltip.GBRRBRChat.part4', isBold: true},
+ ],
+ onHideTooltip: () => dismissProductTraining(GBR_RBR_CHAT),
+ name: GBR_RBR_CHAT,
+ priority: 1900,
+ shouldShow: () => true,
+ },
+ [ACCOUNT_SWITCHER]: {
+ content: [
+ {text: 'productTrainingTooltip.accountSwitcher.part1', isBold: false},
+ {text: 'productTrainingTooltip.accountSwitcher.part2', isBold: true},
+ {text: 'productTrainingTooltip.accountSwitcher.part3', isBold: false},
+ ],
+ onHideTooltip: () => dismissProductTraining(ACCOUNT_SWITCHER),
+ name: ACCOUNT_SWITCHER,
+ priority: 1600,
+ shouldShow: () => true,
+ },
[EXPENSE_REPORTS_FILTER]: {
content: [
{text: 'productTrainingTooltip.expenseReportsFilter.part1', isBold: false},
diff --git a/src/languages/en.ts b/src/languages/en.ts
index df9994f6e67b1..ea3d06089f938 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -6227,6 +6227,17 @@ const translations = {
part3: '\nand more.',
part4: ' Try it out!',
},
+ GBRRBRChat: {
+ part1: 'You’ll see 🟢 on ',
+ part2: 'actions to take',
+ part3: ',\nand 🔴 on ',
+ part4: 'errors to review.',
+ },
+ accountSwitcher: {
+ part1: 'Access your ',
+ part2: 'Copilot accounts',
+ part3: ' here',
+ },
expenseReportsFilter: {
part1: 'Welcome! Find all of your',
part2: "\ncompany's reports",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index d610b35efeaf4..187f183cf8d8d 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -6752,6 +6752,17 @@ const translations = {
part3: '\ny más.',
part4: ' ¡Pruébalo!',
},
+ GBRRBRChat: {
+ part1: 'Verás 🟢 en ',
+ part2: 'las acciones a realizar',
+ part3: '\ny 🔴 en ',
+ part4: 'los errores que debes revisar.',
+ },
+ accountSwitcher: {
+ part1: 'Accede a tus ',
+ part2: 'cuentas copiloto',
+ part3: ' aquí',
+ },
expenseReportsFilter: {
part1: '¡Bienvenido! Aquí encontrarás todos los',
part2: '\ninformes de tu empresa',
diff --git a/src/libs/Navigation/helpers/useRouteActive.ts b/src/libs/Navigation/helpers/useRouteActive.ts
new file mode 100644
index 0000000000000..58d1cadb34bf4
--- /dev/null
+++ b/src/libs/Navigation/helpers/useRouteActive.ts
@@ -0,0 +1,16 @@
+import {findFocusedRoute, useNavigationState} from '@react-navigation/native';
+import useRootNavigationState from '@hooks/useRootNavigationState';
+import NAVIGATORS from '@src/NAVIGATORS';
+import SCREENS from '@src/SCREENS';
+
+function useIsAccountSettingsRouteActive(isNarrowLayout: boolean) {
+ const focusedRoute = useNavigationState(findFocusedRoute);
+ const navigationState = useRootNavigationState((x) => x);
+
+ const isSettingsSplitNavigator = navigationState?.routes.at(-1)?.name === NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR;
+ const isAccountSettings = focusedRoute?.name === SCREENS.SETTINGS.ROOT;
+
+ return isNarrowLayout ? isAccountSettings && isSettingsSplitNavigator : isSettingsSplitNavigator;
+}
+
+export default useIsAccountSettingsRouteActive;
diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx
index a747cac299bab..e8aad5ad9d9ea 100755
--- a/src/pages/settings/InitialSettingsPage.tsx
+++ b/src/pages/settings/InitialSettingsPage.tsx
@@ -34,6 +34,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {resetExitSurveyForm} from '@libs/actions/ExitSurvey';
import {checkIfFeedConnectionIsBroken} from '@libs/CardUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
+import useIsAccountSettingsRouteActive from '@libs/Navigation/helpers/useRouteActive';
import Navigation from '@libs/Navigation/Navigation';
import {getFreeTrialText, hasSubscriptionRedDotError} from '@libs/SubscriptionUtils';
import {getProfilePageBrickRoadIndicator} from '@libs/UserUtils';
@@ -91,7 +92,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true});
const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {canBeMissing: true});
- const [allCards] = useOnyx(`${ONYXKEYS.CARD_LIST}`, {canBeMissing: true});
+ const [allCards] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
const {shouldUseNarrowLayout} = useResponsiveLayout();
const network = useNetwork();
@@ -118,6 +119,8 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
const freeTrialText = getFreeTrialText(policies);
const shouldOpenBookACall = tryNewDot?.classicRedirect?.dismissed === false;
+ const isScreenFocused = useIsAccountSettingsRouteActive(shouldUseNarrowLayout);
+
useEffect(() => {
openInitialSettingsPage();
confirmReadyToOpenApp();
@@ -386,7 +389,7 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr
) : (
-
+