Skip to content

Add removeDelegator action and three-dot menu on delegator rows#91989

Draft
MelvinBot wants to merge 3 commits into
mainfrom
claude-removeDelegatorUI
Draft

Add removeDelegator action and three-dot menu on delegator rows#91989
MelvinBot wants to merge 3 commits into
mainfrom
claude-removeDelegatorUI

Conversation

@MelvinBot
Copy link
Copy Markdown
Contributor

Explanation of Change

Adds the ability for users to remove themselves as a copilot from another user's account. Previously, only the delegator could remove a delegate — now the delegate can also remove themselves.

Changes:

  • CopilotPage: Delegator rows now show a three-dot overflow menu (matching the existing delegate row pattern) with "Switch" and "Remove copilot access" options, plus a confirmation modal
  • Delegate actions: New removeDelegator() action with optimistic/success/failure Onyx updates on the delegators array
  • API types: New REMOVE_DELEGATOR write command and RemoveDelegatorParams type
  • Translations: Added removeCopilotAccess and removeCopilotAccessConfirmation strings (en + es)
  • Constants: Added DELEGATOR_ITEM and DELEGATOR_REMOVE sentry labels

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/638847

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review"

  • Verify that no errors appear in the JS console

Offline tests

// TODO: The human co-author must fill out offline tests

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).

…tor rows

Allows users to remove themselves as a copilot from a delegator's account.
Adds RemoveDelegator write command, a confirmation modal, and replaces the
Switch button on delegator rows with a three-dot popover menu containing
Switch and Remove copilot access options.

Ref: Expensify/Expensify#638847
Co-authored-by: Daniel Gale-Rosen <dangrous@users.noreply.github.com>
@MelvinBot MelvinBot requested a review from a team May 28, 2026 15:51
@OSBotify
Copy link
Copy Markdown
Contributor

🦜 Polyglot Parrot! 🦜

Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:

View the translation diff
diff --git a/src/languages/de.ts b/src/languages/de.ts
index a1428306253..406d782b9ef 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -9166,6 +9166,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
         notAllowedMessage: (accountOwnerEmail: string) =>
             `Als <a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">Copilot</a> von ${accountOwnerEmail} hast du keine Berechtigung, diese Aktion auszuführen. Entschuldigung!`,
         copilotAccess: 'Copilot-Zugriff',
+        removeCopilotAccess: 'Copilot-Zugriff entfernen',
+        removeCopilotAccessConfirmation: 'Sind Sie sicher, dass Sie Ihren Copilot-Zugriff auf dieses Konto entfernen möchten?',
     },
     debug: {
         debug: 'Debug',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 7c70696297b..4321294f8d2 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -9317,8 +9317,8 @@ ${amount} para ${merchant} - ${date}`,
         },
         removeCopilot: 'Eliminar copiloto',
         removeCopilotConfirmation: '¿Estás seguro de que quieres eliminar este copiloto?',
-        removeCopilotAccess: 'Eliminar acceso de copiloto',
-        removeCopilotAccessConfirmation: '¿Estás seguro de que quieres eliminar tu acceso de copiloto a esta cuenta?',
+        removeCopilotAccess: 'Quitar acceso de copiloto',
+        removeCopilotAccessConfirmation: '¿Seguro que quieres eliminar tu acceso como copiloto a esta cuenta?',
         changeAccessLevel: 'Cambiar nivel de acceso',
         makeSureItIsYou: 'Vamos a asegurarnos de que eres tú',
         enterMagicCode: (contactMethod) => `Por favor, introduce el código mágico enviado a ${contactMethod} para agregar un copiloto. Debería llegar en un par de minutos.`,
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 28fa0270c5c..3a200aa44c0 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -2814,9 +2814,9 @@ ${amount} pour ${merchant} - ${date}`,
         title: 'Modifier l’agent',
         agentName: 'Nom de l’agent',
         instructions: 'Écrire des instructions personnalisées',
-        chatWithAgent: 'Discuter avec l\u2019agent',
+        chatWithAgent: 'Discuter avec l’agent',
         copilotIntoAccount: 'Copilote dans le compte',
-        deleteAgent: 'Supprimer l\u2019agent',
+        deleteAgent: 'Supprimer l’agent',
         deleteAgentTitle: 'Supprimer l’agent ?',
         deleteAgentMessage: 'Voulez-vous vraiment supprimer cet agent ? Cette action est irréversible.',
     },
