diff --git a/frontend/public/locales/en-US/application.json b/frontend/public/locales/en-US/application.json
index 246ea265..fb0fd61e 100644
--- a/frontend/public/locales/en-US/application.json
+++ b/frontend/public/locales/en-US/application.json
@@ -163,7 +163,9 @@
"toDarkMode": "Dark",
"toLightMode": "Light",
"myProfile": "My profile",
- "dashboard": "Dashboard"
+ "dashboard": "Dashboard",
+ "addAccount": "Add account",
+ "signedOut": "Signed out"
},
"fileManager": {
"continueInDesktop": "Authorization successful, please continue in the desktop client.",
diff --git a/frontend/public/locales/zh-CN/application.json b/frontend/public/locales/zh-CN/application.json
index 65812cea..cf64d42f 100644
--- a/frontend/public/locales/zh-CN/application.json
+++ b/frontend/public/locales/zh-CN/application.json
@@ -163,7 +163,9 @@
"toDarkMode": "黑暗",
"toLightMode": "浅色",
"myProfile": "个人主页",
- "dashboard": "管理面板"
+ "dashboard": "管理面板",
+ "addAccount": "添加账号",
+ "signedOut": "已退出登录"
},
"fileManager": {
"continueInDesktop": "授权成功,请在桌面客户端中继续操作。",
diff --git a/frontend/src/component/Frame/NavBar/UserAction.tsx b/frontend/src/component/Frame/NavBar/UserAction.tsx
index fb00fa66..63666df3 100644
--- a/frontend/src/component/Frame/NavBar/UserAction.tsx
+++ b/frontend/src/component/Frame/NavBar/UserAction.tsx
@@ -24,6 +24,7 @@ import SessionManager, { Session } from "../../../session";
import { GroupBS } from "../../../session/utils.ts";
import UserAvatar from "../../Common/User/UserAvatar.tsx";
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
+import Add from "../../Icons/Add.tsx";
import HomeOutlined from "../../Icons/HomeOutlined.tsx";
import Person from "../../Icons/Person.tsx";
import SettingsOutlined from "../../Icons/SettingsOutlined.tsx";
@@ -59,6 +60,27 @@ const UserPopover = ({ open, onClose, ...rest }: PopoverProps) => {
onClose && onClose({}, "backdropClick");
}, []);
+ // Other stored sessions for the account switcher. Signed-out entries
+ // route back to the login page with their email prefilled instead of
+ // switching directly.
+ const otherSessions = SessionManager.listSessions().filter((s) => s.user.id !== user.id);
+
+ const switchAccount = (s: Session) => {
+ onClose?.({}, "backdropClick");
+ if (s.signedOut) {
+ signWithHint(s.user.email ?? "");
+ return;
+ }
+ if (SessionManager.switchTo(s.user.id)) {
+ window.location.reload();
+ }
+ };
+
+ const addAccount = () => {
+ navigate("/session");
+ onClose?.({}, "backdropClick");
+ };
+
const openMyProfile = useCallback(() => {
navigate(`/profile/${user?.id}`);
onClose && onClose({}, "backdropClick");
@@ -143,6 +165,26 @@ const UserPopover = ({ open, onClose, ...rest }: PopoverProps) => {
{t("login.logout")}
+
+
+ {otherSessions.map((s) => (
+ switchAccount(s)}>
+
+
+
+
+
+ ))}
+
+
+
+
+ {t("navbar.addAccount")}
+
+
);
};
diff --git a/frontend/src/session/index.ts b/frontend/src/session/index.ts
index e6b37600..dc612347 100644
--- a/frontend/src/session/index.ts
+++ b/frontend/src/session/index.ts
@@ -271,6 +271,24 @@ export class Manager {
this.syncToStore();
}
+ // listSessions returns every stored session for the account switcher.
+ // Signed-out entries are kept so the UI can offer one-click re-login.
+ public listSessions = (): Session[] => {
+ return Object.values(this.state.sessions);
+ };
+
+ // switchTo makes another stored session current; callers reload the app
+ // so all state re-derives from the new session.
+ public switchTo = (uid: string) => {
+ const session = this.state.sessions[uid];
+ if (!session || session.signedOut) {
+ return false;
+ }
+ this.state.current = uid;
+ this.syncToStore();
+ return true;
+ };
+
private syncToStore = () => {
localStorage.setItem(SESSION_KEY, JSON.stringify(this.state));
};