diff --git a/frontend/public/locales/en-US/application.json b/frontend/public/locales/en-US/application.json index 258ee73e..5d384687 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 eca26b8b..32055a49 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)); };