import React, { useState, useEffect } from "react"; import { Label } from "@/components/ui/label.tsx"; import { Button } from "@/components/ui/button.tsx"; import { PasswordInput } from "@/components/ui/password-input.tsx"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@/components/ui/tabs.tsx"; import { Separator } from "@/components/ui/separator.tsx"; import { Switch } from "@/components/ui/switch.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select.tsx"; import { User, Shield, AlertCircle, Palette, Sun, Moon, Monitor, } from "lucide-react"; import { useTheme } from "@/components/theme-provider"; import { TOTPSetup } from "@/ui/desktop/user/TOTPSetup.tsx"; import { getUserInfo, getVersionInfo, deleteAccount, logoutUser, isElectron, getUserRoles, type UserRole, } from "@/ui/main-axios.ts"; import { PasswordReset } from "@/ui/desktop/user/PasswordReset.tsx"; import { useTranslation } from "react-i18next"; import { LanguageSwitcher } from "@/ui/desktop/user/LanguageSwitcher.tsx"; import { useSidebar } from "@/components/ui/sidebar.tsx"; import { toast } from "sonner"; interface UserProfileProps { isTopbarOpen?: boolean; rightSidebarOpen?: boolean; rightSidebarWidth?: number; } async function handleLogout() { try { await logoutUser(); if (isElectron()) { localStorage.removeItem("jwt"); const configuredServerUrl = ( window as Window & typeof globalThis & { configuredServerUrl?: string; } ).configuredServerUrl; if (configuredServerUrl) { const iframe = document.querySelector("iframe"); if (iframe && iframe.contentWindow) { try { const serverOrigin = new URL(configuredServerUrl).origin; iframe.contentWindow.postMessage( { type: "CLEAR_AUTH_DATA", timestamp: Date.now(), }, serverOrigin, ); } catch (err) { console.error("User profile operation failed:", err); } } } } window.location.reload(); } catch (error) { console.error("Logout failed:", error); window.location.reload(); } } export function UserProfile({ isTopbarOpen = true, rightSidebarOpen = false, rightSidebarWidth = 400, }: UserProfileProps) { const { t } = useTranslation(); const { state: sidebarState } = useSidebar(); const { theme, setTheme } = useTheme(); const [userInfo, setUserInfo] = useState<{ username: string; is_admin: boolean; is_oidc: boolean; is_dual_auth: boolean; totp_enabled: boolean; } | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [versionInfo, setVersionInfo] = useState<{ version: string } | null>( null, ); const [deleteAccountOpen, setDeleteAccountOpen] = useState(false); const [deletePassword, setDeletePassword] = useState(""); const [deleteLoading, setDeleteLoading] = useState(false); const [deleteError, setDeleteError] = useState(null); const [fileColorCoding, setFileColorCoding] = useState( localStorage.getItem("fileColorCoding") !== "false", ); const [commandAutocomplete, setCommandAutocomplete] = useState( localStorage.getItem("commandAutocomplete") === "true", ); const [terminalSyntaxHighlighting, setTerminalSyntaxHighlighting] = useState( () => localStorage.getItem("terminalSyntaxHighlighting") === "true", ); const [defaultSnippetFoldersCollapsed, setDefaultSnippetFoldersCollapsed] = useState( localStorage.getItem("defaultSnippetFoldersCollapsed") !== "false", ); const [showHostTags, setShowHostTags] = useState(() => { const saved = localStorage.getItem("showHostTags"); return saved !== null ? saved === "true" : true; }); const [userRoles, setUserRoles] = useState([]); useEffect(() => { fetchUserInfo(); fetchVersion(); }, []); const fetchVersion = async () => { try { const info = await getVersionInfo(); setVersionInfo({ version: info.localVersion }); } catch { toast.error(t("user.failedToLoadVersionInfo")); } }; const fetchUserInfo = async () => { setLoading(true); setError(null); try { const info = await getUserInfo(); setUserInfo({ username: info.username, is_admin: info.is_admin, is_oidc: info.is_oidc, is_dual_auth: info.is_dual_auth || false, totp_enabled: info.totp_enabled || false, }); try { const rolesResponse = await getUserRoles(info.userId); setUserRoles(rolesResponse.roles || []); } catch (rolesErr) { console.error("Failed to fetch user roles:", rolesErr); setUserRoles([]); } } catch (err: unknown) { const error = err as { response?: { data?: { error?: string } } }; setError(error?.response?.data?.error || t("errors.loadFailed")); } finally { setLoading(false); } }; const handleTOTPStatusChange = (enabled: boolean) => { if (userInfo) { setUserInfo({ ...userInfo, totp_enabled: enabled }); } }; const handleFileColorCodingToggle = (enabled: boolean) => { setFileColorCoding(enabled); localStorage.setItem("fileColorCoding", enabled.toString()); window.dispatchEvent(new Event("fileColorCodingChanged")); }; const handleCommandAutocompleteToggle = (enabled: boolean) => { setCommandAutocomplete(enabled); localStorage.setItem("commandAutocomplete", enabled.toString()); }; const handleTerminalSyntaxHighlightingToggle = (enabled: boolean) => { setTerminalSyntaxHighlighting(enabled); localStorage.setItem("terminalSyntaxHighlighting", enabled.toString()); window.dispatchEvent(new Event("terminalSyntaxHighlightingChanged")); }; const handleDefaultSnippetFoldersCollapsedToggle = (enabled: boolean) => { setDefaultSnippetFoldersCollapsed(enabled); localStorage.setItem("defaultSnippetFoldersCollapsed", enabled.toString()); window.dispatchEvent(new Event("defaultSnippetFoldersCollapsedChanged")); }; const handleShowHostTagsToggle = (enabled: boolean) => { setShowHostTags(enabled); localStorage.setItem("showHostTags", enabled.toString()); window.dispatchEvent(new Event("showHostTagsChanged")); }; const handleDeleteAccount = async (e: React.FormEvent) => { e.preventDefault(); setDeleteLoading(true); setDeleteError(null); if (!deletePassword.trim()) { setDeleteError(t("leftSidebar.passwordRequired")); setDeleteLoading(false); return; } try { await deleteAccount(deletePassword); handleLogout(); } catch (err: unknown) { setDeleteError( (err as { response?: { data?: { error?: string } } })?.response?.data ?.error || t("leftSidebar.failedToDeleteAccount"), ); setDeleteLoading(false); } }; const topMarginPx = isTopbarOpen ? 74 : 26; const leftMarginPx = sidebarState === "collapsed" ? 26 : 8; const bottomMarginPx = 8; const wrapperStyle: React.CSSProperties = { marginLeft: leftMarginPx, marginRight: rightSidebarOpen ? `calc(var(--right-sidebar-width, ${rightSidebarWidth}px) + 8px)` : 17, marginTop: topMarginPx, marginBottom: bottomMarginPx, height: `calc(100vh - ${topMarginPx + bottomMarginPx}px)`, transition: "margin-left 200ms linear, margin-right 200ms linear, margin-top 200ms linear", }; if (loading) { return (

