import { create } from "@bufbuild/protobuf"; import { FieldMaskSchema } from "@bufbuild/protobuf/wkt"; import { ArchiveIcon, CheckIcon, GlobeIcon, LogOutIcon, PaletteIcon, SettingsIcon, SquareUserIcon, User2Icon } from "lucide-react"; import { userServiceClient } from "@/connect"; import { useAuth } from "@/contexts/AuthContext"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; import i18n, { locales } from "@/i18n"; import { cn } from "@/lib/utils"; import { Routes } from "@/router"; import { UserSetting_GeneralSettingSchema, UserSettingSchema } from "@/types/proto/api/v1/user_service_pb"; import { getLocaleDisplayName, useTranslate } from "@/utils/i18n"; import { loadTheme, THEME_OPTIONS } from "@/utils/theme"; import UserAvatar from "./UserAvatar"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./ui/dropdown-menu"; interface Props { collapsed?: boolean; } const UserMenu = (props: Props) => { const { collapsed } = props; const t = useTranslate(); const navigateTo = useNavigateTo(); const currentUser = useCurrentUser(); const { userGeneralSetting, refetchSettings, logout } = useAuth(); const currentLocale = userGeneralSetting?.locale || "en"; const currentTheme = userGeneralSetting?.theme || "default"; const handleLocaleChange = async (locale: Locale) => { if (!currentUser) return; // Apply locale immediately for instant UI feedback i18n.changeLanguage(locale); // Persist to user settings const settingName = `${currentUser.name}/setting`; const updatedGeneralSetting = create(UserSetting_GeneralSettingSchema, { locale, theme: userGeneralSetting?.theme, memoVisibility: userGeneralSetting?.memoVisibility, }); await userServiceClient.updateUserSetting({ setting: create(UserSettingSchema, { name: settingName, value: { case: "generalSetting", value: updatedGeneralSetting, }, }), updateMask: create(FieldMaskSchema, { paths: ["general_setting.locale"] }), }); await refetchSettings(); }; const handleThemeChange = async (theme: string) => { if (!currentUser) return; // Apply theme immediately for instant UI feedback loadTheme(theme); // Persist to user settings const settingName = `${currentUser.name}/setting`; const updatedGeneralSetting = create(UserSetting_GeneralSettingSchema, { locale: userGeneralSetting?.locale, theme, memoVisibility: userGeneralSetting?.memoVisibility, }); await userServiceClient.updateUserSetting({ setting: create(UserSettingSchema, { name: settingName, value: { case: "generalSetting", value: updatedGeneralSetting, }, }), updateMask: create(FieldMaskSchema, { paths: ["general_setting.theme"] }), }); await refetchSettings(); }; const handleSignOut = async () => { // First, clear auth state and cache BEFORE doing anything else await logout(); try { // Then clear user-specific localStorage items // Preserve app-wide settings (theme, locale, view preferences, tag view settings) const keysToPreserve = ["memos-theme", "memos-locale", "memos-view-setting", "tag-view-as-tree", "tag-tree-auto-expand"]; const keysToRemove: string[] = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && !keysToPreserve.includes(key)) { keysToRemove.push(key); } } keysToRemove.forEach((key) => localStorage.removeItem(key)); } catch { // Ignore errors from localStorage operations } // Always redirect to auth page (use replace to prevent back navigation) window.location.replace(Routes.AUTH); }; return (
{currentUser?.avatarUrl ? ( ) : ( )} {!collapsed && ( {currentUser?.displayName || currentUser?.username} )}
navigateTo(`/u/${encodeURIComponent(currentUser?.username ?? "")}`)}> {t("common.profile")} navigateTo(Routes.ARCHIVED)}> {t("common.archived")} {t("common.language")} {locales.map((locale) => ( handleLocaleChange(locale)}> {currentLocale === locale && } {currentLocale !== locale && } {getLocaleDisplayName(locale)} ))} {t("setting.preference-section.theme")} {THEME_OPTIONS.map((option) => ( handleThemeChange(option.value)}> {currentTheme === option.value && } {currentTheme !== option.value && } {option.label} ))} navigateTo(Routes.SETTING)}> {t("common.settings")} {t("common.sign-out")}
); }; export default UserMenu;