Skip to content

Commit cab9799

Browse files
authored
Merge pull request #91533 from Expensify/claude-addAgentChatAndCopilotButtons
Add Chat and Copilot buttons to Agents page
2 parents 7001b0d + d3e2c18 commit cab9799

16 files changed

Lines changed: 201 additions & 82 deletions

File tree

src/hooks/useChatWithAgent.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {hasSeenTourSelector} from '@selectors/Onboarding';
2+
import {navigateToAndOpenReportWithAccountIDs} from '@libs/actions/Report';
3+
import ONYXKEYS from '@src/ONYXKEYS';
4+
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
5+
import useOnyx from './useOnyx';
6+
7+
/**
8+
* Encapsulates the data fetching and navigation logic for opening a DM chat with an agent.
9+
* Returns a function that, given an accountID, navigates to the DM report with that agent.
10+
*/
11+
function useChatWithAgent() {
12+
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
13+
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
14+
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
15+
const [betas] = useOnyx(ONYXKEYS.BETAS);
16+
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
17+
18+
const chatWithAgent = (accountID: number) => {
19+
navigateToAndOpenReportWithAccountIDs([accountID], currentUserPersonalDetails.accountID, introSelected, isSelfTourViewed, betas, personalDetails);
20+
};
21+
22+
return chatWithAgent;
23+
}
24+
25+
export default useChatWithAgent;

src/hooks/useSwitchToDelegator.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
2+
import {ModalActions} from '@components/Modal/Global/ModalContext';
3+
import {connect, disconnect} from '@libs/actions/Delegate';
4+
import {close as modalClose} from '@libs/actions/Modal';
5+
import {getGpsPoints, stopGpsTrip} from '@libs/GPSDraftDetailsUtils';
6+
import CONST from '@src/CONST';
7+
import ONYXKEYS from '@src/ONYXKEYS';
8+
import {isTrackingSelector} from '@src/selectors/GPSDraftDetails';
9+
import type {Account} from '@src/types/onyx';
10+
import useConfirmModal from './useConfirmModal';
11+
import useLocalize from './useLocalize';
12+
import useNetwork from './useNetwork';
13+
import useOnyx from './useOnyx';
14+
15+
/**
16+
* Encapsulates the safety checks needed before switching to a delegator account:
17+
* 1. Offline check – blocks the switch and shows an offline modal.
18+
* 2. Chained delegation check – if already acting as a delegate and not returning
19+
* to the original user, shows the "not so fast" modal.
20+
* 3. GPS tracking check – if a GPS trip is in progress, asks the user to confirm
21+
* stopping the trip before switching.
22+
*/
23+
function useSwitchToDelegator() {
24+
const {translate} = useLocalize();
25+
const {isOffline} = useNetwork();
26+
const {showConfirmModal} = useConfirmModal();
27+
const {isActingAsDelegate} = useDelegateNoAccessState();
28+
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
29+
30+
const [delegatedAccess] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account: Account | undefined) => account?.delegatedAccess});
31+
const [credentials] = useOnyx(ONYXKEYS.CREDENTIALS);
32+
const [stashedCredentials = CONST.EMPTY_OBJECT] = useOnyx(ONYXKEYS.STASHED_CREDENTIALS);
33+
const [session] = useOnyx(ONYXKEYS.SESSION);
34+
const [stashedSession] = useOnyx(ONYXKEYS.STASHED_SESSION);
35+
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
36+
const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS);
37+
const [isTrackingGPS = false] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {selector: isTrackingSelector});
38+
39+
const showOfflineModal = () => {
40+
showConfirmModal({
41+
title: translate('common.youAppearToBeOffline'),
42+
prompt: translate('common.offlinePrompt'),
43+
confirmText: translate('common.buttonConfirm'),
44+
shouldShowCancelButton: false,
45+
});
46+
};
47+
48+
const showGpsInProgressModal = async (switchAccount: () => ReturnType<typeof connect | typeof disconnect>) => {
49+
const result = await showConfirmModal({
50+
title: translate('gps.switchAccountWarningTripInProgress.title'),
51+
prompt: translate('gps.switchAccountWarningTripInProgress.prompt'),
52+
confirmText: translate('gps.switchAccountWarningTripInProgress.confirm'),
53+
cancelText: translate('common.cancel'),
54+
});
55+
56+
if (result.action !== ModalActions.CONFIRM) {
57+
return;
58+
}
59+
60+
await stopGpsTrip(false, getGpsPoints(gpsDraftDetails), true);
61+
switchAccount();
62+
};
63+
64+
const switchToDelegator = (email: string) => {
65+
if (isOffline) {
66+
modalClose(() => showOfflineModal());
67+
return;
68+
}
69+
const isReturningToOriginalUser = isActingAsDelegate && email === stashedSession?.email;
70+
// Chained delegation isn't supported by the backend — if we're already acting as a delegate,
71+
// the only legal switch is back to the original user. Anything else triggers the "Not so fast" modal.
72+
if (isActingAsDelegate && !isReturningToOriginalUser) {
73+
modalClose(() => showDelegateNoAccessModal());
74+
return;
75+
}
76+
const switchAction = isReturningToOriginalUser
77+
? () => disconnect({stashedCredentials, stashedSession})
78+
: () => connect({email, delegatedAccess, credentials, session, activePolicyID});
79+
if (isTrackingGPS) {
80+
modalClose(() => showGpsInProgressModal(switchAction));
81+
return;
82+
}
83+
switchAction();
84+
};
85+
86+
return switchToDelegator;
87+
}
88+
89+
export default useSwitchToDelegator;