{t("nav.userProfile")}

{t("common.loading")}
); } if (error || !userInfo) { return (

{t("nav.userProfile")}

{t("common.error")} {error || t("errors.loadFailed")}
); } return ( <>

{t("nav.userProfile")}

{t("profile.account")} {t("profile.appearance")} {(!userInfo.is_oidc || userInfo.is_dual_auth) && ( {t("profile.security")} )}

{t("profile.accountInfo")}

{userInfo.username}

{userRoles.length > 0 ? (
{userRoles.map((role) => ( {t(role.roleDisplayName)} ))}
) : (

{userInfo.is_admin ? t("interface.administrator") : t("interface.user")}

)}

{userInfo.is_dual_auth ? t("profile.externalAndLocal") : userInfo.is_oidc ? t("profile.external") : t("profile.local")}

{userInfo.is_oidc && !userInfo.is_dual_auth ? ( {t("auth.lockedOidcAuth")} ) : userInfo.totp_enabled ? ( {t("common.enabled")} ) : ( {t("common.disabled")} )}

{versionInfo?.version || t("common.loading")}

{t("leftSidebar.deleteAccountWarningShort")}

{t("profile.languageLocalization")}

{t("profile.selectPreferredLanguage")}

{t("profile.appearance")}

{t("profile.appearanceDesc")}

{t("profile.fileManagerSettings")}

{t("profile.fileColorCodingDesc")}

{t("profile.terminalSettings")}

{t("profile.commandAutocompleteDesc")}

{t("profile.terminalSyntaxHighlightingDesc")}

{t("profile.hostSidebarSettings")}

{t("profile.showHostTagsDesc")}

{t("profile.snippetsSettings")}

{t("profile.defaultSnippetFoldersCollapsedDesc")}

{(!userInfo.is_oidc || userInfo.is_dual_auth) && ( )}
{deleteAccountOpen && (
e.stopPropagation()} >

{t("leftSidebar.deleteAccount")}

{t("leftSidebar.deleteAccountWarning")} {t("common.warning")} {t("leftSidebar.deleteAccountWarningDetails")} {deleteError && ( {t("common.error")} {deleteError} )}
setDeletePassword(e.target.value)} placeholder={t("placeholders.confirmPassword")} required />
{ setDeleteAccountOpen(false); setDeletePassword(""); setDeleteError(null); }} />
)} ); }