Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6806,17 +6806,19 @@ const CONST = {

BASE_LIST_ITEM_TEST_ID: 'base-list-item-',
PRODUCT_TRAINING_TOOLTIP_NAMES: {
// TODO: CONCEIRGE_LHN_GBR tooltip will be replaced by a tooltip in the #admins room
// https://github.com/Expensify/App/issues/57045#issuecomment-2701455668
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',
SCAN_TEST_TOOLTIP: 'scanTestTooltip',
SCAN_TEST_TOOLTIP_MANAGER: 'scanTestTooltipManager',
SCAN_TEST_CONFIRMATION: 'scanTestConfirmation',
EXPENSE_REPORTS_FILTER: 'expenseReportsFilter',
GBR_RBR_CHAT: 'chatGBRRBR',
ACCOUNT_SWITCHER: 'accountSwitcher',
},
CHANGE_POLICY_TRAINING_MODAL: 'changePolicyModal',
SMART_BANNER_HEIGHT: 152,
Expand Down
143 changes: 83 additions & 60 deletions src/components/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import {clearDelegatorErrors, connect, disconnect} from '@libs/actions/Delegate';
import * as EmojiUtils from '@libs/EmojiUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import {getProcessedText, splitTextWithEmojis} from '@libs/EmojiUtils';
import {getLatestError} from '@libs/ErrorUtils';
import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import variables from '@styles/variables';
import * as Modal from '@userActions/Modal';
import {close} from '@userActions/Modal';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails} from '@src/types/onyx';
Expand All @@ -26,7 +26,9 @@ 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 EducationalTooltip from './Tooltip/EducationalTooltip';

