Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8132703
Make Copilot switching discoverable with a dedicated Switch button
wildan-m Jun 1, 2026
2c55c33
Remove unused Emoji lazy icon entry
wildan-m Jun 12, 2026
e24774b
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 12, 2026
f441a09
Remove dead styles and orphaned status-picker Sentry label
wildan-m Jun 12, 2026
2015448
Add UI tests for AccountSwitcher Switch button
wildan-m Jun 12, 2026
4128e85
Avoid unsafe type assertion in ReanimatedModal test mock
wildan-m Jun 12, 2026
69b0911
Simplify ReanimatedModal test mock so it compiles with React Compiler
wildan-m Jun 12, 2026
73ca990
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 14, 2026
1fb32dc
Wrap Switch button in a View so the Copilot access tooltip shows on h…
wildan-m Jun 14, 2026
db7935f
Add justification to eslint-disable in AccountSwitcher test mock
wildan-m Jun 14, 2026
18f81fc
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 17, 2026
16f4ed4
Update Copilot switcher tooltip copy to match design
wildan-m Jun 17, 2026
a99e429
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 18, 2026
303dc84
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 18, 2026
7cfb5ea
Merge remote-tracking branch 'upstream/main' into HEAD
wildan-m Jun 19, 2026
1d1e053
Add caret to the Switch button to match the design mockup
wildan-m Jun 19, 2026
dad4870
Keep the first-time copilot tooltip visible until dismissed
wildan-m Jun 19, 2026
370a7c2
Align copilot tooltip with mockup: open leftward into the sidebar and…
wildan-m Jun 19, 2026
596d766
Apply reviewer copy feedback: 'copilot into another account!'
wildan-m Jun 19, 2026
0fc14e0
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 20, 2026
366939e
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jun 26, 2026
6e697b7
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jul 2, 2026
abc4170
Apply Polyglot Parrot translations for the copilot switcher tooltip
wildan-m Jul 2, 2026
ac973fb
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jul 2, 2026
99c59f1
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jul 2, 2026
e29191d
Merge remote-tracking branch 'upstream/main' into wildan/92253-copilo…
wildan-m Jul 6, 2026
ddb3e86
Format AccountSwitcherTest with oxfmt
wildan-m Jul 6, 2026
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
1 change: 0 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8229,7 +8229,6 @@ const CONST = {
COPILOT: 'Account-Copilot',
SECURITY: 'Account-Security',
SUBSCRIPTION: 'Account-Subscription',
STATUS_PICKER: 'Account-StatusPicker',
},
DISCOVER_SECTION: {
TEST_DRIVE: 'DiscoverSection-TestDrive',
Expand Down
129 changes: 62 additions & 67 deletions src/components/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';

Expand Down Expand Up @@ -34,10 +33,9 @@ import {View} from 'react-native';
import type {PopoverMenuItem} from './PopoverMenu';

import Avatar from './Avatar';
import Icon from './Icon';
import Button from './Button';
import {ModalActions} from './Modal/Global/ModalContext';
import PopoverMenu from './PopoverMenu';
import {PressableWithFeedback} from './Pressable';
import {useProductTrainingContext} from './ProductTrainingContext';
import Text from './Text';
import Tooltip from './Tooltip';
Expand All @@ -49,10 +47,9 @@ type AccountSwitcherProps = {
};

function AccountSwitcher({isScreenFocused}: AccountSwitcherProps) {
const icons = useMemoizedLazyExpensifyIcons(['CaretUpDown', 'Checkmark']);
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const icons = useMemoizedLazyExpensifyIcons(['CaretUpDown']);
const styles = useThemeStyles();
const theme = useTheme();
const {localeCompare, translate, formatPhoneNumber} = useLocalize();
const {isOffline} = useNetwork();
const {shouldUseNarrowLayout} = useResponsiveLayout();
Expand All @@ -67,7 +64,7 @@ function AccountSwitcher({isScreenFocused}: AccountSwitcherProps) {
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS);

const buttonRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<View>(null);
const {windowHeight} = useWindowDimensions();

const [shouldShowDelegatorMenu, setShouldShowDelegatorMenu] = useState(false);
Expand Down Expand Up @@ -122,13 +119,18 @@ function AccountSwitcher({isScreenFocused}: AccountSwitcherProps) {
shouldRender: shouldShowProductTrainingTooltip,
renderTooltipContent: renderProductTrainingTooltip,
anchorAlignment: {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
// Right-align so the tooltip opens leftward into the sidebar (matching the design mockup),
// instead of overflowing past the Switch button into the central pane.
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
},
shiftVertical: variables.accountSwitcherTooltipShiftVertical,
shiftHorizontal: variables.accountSwitcherTooltipShiftHorizontal,
wrapperStyle: styles.productTrainingTooltipWrapper,
onTooltipPress: onPressSwitcher,
// The switcher lives in the settings sidebar, which isn't the navigation-focused screen on wide layouts.
// Without this the educational tooltip is suppressed (it relies on the screen being focused), so keep it shown until dismissed.
shouldHideOnNavigate: false,
}
: {
text: translate('delegate.copilotAccess'),
Expand Down Expand Up @@ -230,71 +232,64 @@ function AccountSwitcher({isScreenFocused}: AccountSwitcherProps) {

return (
<>
<TooltipToRender {...tooltipProps}>
<PressableWithFeedback
accessible
accessibilityLabel={`${translate('common.profile')}, ${displayName}, ${Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')}`}
onPress={onPressSwitcher}
ref={buttonRef}
interactive={canSwitchAccounts}
pressDimmingValue={canSwitchAccounts ? undefined : 1}
wrapperStyle={[styles.flexGrow1, styles.flex1, styles.mnw0, styles.justifyContentCenter]}
sentryLabel={CONST.SENTRY_LABEL.ACCOUNT_SWITCHER.SHOW_ACCOUNTS}
>
<View style={[styles.flexRow, styles.gap3, styles.alignItemsCenter]}>
<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]}>
{doesDisplayNameContainEmojis ? (
<Text numberOfLines={1}>
<TextWithEmojiFragment
message={displayName}
style={[styles.textBold, styles.textLarge, styles.flexShrink1, styles.lineHeightXLarge]}
/>
</Text>
) : (
<Text
numberOfLines={1}
style={[styles.textBold, styles.textLarge, styles.flexShrink1, styles.lineHeightXLarge]}
>
{formatPhoneNumber(displayName)}
</Text>
)}
{!!canSwitchAccounts && (
<View style={styles.justifyContentCenter}>
<Icon
fill={theme.icon}
src={icons.CaretUpDown}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
/>
</View>
)}
</View>
<View style={[styles.flexRow, styles.gap3, styles.alignItemsCenter, styles.flexGrow1, styles.flex1, styles.mnw0]}>
<View style={[styles.flexRow, styles.gap3, styles.alignItemsCenter, styles.flex1, styles.flexShrink1, styles.mnw0, styles.justifyContentCenter]}>
<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]}>
{doesDisplayNameContainEmojis ? (
<Text numberOfLines={1}>
<TextWithEmojiFragment
message={displayName}
style={[styles.textBold, styles.textLarge, styles.flexShrink1, styles.lineHeightXLarge]}
/>
</Text>
) : (
<Text
numberOfLines={1}
style={[styles.colorMuted, styles.fontSizeLabel]}
style={[styles.textBold, styles.textLarge, styles.flexShrink1, styles.lineHeightXLarge]}
>
{Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')}
{formatPhoneNumber(displayName)}
</Text>
{!!isDebugModeEnabled && (
<Text
style={[styles.textLabelSupporting, styles.mt1, styles.w100]}
numberOfLines={1}
>
AccountID: {accountID}
</Text>
)}
</View>
)}
<Text
numberOfLines={1}
style={[styles.colorMuted, styles.fontSizeLabel]}
>
{Str.removeSMSDomain(currentUserPersonalDetails?.login ?? '')}
</Text>
{!!isDebugModeEnabled && (
<Text
style={[styles.textLabelSupporting, styles.mt1, styles.w100]}
numberOfLines={1}
>
AccountID: {accountID}
</Text>
)}
</View>
</PressableWithFeedback>
</TooltipToRender>
</View>
{!!canSwitchAccounts && (
<TooltipToRender {...tooltipProps}>
{/* View wrapper forwards the hover events Tooltip injects; Button doesn't pass them to its underlying pressable, so the tooltip wouldn't show without it */}
<View>
<Button
small
ref={buttonRef}
text={translate('delegate.switch')}
onPress={onPressSwitcher}
sentryLabel={CONST.SENTRY_LABEL.ACCOUNT_SWITCHER.SHOW_ACCOUNTS}
shouldShowRightIcon
iconRight={icons.CaretUpDown}
/>
</View>
</TooltipToRender>
)}
</View>

{!!canSwitchAccounts && (
<PopoverMenu
Expand Down
2 changes: 1 addition & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9941,7 +9941,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>Beginne <strong>hier!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Benenne deine gespeicherten Suchen um</strong> – hier!</tooltip>',
accountSwitcher: '<tooltip>Greifen Sie hier auf Ihre <strong>Copilot-Konten</strong> zu</tooltip>',
accountSwitcher: '<tooltip>Sie können jetzt als Copilot in ein anderes Konto einsteigen!</tooltip>',
outstandingFilter: '<tooltip>Nach Ausgaben filtern,\ndie <strong>genehmigt werden müssen</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Sende diese Quittung, um\n<strong>die Probefahrt abzuschließen!</strong></tooltip>',
gpsTooltip: '<tooltip>GPS-Tracking läuft! Wenn du fertig bist, stoppe die Aufzeichnung unten.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10021,7 +10021,7 @@ const translations = {
// https://github.com/Expensify/App/issues/57045#issuecomment-2701455668
conciergeLHNGBR: '<tooltip>Get started <strong>here!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Rename your saved searches</strong> here!</tooltip>',
accountSwitcher: '<tooltip>Access your <strong>Copilot accounts</strong> here</tooltip>',
accountSwitcher: '<tooltip>You can now copilot into another account!</tooltip>',
outstandingFilter: '<tooltip>Filter for expenses\nthat <strong>need approval</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Send this receipt to\n<strong>complete the test drive!</strong></tooltip>',
gpsTooltip: "<tooltip>GPS tracking in progress! When you're done, stop tracking below.</tooltip>",
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10090,7 +10090,7 @@ ${amount} para ${merchant} - ${date}`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>¡Comienza <strong>aquí</strong>!</tooltip>',
saveSearchTooltip: '<tooltip><strong>Renombra tus búsquedas guardadas</strong> aquí</tooltip>',
accountSwitcher: '<tooltip>Accede a tus <strong>cuentas copiloto</strong> aquí</tooltip>',
accountSwitcher: '<tooltip>¡Ahora puedes copilotar en otra cuenta!</tooltip>',
outstandingFilter: '<tooltip>Filtra los gastos\nque <strong>necesitan aprobación</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>¡Envía este recibo para\n<strong>completar la prueba</strong>!</tooltip>',
gpsTooltip: '<tooltip>¡Seguimiento por GPS en curso! Cuando termines, detén el seguimiento a continuación.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9973,7 +9973,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>Commencez <strong>ici&nbsp;!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Renommez vos recherches enregistrées</strong> ici !</tooltip>',
accountSwitcher: '<tooltip>Accédez à vos <strong>comptes Copilot</strong> ici</tooltip>',
accountSwitcher: '<tooltip>Vous pouvez désormais copiloter un autre compte&nbsp;!</tooltip>',
outstandingFilter: '<tooltip>Filtrer les dépenses\nqui <strong>doivent être approuvées</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Envoyez ce reçu pour\n<strong>terminer l’essai !</strong></tooltip>',
gpsTooltip: '<tooltip>Suivi GPS en cours ! Lorsque vous avez terminé, arrêtez le suivi ci-dessous.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9930,7 +9930,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>Inizia <strong>qui!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Rinomina qui le ricerche salvate</strong>!</tooltip>',
accountSwitcher: '<tooltip>Accedi ai tuoi <strong>account Copilot</strong> qui</tooltip>',
accountSwitcher: '<tooltip>Ora puoi fare da copilota in un altro account!</tooltip>',
outstandingFilter: '<tooltip>Filtra per le spese\nche <strong>necessitano di approvazione</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Invia questa ricevuta per\n<strong>completare il test drive!</strong></tooltip>',
gpsTooltip: '<tooltip>Tracciamento GPS in corso! Quando hai finito, interrompi il tracciamento qui sotto.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9791,7 +9791,7 @@ ${reportName}`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>まずは<strong>こちらから!</strong></tooltip>',
saveSearchTooltip: '<tooltip>保存済み検索の<strong>名前を変更</strong>しましょう!</tooltip>',
accountSwitcher: '<tooltip>ここから<strong>Copilot アカウント</strong>にアクセスできます</tooltip>',
accountSwitcher: '<tooltip>他のアカウントにコパイロットとして入ることができるようになりました!</tooltip>',
outstandingFilter: '<tooltip><strong>承認が必要な</strong>経費を絞り込む</tooltip>',
scanTestDriveTooltip: '<tooltip>このレシートを送信して\n<strong>試用を完了しましょう!</strong></tooltip>',
gpsTooltip: '<tooltip>GPS追跡を実行中です!完了したら、下で追跡を停止してください。</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9898,7 +9898,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>Begin <strong>hier!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Hernoem hier je opgeslagen zoekopdrachten</strong>!</tooltip>',
accountSwitcher: '<tooltip>Krijg hier toegang tot je <strong>Copilot-accounts</strong></tooltip>',
accountSwitcher: '<tooltip>Je kunt nu copilot worden in een ander account!</tooltip>',
outstandingFilter: '<tooltip>Filter voor uitgaven\ndie <strong>goedkeuring nodig hebben</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Stuur deze bon om\n<strong>de proefrit te voltooien!</strong></tooltip>',
gpsTooltip: '<tooltip>GPS-tracking bezig! Als je klaar bent, stop dan hieronder met bijhouden.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9872,7 +9872,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>Rozpocznij <strong>tutaj!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Zmień nazwę zapisanych wyszukiwań</strong> tutaj!</tooltip>',
accountSwitcher: '<tooltip>Uzyskaj tutaj dostęp do <strong>kont Copilot</strong></tooltip>',
accountSwitcher: '<tooltip>Teraz możesz dołączyć jako kopilot do innego konta!</tooltip>',
outstandingFilter: '<tooltip>Filtruj wydatki,\nktóre <strong>wymagają zatwierdzenia</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Wyślij ten paragon, aby\n<strong>zakończyć jazdę próbną!</strong></tooltip>',
gpsTooltip: '<tooltip>Śledzenie GPS w toku! Gdy skończysz, zatrzymaj śledzenie poniżej.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9884,7 +9884,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>Comece <strong>aqui!</strong></tooltip>',
saveSearchTooltip: '<tooltip><strong>Renomeie suas buscas salvas</strong> aqui!</tooltip>',
accountSwitcher: '<tooltip>Acesse suas <strong>contas Copilot</strong> aqui</tooltip>',
accountSwitcher: '<tooltip>Agora você pode fazer copiloto em outra conta!</tooltip>',
outstandingFilter: '<tooltip>Filtrar despesas\nque <strong>precisam de aprovação</strong></tooltip>',
scanTestDriveTooltip: '<tooltip>Envie este recibo para\n<strong>concluir o test drive!</strong></tooltip>',
gpsTooltip: '<tooltip>Rastreamento por GPS em andamento! Quando terminar, pare o rastreamento abaixo.</tooltip>',
Expand Down
2 changes: 1 addition & 1 deletion src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9600,7 +9600,7 @@ ${reportName}`,
productTrainingTooltip: {
conciergeLHNGBR: '<tooltip>从<strong>这里</strong>开始!</tooltip>',
saveSearchTooltip: '<tooltip>在这里<strong>重命名已保存的搜索</strong>!</tooltip>',
accountSwitcher: '<tooltip>在此访问你的<strong>Copilot 账户</strong></tooltip>',
accountSwitcher: '<tooltip>您现在可以协同操作另一位用户的账户了!</tooltip>',
outstandingFilter: '<tooltip>筛选<strong>待审批</strong>的报销</tooltip>',
scanTestDriveTooltip: '<tooltip>发送此收据以\n<strong>完成试用体验!</strong></tooltip>',
gpsTooltip: '<tooltip>正在进行 GPS 跟踪!完成后,请在下方停止跟踪。</tooltip>',
Expand Down
Loading
Loading