diff --git a/components/layout/account-switcher.tsx b/components/layout/account-switcher.tsx
index 179aeddd..f8c1d6c6 100644
--- a/components/layout/account-switcher.tsx
+++ b/components/layout/account-switcher.tsx
@@ -2,7 +2,7 @@
import { useState, useRef, useEffect, useCallback, useMemo } from "react";
import { createPortal } from "react-dom";
-import { Check, Plus, LogOut, Star, ChevronDown, AlertCircle, GripVertical } from "lucide-react";
+import { Check, Plus, LogOut, Star, ChevronDown, AlertCircle, GripVertical, X } from "lucide-react";
import { useTranslations } from "next-intl";
import { useAccountStore, type AccountEntry } from "@/stores/account-store";
import { useAuthStore } from "@/stores/auth-store";
@@ -48,6 +48,7 @@ export function AccountSwitcher({ variant = "rail", className }: AccountSwitcher
const activeAccount = accounts.find((a) => a.id === activeAccountId);
const switchAccount = useAuthStore((s) => s.switchAccount);
const logout = useAuthStore((s) => s.logout);
+ const removeAccount = useAuthStore((s) => s.removeAccount);
const logoutAll = useAuthStore((s) => s.logoutAll);
const updatePosition = useCallback(() => {
@@ -100,6 +101,13 @@ export function AccountSwitcher({ variant = "rail", className }: AccountSwitcher
router.push(`/login?mode=add-account` as never);
};
+ const handleRemove = (e: React.MouseEvent, account: AccountEntry) => {
+ e.stopPropagation();
+ const label = account.email || account.username;
+ if (!window.confirm(t("remove_account_confirm", { account: label }))) return;
+ removeAccount(account.id);
+ };
+
const handleLogout = () => {
setOpen(false);
logout();
@@ -221,7 +229,7 @@ export function AccountSwitcher({ variant = "rail", className }: AccountSwitcher
className={cn(
"w-full flex items-start gap-3 px-3 py-2.5 text-start transition-colors",
isActive ? "bg-accent/50" : "hover:bg-muted",
- isDraggable && "pe-7"
+ (!isActive && !account.isDefault) ? (isDraggable ? "pe-14" : "pe-8") : (isDraggable && "pe-7")
)}
role="menuitem"
disabled={isActive}
@@ -264,11 +272,22 @@ export function AccountSwitcher({ variant = "rail", className }: AccountSwitcher
{isDraggable && (
)}
+ {!isActive && !account.isDefault && (
+
+ )}
);
})}
diff --git a/locales/cs/common.json b/locales/cs/common.json
index c50565f9..19273b00 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -138,7 +138,9 @@
"scheduled": "Naplánováno",
"unified_all_unread": "Vše nepřečtené",
"unified_all_starred": "Vše s hvězdičkou",
- "unified_all_mail": "Veškerá pošta"
+ "unified_all_mail": "Veškerá pošta",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Výchozí aplikace",
diff --git a/locales/da/common.json b/locales/da/common.json
index d1956177..db1ca4ed 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -138,7 +138,9 @@
"scheduled": "Planlagt",
"unified_all_unread": "Alle ulæste",
"unified_all_starred": "Alle med stjerne",
- "unified_all_mail": "Al post"
+ "unified_all_mail": "Al post",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Standardapps",
diff --git a/locales/de/common.json b/locales/de/common.json
index b4676020..5889ac66 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -138,7 +138,9 @@
"scheduled": "Geplant",
"unified_all_unread": "Alle ungelesenen",
"unified_all_starred": "Alle markierten",
- "unified_all_mail": "Alle Mails"
+ "unified_all_mail": "Alle Mails",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Standard-Apps",
diff --git a/locales/en/common.json b/locales/en/common.json
index 9fbf67fb..ca9d8e70 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -138,7 +138,9 @@
"scheduled": "Scheduled",
"unified_all_unread": "All unread",
"unified_all_starred": "All starred",
- "unified_all_mail": "All mail"
+ "unified_all_mail": "All mail",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Default apps",
diff --git a/locales/es/common.json b/locales/es/common.json
index 2a2b0ffd..d14a3c25 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -138,7 +138,9 @@
"scheduled": "Programados",
"unified_all_unread": "Todo no leído",
"unified_all_starred": "Todo destacado",
- "unified_all_mail": "Todo el correo"
+ "unified_all_mail": "Todo el correo",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Aplicaciones predeterminadas",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 69982ebc..773d58cd 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -138,7 +138,9 @@
"scheduled": "زمانبندی شده",
"unified_all_mail": "همه ایمیلها",
"unified_all_starred": "همه ستارهدارها",
- "unified_all_unread": "همه خواندهنشدهها"
+ "unified_all_unread": "همه خواندهنشدهها",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "برنامههای پیشفرض",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 9603a2c5..b37907ff 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -138,7 +138,9 @@
"scheduled": "Planifiés",
"unified_all_unread": "Tous les non lus",
"unified_all_starred": "Tous les favoris",
- "unified_all_mail": "Tout le courrier"
+ "unified_all_mail": "Tout le courrier",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Applications par défaut",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index e45826d2..df095070 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -138,7 +138,9 @@
"scheduled": "Ütemezett",
"unified_all_unread": "Összes olvasatlan",
"unified_all_starred": "Összes csillagozott",
- "unified_all_mail": "Összes levél"
+ "unified_all_mail": "Összes levél",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Alapértelmezett alkalmazások",
diff --git a/locales/it/common.json b/locales/it/common.json
index 7d276203..f6de43a3 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -138,7 +138,9 @@
"scheduled": "Programmate",
"unified_all_unread": "Tutti i non letti",
"unified_all_starred": "Tutti gli speciali",
- "unified_all_mail": "Tutta la posta"
+ "unified_all_mail": "Tutta la posta",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "App predefinite",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 234390a5..ab7c653f 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -138,7 +138,9 @@
"scheduled": "予約済み",
"unified_all_unread": "すべての未読",
"unified_all_starred": "すべてのスター付き",
- "unified_all_mail": "すべてのメール"
+ "unified_all_mail": "すべてのメール",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "既定のアプリ",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index ef5723bb..80295ed7 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -138,7 +138,9 @@
"scheduled": "예약됨",
"unified_all_unread": "모든 읽지 않음",
"unified_all_starred": "모든 별표",
- "unified_all_mail": "모든 메일"
+ "unified_all_mail": "모든 메일",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "기본 앱",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 22fddbe9..f1a233f4 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -138,7 +138,9 @@
"scheduled": "Ieplānots",
"unified_all_unread": "Visi nelasītie",
"unified_all_starred": "Visi ar zvaigzni",
- "unified_all_mail": "Visas vēstules"
+ "unified_all_mail": "Visas vēstules",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Noklusējuma lietotnes",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index 43c63cc0..4438c9ec 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -138,7 +138,9 @@
"scheduled": "Gepland",
"unified_all_unread": "Alle ongelezen",
"unified_all_starred": "Alle met ster",
- "unified_all_mail": "Alle e-mail"
+ "unified_all_mail": "Alle e-mail",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Standaardapps",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index 4c4656be..be6642d9 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -138,7 +138,9 @@
"scheduled": "Zaplanowane",
"unified_all_unread": "Wszystkie nieprzeczytane",
"unified_all_starred": "Wszystkie oznaczone gwiazdką",
- "unified_all_mail": "Cała poczta"
+ "unified_all_mail": "Cała poczta",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Aplikacje domyślne",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index 17453f63..81cce094 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -138,7 +138,9 @@
"scheduled": "Agendados",
"unified_all_unread": "Tudo não lido",
"unified_all_starred": "Tudo com estrela",
- "unified_all_mail": "Todo o correio"
+ "unified_all_mail": "Todo o correio",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Aplicativos padrão",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 3c33493a..70a438c3 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -138,7 +138,9 @@
"scheduled": "Programat",
"unified_all_unread": "Toate necitite",
"unified_all_starred": "Toate cu stea",
- "unified_all_mail": "Toată poșta"
+ "unified_all_mail": "Toată poșta",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Aplicații implicite",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index f3859b57..4ad2a908 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -138,7 +138,9 @@
"scheduled": "Запланировано",
"unified_all_unread": "Все непрочитанные",
"unified_all_starred": "Все помеченные",
- "unified_all_mail": "Вся почта"
+ "unified_all_mail": "Вся почта",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Приложения по умолчанию",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index d6ab561e..2a4ac304 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -138,7 +138,9 @@
"scheduled": "Zamanlandı",
"unified_all_unread": "Tüm okunmamışlar",
"unified_all_starred": "Tüm yıldızlılar",
- "unified_all_mail": "Tüm postalar"
+ "unified_all_mail": "Tüm postalar",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Varsayılan uygulamalar",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index 95b1e129..63636400 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -138,7 +138,9 @@
"scheduled": "Заплановано",
"unified_all_unread": "Усі непрочитані",
"unified_all_starred": "Усі позначені",
- "unified_all_mail": "Уся пошта"
+ "unified_all_mail": "Уся пошта",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "Програми за замовчуванням",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index 637b257b..4c57d997 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -138,7 +138,9 @@
"scheduled": "已计划",
"unified_all_unread": "全部未读",
"unified_all_starred": "全部加星标",
- "unified_all_mail": "全部邮件"
+ "unified_all_mail": "全部邮件",
+ "remove_account": "Remove account",
+ "remove_account_confirm": "Remove {account} from this device? You can add it back later."
},
"protocol_handlers": {
"title": "默认应用",
diff --git a/stores/auth-store.ts b/stores/auth-store.ts
index e195d65c..392216ae 100644
--- a/stores/auth-store.ts
+++ b/stores/auth-store.ts
@@ -44,6 +44,7 @@ interface AuthState {
refreshAccessToken: () => Promise;
logout: () => void;
logoutAll: () => void;
+ removeAccount: (accountId: string) => void;
switchAccount: (accountId: string) => Promise;
checkAuth: () => Promise;
clearError: () => void;
@@ -1243,6 +1244,31 @@ export const useAuthStore = create()(
redirectToLogin();
},
+ // Remove a specific (typically non-active) account: tear down its client,
+ // drop it from the registry, and clear its per-slot cookies. If asked to
+ // remove the active account, defer to logout() which handles switching
+ // away or redirecting.
+ removeAccount: (accountId: string) => {
+ if (accountId === get().activeAccountId) { get().logout(); return; }
+ const accountStore = useAccountStore.getState();
+ const account = accountStore.getAccountById(accountId);
+ if (!account) return;
+ const slot = account.cookieSlot ?? 0;
+ const wasOAuth = account.authMode === 'oauth';
+
+ clearRefreshTimer(accountId);
+ const client = clients.get(accountId);
+ if (client) { try { client.disconnect(); } catch { /* noop */ } }
+ clients.delete(accountId);
+ evictAccount(accountId);
+ accountStore.removeAccount(accountId);
+
+ apiFetch(`/api/auth/session?slot=${slot}`, { method: 'DELETE', keepalive: true }).catch(() => {});
+ if (wasOAuth) {
+ apiFetch(`/api/auth/token?slot=${slot}`, { method: 'DELETE', keepalive: true }).catch(() => {});
+ }
+ },
+
logoutAll: () => {
// Disconnect all clients
for (const c of clients.values()) {