import { ArchiveIcon, CheckIcon, GlobeIcon, LogOutIcon, PaletteIcon, SettingsIcon, SquareUserIcon, User2Icon } from "lucide-react"; import { useAuth } from "@/contexts/AuthContext"; import useCurrentUser from "@/hooks/useCurrentUser"; import useNavigateTo from "@/hooks/useNavigateTo"; import { useUpdateUserGeneralSetting } from "@/hooks/useUserQueries"; import { locales } from "@/i18n"; import { cn } from "@/lib/utils"; import { Routes } from "@/router"; import { getLocaleDisplayName, getLocaleWithFallback, loadLocale, useTranslate } from "@/utils/i18n"; import { getThemeWithFallback, 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 { mutate: updateUserGeneralSetting } = useUpdateUserGeneralSetting(currentUser?.name); const currentLocale = getLocaleWithFallback(userGeneralSetting?.locale); const currentTheme = getThemeWithFallback(userGeneralSetting?.theme); const handleLocaleChange = async (locale: Locale) => { if (!currentUser) return; // Apply locale immediately for instant UI feedback and persist to localStorage loadLocale(locale); // Persist to user settings updateUserGeneralSetting( { generalSetting: { locale }, updateMask: ["locale"] }, { onSuccess: () => { refetchSettings(); }, }, ); }; const handleThemeChange = async (theme: string) => { if (!currentUser) return; // Apply theme immediately for instant UI feedback loadTheme(theme); // Persist to user settings updateUserGeneralSetting( { generalSetting: { theme }, updateMask: ["theme"] }, { onSuccess: () => { 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;