function AccountSwitcher() {
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
Expand All @@ -47,7 +49,12 @@ function AccountSwitcher() {

const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false;
const canSwitchAccounts = delegators.length > 0 || isActingAsDelegate;
const processedTextArray = EmojiUtils.splitTextWithEmojis(currentUserPersonalDetails?.displayName);
const processedTextArray = splitTextWithEmojis(currentUserPersonalDetails?.displayName);

const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useProductTrainingContext(
CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.ACCOUNT_SWITCHER,
canSwitchAccounts,
);

const createBaseMenuItem = (
personalDetails: PersonalDetails | undefined,
Expand All @@ -58,7 +65,7 @@ function AccountSwitcher() {
return {
text: personalDetails?.displayName ?? personalDetails?.login ?? '',
description: Str.removeSMSDomain(personalDetails?.login ?? ''),
avatarID: personalDetails?.accountID ?? -1,
avatarID: personalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
icon: personalDetails?.avatar ?? '',
iconType: CONST.ICON_TYPE_AVATAR,
outerWrapperStyle: shouldUseNarrowLayout ? {} : styles.accountSwitcherPopover,
Expand Down Expand Up @@ -86,14 +93,14 @@ function AccountSwitcher() {
return [currentUserMenuItem];
}

const delegatePersonalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(delegateEmail);
const error = ErrorUtils.getLatestError(account?.delegatedAccess?.errorFields?.disconnect);
const delegatePersonalDetails = getPersonalDetailByEmail(delegateEmail);
const error = getLatestError(account?.delegatedAccess?.errorFields?.disconnect);

return [
createBaseMenuItem(delegatePersonalDetails, error, {
onSelected: () => {
if (isOffline) {
Modal.close(() => setShouldShowOfflineModal(true));
close(() => setShouldShowOfflineModal(true));
return;
}
disconnect();
Expand All @@ -107,13 +114,13 @@ function AccountSwitcher() {
.filter(({email}) => email !== currentUserPersonalDetails.login)
.map(({email, role}) => {
const errorFields = account?.delegatedAccess?.errorFields ?? {};
const error = ErrorUtils.getLatestError(errorFields?.connect?.[email]);
const personalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email);
const error = getLatestError(errorFields?.connect?.[email]);
const personalDetails = getPersonalDetailByEmail(email);
return createBaseMenuItem(personalDetails, error, {
badgeText: translate('delegate.role', {role}),
onSelected: () => {
if (isOffline) {
Modal.close(() => setShouldShowOfflineModal(true));
close(() => setShouldShowOfflineModal(true));
return;
}
connect(email);
Expand All @@ -124,65 +131,81 @@ function AccountSwitcher() {
return [currentUserMenuItem, ...delegatorMenuItems];
};

const onPressSwitcher = () => {
hideProductTrainingTooltip();
setShouldShowDelegatorMenu(!shouldShowDelegatorMenu);
};

return (
<>
<PressableWithFeedback
accessible
accessibilityLabel={translate('common.profile')}
onPress={() => {
setShouldShowDelegatorMenu(!shouldShowDelegatorMenu);
<EducationalTooltip
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
shouldRender={shouldShowProductTrainingTooltip}
renderTooltipContent={renderProductTrainingTooltip}
anchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
}}
ref={buttonRef}
interactive={canSwitchAccounts}
pressDimmingValue={canSwitchAccounts ? undefined : 1}
wrapperStyle={[styles.flexGrow1, styles.flex1, styles.mnw0, styles.justifyContentCenter]}
shiftVertical={variables.accountSwitcherTooltipShiftVertical}
shiftHorizontal={variables.accountSwitcherTooltipShiftHorizontal}
wrapperStyle={styles.productTrainingTooltipWrapper}
onTooltipPress={onPressSwitcher}
>
<View style={[styles.flexRow, styles.gap3]}>
<Avatar
type={CONST.ICON_TYPE_AVATAR}
size={CONST.AVATAR_SIZE.DEFAULT}
avatarID={currentUserPersonalDetails?.accountID}
source={currentUserPersonalDetails?.avatar}
fallbackIcon={currentUserPersonalDetails.fallbackIcon}
/>
<View style={[styles.flex1, styles.flexShrink1, styles.flexBasis0, styles.justifyContentCenter, styles.gap1]}>
<View style={[styles.flexRow, styles.gap1]}>
<PressableWithFeedback
accessible
accessibilityLabel={translate('common.profile')}
onPress={onPressSwitcher}
ref={buttonRef}
interactive={canSwitchAccounts}
pressDimmingValue={canSwitchAccounts ? undefined : 1}
wrapperStyle={[styles.flexGrow1, styles.flex1, styles.mnw0, styles.justifyContentCenter]}
>
<View style={[styles.flexRow, styles.gap3]}>
<Avatar
type={CONST.ICON_TYPE_AVATAR}
size={CONST.AVATAR_SIZE.DEFAULT}
avatarID={currentUserPersonalDetails?.accountID}
source={currentUserPersonalDetails?.avatar}
fallbackIcon={currentUserPersonalDetails.fallbackIcon}
/>
<View style={[styles.flex1, styles.flexShrink1, styles.flexBasis0, styles.justifyContentCenter, styles.gap1]}>
<View style={[styles.flexRow, styles.gap1]}>
<Text
numberOfLines={1}
style={[styles.textBold, styles.textLarge, styles.flexShrink1]}
>
{processedTextArray.length !== 0 ? getProcessedText(processedTextArray, styles.initialSettingsUsernameEmoji) : currentUserPersonalDetails?.displayName}
</Text>
{!!canSwitchAccounts && (
<View style={styles.justifyContentCenter}>
<Icon
fill={theme.icon}
src={Expensicons.CaretUpDown}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
/>
</View>
)}
</View>
<Text
numberOfLines={1}
style={[styles.textBold, styles.textLarge, styles.flexShrink1]}
style={[styles.colorMuted, styles.fontSizeLabel]}
>
{processedTextArray.length !== 0
? EmojiUtils.getProcessedText(processedTextArray, styles.initialSettingsUsernameEmoji)
: currentUserPersonalDetails?.displayName}
{Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')}
</Text>
{!!canSwitchAccounts && (
<View style={styles.justifyContentCenter}>
<Icon
fill={theme.icon}
src={Expensicons.CaretUpDown}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
/>
</View>
{!!user?.isDebugModeEnabled && (
<Text
style={[styles.textLabelSupporting, styles.mt1, styles.w100]}
numberOfLines={1}
>
AccountID: {session?.accountID}
</Text>
)}
</View>
<Text
numberOfLines={1}
style={[styles.colorMuted, styles.fontSizeLabel]}
>
{Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')}
</Text>
{!!user?.isDebugModeEnabled && (
<Text
style={[styles.textLabelSupporting, styles.mt1, styles.w100]}
numberOfLines={1}
>
AccountID: {session?.accountID}
</Text>
)}
</View>
</View>
</PressableWithFeedback>
</PressableWithFeedback>
</EducationalTooltip>

{!!canSwitchAccounts && (
<PopoverMenu
isVisible={shouldShowDelegatorMenu}
Expand Down
30 changes: 28 additions & 2 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import BlockingView from '@components/BlockingViews/BlockingView';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import LottieAnimations from '@components/LottieAnimations';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider';
import TextBlock from '@components/TextBlock';
import useLHNEstimatedListSize from '@hooks/useLHNEstimatedListSize';
Expand All @@ -20,9 +21,9 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {isValidDraftComment} from '@libs/DraftCommentUtils';
import getPlatform from '@libs/getPlatform';
import {getIOUReportIDOfLastAction, getLastMessageTextForReport} from '@libs/OptionsListUtils';
import {getIOUReportIDOfLastAction, getLastMessageTextForReport, hasReportErrors} from '@libs/OptionsListUtils';
import {getOneTransactionThreadReportID, getOriginalMessage, getSortedReportActionsForDisplay, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {canUserPerformWriteAction} from '@libs/ReportUtils';
import {canUserPerformWriteAction, requiresAttentionFromCurrentUser} from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -57,6 +58,27 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
const platform = getPlatform();
const isWebOrDesktop = platform === CONST.PLATFORM.WEB || platform === CONST.PLATFORM.DESKTOP;

const {shouldShowProductTrainingTooltip} = useProductTrainingContext(CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.GBR_RBR_CHAT, true);
const firstReportIDWithGBRorRGR = useMemo(() => {
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);
Expand Down Expand Up @@ -179,6 +201,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
}
const lastMessageTextFromReport = getLastMessageTextForReport(itemFullReport, lastActorDetails, itemPolicy);

const shouldShowRBRorGPRTooltip = firstReportIDWithGBRorRGR === reportID;

return (
<OptionRowLHNData
reportID={reportID}
Expand All @@ -202,6 +226,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
hasDraftComment={hasDraftComment}
transactionViolations={transactionViolations}
onLayout={onLayoutItem}
shouldShowRBRorGPRTooltip={shouldShowRBRorGPRTooltip}
/>
);
},
Expand All @@ -220,6 +245,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
transactionViolations,
onLayoutItem,
isOffline,
firstReportIDWithGBRorRGR,
],
);

Expand Down
Loading