src/languages/de.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,6 +2792,8 @@ ${amount} für ${merchant} – ${date}`,
27922792
title: 'Agent bearbeiten',
27932793
agentName: 'Name der Agentin/des Agenten',
27942794
instructions: 'Eigene Anweisungen schreiben',
2795+
chatWithAgent: 'Mit Agent chatten',
2796+
copilotIntoAccount: 'Copilot ins Konto',
27952797
deleteAgent: 'Agent löschen',
27962798
deleteAgentTitle: 'Agent löschen?',
27972799
deleteAgentMessage: 'Sind Sie sicher, dass Sie diese Vermittlerin/diesen Vermittler löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.',

src/languages/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2851,6 +2851,8 @@ const translations = {
28512851
title: 'Edit agent',
28522852
agentName: 'Agent name',
28532853
instructions: 'Write custom instructions',
2854+
chatWithAgent: 'Chat with agent',
2855+
copilotIntoAccount: 'Copilot into account',
28542856
deleteAgent: 'Delete agent',
28552857
deleteAgentTitle: 'Delete agent?',
28562858
deleteAgentMessage: 'Are you sure you want to delete this agent? This action cannot be undone.',

src/languages/es.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,8 @@ ${amount} para ${merchant} - ${date}`,
26672667
title: 'Editar agente',
26682668
agentName: 'Nombre del agente',
26692669
instructions: 'Escribir instrucciones personalizadas',
2670+
chatWithAgent: 'Chatear con el agente',
2671+
copilotIntoAccount: 'Copilot en la cuenta',
26702672
deleteAgent: 'Eliminar agente',
26712673
deleteAgentTitle: '¿Eliminar agente?',
26722674
deleteAgentMessage: '¿Seguro que quieres eliminar a este agente? Esta acción no se puede deshacer.',

src/languages/fr.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2800,7 +2800,9 @@ ${amount} pour ${merchant} - ${date}`,
28002800
title: 'Modifier l’agent',
28012801
agentName: 'Nom de l’agent',
28022802
instructions: 'Écrire des instructions personnalisées',
2803-
deleteAgent: 'Supprimer l’agent',
2803+
chatWithAgent: 'Discuter avec l\u2019agent',
2804+
copilotIntoAccount: 'Copilote dans le compte',
2805+
deleteAgent: 'Supprimer l\u2019agent',
28042806
deleteAgentTitle: 'Supprimer l’agent ?',
28052807
deleteAgentMessage: 'Voulez-vous vraiment supprimer cet agent ? Cette action est irréversible.',
28062808
},

src/languages/it.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,6 +2788,8 @@ ${amount} per ${merchant} - ${date}`,
27882788
title: 'Modifica agente',
27892789
agentName: 'Nome agente',
27902790
instructions: 'Scrivi istruzioni personalizzate',
2791+
chatWithAgent: 'Chatta con l\u2019agente',
2792+
copilotIntoAccount: 'Copilot nell\u2019account',
27912793
deleteAgent: 'Elimina agente',
27922794
deleteAgentTitle: 'Eliminare agente?',
27932795
deleteAgentMessage: 'Sei sicuro di voler eliminare questo agente? Questa azione non può essere annullata.',

src/languages/ja.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2761,6 +2761,8 @@ ${date} の ${merchant} への ${amount}`,
27612761
title: 'エージェントを編集',
27622762
agentName: '担当者名',
27632763
instructions: 'カスタム手順を作成',
2764+
chatWithAgent: 'エージェントとチャット',
2765+
copilotIntoAccount: 'アカウントにコパイロット',
27642766
deleteAgent: 'エージェントを削除',
27652767
deleteAgentTitle: 'エージェントを削除しますか?',
27662768
deleteAgentMessage: 'このエージェントを削除してもよろしいですか?この操作は元に戻せません。',

src/languages/nl.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2785,6 +2785,8 @@ ${amount} voor ${merchant} - ${date}`,
27852785
title: 'Agent bewerken',
27862786
agentName: 'Naam medewerker',
27872787
instructions: 'Aangepaste instructies schrijven',
2788+
chatWithAgent: 'Chat met agent',
2789+
copilotIntoAccount: 'Copilot in account',
27882790
deleteAgent: 'Agent verwijderen',
27892791
deleteAgentTitle: 'Agent verwijderen?',
27902792
deleteAgentMessage: 'Weet je zeker dat je deze agent wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.',

src/languages/pl.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2779,6 +2779,8 @@ ${amount} dla ${merchant} - ${date}`,
27792779
title: 'Edytuj agenta',
27802780
agentName: 'Nazwa agenta',
27812781
instructions: 'Napisz własne instrukcje',
2782+
chatWithAgent: 'Czat z agentem',
2783+
copilotIntoAccount: 'Copilot na konto',
27822784
deleteAgent: 'Usuń agenta',
27832785
deleteAgentTitle: 'Usunąć agenta?',
27842786
deleteAgentMessage: 'Czy na pewno chcesz usunąć tego agenta? Tej akcji nie można cofnąć.',

0 commit comments

Comments
 (0)