@@ -9193,6 +9193,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
         notAllowedMessage: (accountOwnerEmail: string) =>
             `En tant que <a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">copilote</a> pour ${accountOwnerEmail}, vous n’avez pas l’autorisation d’effectuer cette action. Désolé !`,
         copilotAccess: 'Accès Copilot',
+        removeCopilotAccess: 'Supprimer l’accès au copilote',
+        removeCopilotAccessConfirmation: 'Voulez-vous vraiment supprimer votre accès copilote à ce compte ?',
     },
     debug: {
         debug: 'Déboguer',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 8ab6b4ea1ee..a71cab7a021 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -2802,8 +2802,8 @@ ${amount} per ${merchant} - ${date}`,
         title: 'Modifica agente',
         agentName: 'Nome agente',
         instructions: 'Scrivi istruzioni personalizzate',
-        chatWithAgent: 'Chatta con l\u2019agente',
-        copilotIntoAccount: 'Copilot nell\u2019account',
+        chatWithAgent: 'Chatta con l’agente',
+        copilotIntoAccount: 'Copilot nell’account',
         deleteAgent: 'Elimina agente',
         deleteAgentTitle: 'Eliminare agente?',
         deleteAgentMessage: 'Sei sicuro di voler eliminare questo agente? Questa azione non può essere annullata.',
@@ -9154,6 +9154,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
         notAllowedMessage: (accountOwnerEmail: string) =>
             `Come <a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">copilota</a> per ${accountOwnerEmail}, non hai l'autorizzazione per eseguire questa azione. Spiacenti!`,
         copilotAccess: 'Accesso a Copilot',
+        removeCopilotAccess: 'Rimuovi accesso a Copilot',
+        removeCopilotAccessConfirmation: 'Sei sicuro di voler rimuovere il tuo accesso come copilota a questo conto?',
     },
     debug: {
         debug: 'Debug',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index c2cd656b7a3..2d5b7cad273 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -9040,6 +9040,8 @@ ${reportName}
         notAllowedMessage: (accountOwnerEmail: string) =>
             `${accountOwnerEmail} の<a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">コパイロット</a>として、この操作を行う権限がありません。申し訳ありません。`,
         copilotAccess: 'Copilot へのアクセス',
+        removeCopilotAccess: 'Copilot へのアクセスを削除',
+        removeCopilotAccessConfirmation: 'このアカウントへのコパイロットアクセスを削除してもよろしいですか?',
     },
     debug: {
         debug: 'デバッグ',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 6a1181594d1..5949d411cc8 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -9122,6 +9122,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
         notAllowedMessage: (accountOwnerEmail: string) =>
             `Als <a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">copiloot</a> voor ${accountOwnerEmail} heb je geen toestemming om deze actie uit te voeren. Sorry!`,
         copilotAccess: 'Copilot-toegang',
+        removeCopilotAccess: 'Copilot-toegang verwijderen',
+        removeCopilotAccessConfirmation: 'Weet je zeker dat je je copilottoegang tot deze account wilt verwijderen?',
     },
     debug: {
         debug: 'Debug',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index a7140cf7275..1deef0ec250 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -9104,6 +9104,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
         notAllowedMessage: (accountOwnerEmail: string) =>
             `Jako <a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">kopilot</a> dla ${accountOwnerEmail} nie masz uprawnień do wykonania tej akcji. Przepraszamy!`,
         copilotAccess: 'Dostęp do Copilota',
+        removeCopilotAccess: 'Usuń dostęp współpilota',
+        removeCopilotAccessConfirmation: 'Czy na pewno chcesz usunąć swój dostęp współpilota do tego konta?',
     },
     debug: {
         debug: 'Debug',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index d513966555f..02d413dcb41 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -9113,6 +9113,8 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
         notAllowedMessage: (accountOwnerEmail: string) =>
             `Como <a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">copiloto</a> de ${accountOwnerEmail}, você não tem permissão para realizar esta ação. Desculpe!`,
         copilotAccess: 'Acesso ao Copilot',
+        removeCopilotAccess: 'Remover acesso do copiloto',
+        removeCopilotAccessConfirmation: 'Tem certeza de que quer remover seu acesso como copiloto desta conta?',
     },
     debug: {
         debug: 'Depurar',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 882adf31011..2e9da2c5219 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -8882,6 +8882,8 @@ ${reportName}
         `),
         notAllowedMessage: (accountOwnerEmail: string) => `作为${accountOwnerEmail}的<a href="${CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}">副驾驶</a>,你没有权限执行此操作。抱歉!`,
         copilotAccess: 'Copilot 访问',
+        removeCopilotAccess: '移除副驾驶访问权限',
+        removeCopilotAccessConfirmation: '确定要移除你对该账户的副驾访问权限吗?',
     },
     debug: {
         debug: '调试',

Note

You can apply these changes to your branch by copying the patch to your clipboard, then running pbpaste | git apply 😉

View workflow run

- Run Prettier on Delegate.ts to fix formatting
- Convert delegatorMenuItems from useMemo to inline computation to fix
  React Compiler 'Cannot access refs during render' error. The compiler
  flags two useMemo hooks capturing callbacks that mutate the same ref.
  React Compiler auto-memoizes the inline computation, so manual useMemo
  is unnecessary.

Co-authored-by: Daniel Gale-Rosen <dangrous@users.noreply.github.com>
@MelvinBot
Copy link
Copy Markdown
Contributor Author

Fixed the two failing checks:

  1. Prettier — ran formatter on src/libs/actions/Delegate.ts
  2. React Compiler — the delegatorMenuItems useMemo captured showDelegatorPopoverMenu (which mutates delegateButtonRef.current), causing a "Cannot access refs during render" regression. Converted delegatorMenuItems from useMemo to inline computation — React Compiler auto-memoizes it, so the manual useMemo was unnecessary and was the source of the conflict.

@MelvinBot
Copy link
Copy Markdown
Contributor Author

⚠️ Something went wrong. This may be a transient issue — try again. If it persists, check status.anthropic.com.

Run details

@MelvinBot
Copy link
Copy Markdown
Contributor Author

⚠️ Something went wrong. This may be a transient issue — try again. If it persists, check status.anthropic.com.

Run details

@MelvinBot
Copy link
Copy Markdown
Contributor Author

⚠️ Something went wrong. This may be a transient issue — try again. If it persists, check status.anthropic.com.

Run details

- Add removeDelegator field to DelegateErrors type
- Add removeCopilotAccess/removeCopilotAccessConfirmation translations
  to de, fr, it, ja, nl, pl, pt-BR, zh-hans
- Update CopilotPageTest to expect three-dot menu instead of Switch
  button in delegator rows
- Add removeDelegator to Delegate action mock

Co-authored-by: Daniel Gale-Rosen <dangrous@users.noreply.github.com>
@MelvinBot
Copy link
Copy Markdown
Contributor Author

Fixed additional failing checks (typecheck and test job 7):

  1. Typecheck — added removeDelegator field to the DelegateErrors type in Account.ts, and added missing removeCopilotAccess/removeCopilotAccessConfirmation translations to de, fr, it, ja, nl, pl, pt-BR, zh-hans
  2. Test — updated CopilotPageTest.tsx to expect a three-dot menu icon instead of a "Switch" button in delegator rows (matching the new UI), and added removeDelegator to the Delegate action mock

@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/CONST/index.ts 93.84% <ø> (ø)
src/libs/API/types.ts 100.00% <ø> (ø)
src/pages/settings/Copilot/CopilotPage.tsx 71.32% <83.87%> (+1.09%) ⬆️
src/libs/actions/Delegate.ts 31.52% <0.00%> (-2.80%) ⬇️
... and 27 files with indirect coverage changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants