import { useMemo, useState } from "react"; import * as sync from "../syncEngine"; import { I } from "./icons.jsx"; /* ═══════ ACCOUNT MENU ═══════ */ export function AccountMenu({ user, onClose, onLogout, onUserUpdate, onOpenAdmin, onExportAll, dark }) { const choosePreference = async (change) => { try { onUserUpdate(await sync.updatePreferences(change)); } catch { /* the choice is presentation; failing to store it must not block the panel */ } }; const chooseFont = (noteFont) => choosePreference({ noteFont }); const chooseChapterView = (chapterView) => choosePreference({ chapterView }); const [tab, setTab] = useState("profile"); // profile | username | password const [displayName, setDisplayName] = useState(user?.displayName || ""); const [newUsername, setNewUsername] = useState(user?.username || ""); const [usernamePass, setUsernamePass] = useState(""); const [curPass, setCurPass] = useState(""); const [newPass, setNewPass] = useState(""); const [newPass2, setNewPass2] = useState(""); const [msg, setMsg] = useState(""); const [err, setErr] = useState(""); const [saving, setSaving] = useState(false); const T = useMemo( () => ({ bg: dark ? "#161b22" : "#fff", border: dark ? "#30363d" : "#d0d7de", text: dark ? "#e6edf3" : "#1f2328", muted: dark ? "#8b949e" : "#656d76", accent: dark ? "#388bfd" : "#0969da", danger: dark ? "#f85149" : "#cf222e", success: dark ? "#3fb950" : "#1a7f37", inputBg: dark ? "#0d1117" : "#f6f8fa", }), [dark] ); const inp = { width: "100%", padding: "10px 14px", borderRadius: 10, border: `1px solid ${T.border}`, background: T.inputBg, color: T.text, fontSize: 14, outline: "none", boxSizing: "border-box", marginBottom: 10, fontFamily: "'DM Sans',sans-serif", }; const tbtn = (active) => ({ padding: "7px 14px", borderRadius: 8, border: "none", cursor: "pointer", fontSize: 12, fontWeight: 600, background: active ? T.accent + "22" : "transparent", color: active ? T.accent : T.muted, }); const clear = () => { setMsg(""); setErr(""); }; const saveProfile = async () => { if (!displayName.trim()) return; clear(); setSaving(true); try { const u = await sync.updateProfile(displayName.trim()); onUserUpdate(u); setMsg("Display name updated"); } catch (e) { setErr(e.message); } setSaving(false); }; const saveUsername = async () => { if (!newUsername || !usernamePass) return; clear(); setSaving(true); try { const u = await sync.updateUsername(newUsername.trim(), usernamePass); onUserUpdate(u); setUsernamePass(""); setMsg("Username updated"); } catch (e) { setErr(e.message); } setSaving(false); }; const savePassword = async () => { if (!curPass || !newPass) return; if (newPass !== newPass2) { setErr("Passwords don't match"); return; } clear(); setSaving(true); try { await sync.updatePassword(curPass, newPass); setCurPass(""); setNewPass(""); setNewPass2(""); setMsg("Password changed"); } catch (e) { setErr(e.message); } setSaving(false); }; return (
e.stopPropagation()} >

Account

{msg && (
{msg}
)} {err && (
{err}
)} {tab === "profile" && ( <> setDisplayName(e.target.value)} style={inp} onKeyDown={(e) => { if (e.key === "Enter") saveProfile(); }} /> {/* Kept on the account rather than in this browser: a choice about how your own notes are set should follow you to the phone, not be made again on every device. The export is untouched — this is presentation and nothing else. */}
{[ ["serif", "Serif", "'Newsreader',Georgia,serif"], ["sans", "Sans", "'DM Sans',system-ui,sans-serif"], ["mono", "Mono", "ui-monospace,Menlo,Consolas,monospace"], ].map(([value, label, stack]) => { const active = (user.preferences?.noteFont || "serif") === value; return ( ); })}
{/* How a chapter opens. Kept beside the typeface because both are the same kind of thing: how your own notes are laid out, stored on the account so the answer follows you to the phone. */}
{[ ["column", "Beside the list", "Its notes in a further column, the way OneNote lays out pages"], ["inline", "Under the chapter", "Its notes unfold in place, below the chapter's own row"], ].map(([value, label, hint]) => { const active = (user.preferences?.chapterView || "column") === value; return ( ); })}
A phone has room for neither, and fills the screen either way — with a way back.
)} {tab === "username" && ( <> setNewUsername(e.target.value)} autoCapitalize="none" autoCorrect="off" spellCheck={false} style={inp} />
3–32 characters · letters, digits, . _ -
setUsernamePass(e.target.value)} style={inp} onKeyDown={(e) => { if (e.key === "Enter") saveUsername(); }} /> )} {tab === "password" && ( <> setCurPass(e.target.value)} style={inp} /> setNewPass(e.target.value)} style={inp} /> setNewPass2(e.target.value)} style={inp} onKeyDown={(e) => { if (e.key === "Enter") savePassword(); }} /> )}
{user?.isAdmin && ( )}
); }