// Account-level pages: Support (live API), Settings (live API)
const { useState: useStateO, useEffect: useEffectO } = React;
function SupportPage({ ctx }) {
const { t } = ctx;
const [tickets, setTickets] = useStateO([]);
const [loading, setLoading] = useStateO(true);
const [creating, setCreating] = useStateO(false);
const [subject, setSubject] = useStateO("");
const [body, setBody] = useStateO("");
const [viewing, setViewing] = useStateO(null);
const [messages, setMessages] = useStateO([]);
const [replyBody, setReplyBody] = useStateO("");
useEffectO(() => { loadTickets(); }, []);
async function loadTickets() {
try {
const data = await ww.get("/tickets");
setTickets(data);
} catch {}
setLoading(false);
}
async function handleCreate(e) {
e.preventDefault();
if (!subject.trim() || !body.trim()) return;
try {
await ww.post("/tickets", { subject: subject.trim(), body: body.trim() });
setSubject(""); setBody(""); setCreating(false);
loadTickets();
} catch (err) { alert(err.message); }
}
async function openTicket(tk) {
setViewing(tk);
try {
const data = await ww.get("/tickets/" + tk.id);
setMessages(data.messages || []);
} catch (err) { alert(err.message); }
}
async function handleReply() {
if (!replyBody.trim() || !viewing) return;
try {
const msg = await ww.post("/tickets/" + viewing.id + "/reply", { body: replyBody.trim() });
setMessages([...messages, msg]);
setReplyBody("");
loadTickets();
} catch (err) { alert(err.message); }
}
function fmtDate(ts) {
return new Date(ts * 1000).toISOString().slice(0, 10);
}
function fmtTime(ts) {
return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, 19);
}
if (viewing) {
return (
{ setViewing(null); setMessages([]); setReplyBody(""); }}>← {t.common.back || "Back"}
{viewing.subject}
{viewing.state}
{messages.map((msg, i) => (
0 ? "1px solid var(--line-2)" : "none",
}}>
{msg.sender === "user" ? (t.support.you || "You") : (t.support.supportTeam || "Support")}
{fmtTime(msg.created_at)}
{msg.body}
))}
{messages.length === 0 &&
No messages.
}
{viewing.state === "open" && (
{t.support.reply || "Reply"}
)}
);
}
return (
{t.nav.support}
setCreating(!creating)}>{t.support.newTicket}
{creating && (
)}
{[t.support.opened, t.support.subject, t.support.state, t.support.lastUpdate, ""].map((c, i) =>
{c}
)}
{loading &&
Loading...
}
{!loading && tickets.length === 0 && (
No tickets.
)}
{tickets.map((tk) => (
openTicket(tk)} style={{ cursor: "pointer" }}>
{fmtDate(tk.created_at)}
{tk.subject}
{tk.state}
{fmtDate(tk.updated_at)}
→
))}
);
}
function SettingsPage({ ctx }) {
const { t } = ctx;
const [profile, setProfile] = useStateO(null);
const [tokens, setTokens] = useStateO([]);
const [changingPw, setChangingPw] = useStateO(false);
const [oldPw, setOldPw] = useStateO("");
const [newPw, setNewPw] = useStateO("");
const [pwMsg, setPwMsg] = useStateO("");
const [mfaSetup, setMfaSetup] = useStateO(null);
const [mfaCode, setMfaCode] = useStateO("");
const [mfaMsg, setMfaMsg] = useStateO("");
const [addingToken, setAddingToken] = useStateO(false);
const [tokenForm, setTokenForm] = useStateO({ name: "", scope: "readonly", expires_days: "" });
const [newTokenValue, setNewTokenValue] = useStateO("");
useEffectO(() => {
ww.get("/account").then(setProfile).catch(() => {});
ww.get("/tokens").then(setTokens).catch(() => {});
}, []);
async function handleDeleteAccount() {
if (!confirm(t.settings.deleteConfirm || "Are you sure? This cannot be undone.")) return;
try {
await ww.del("/account");
ww.logout();
window.location.href = "login.html";
} catch (err) { alert(err.message); }
}
async function handleRevokeToken(id) {
try {
await ww.del("/tokens/" + id);
setTokens(tokens.filter(tk => tk.id !== id));
} catch (err) { alert(err.message); }
}
const email = profile ? profile.email : ME.email;
const name = profile ? email.split("@")[0] : ME.name;
return (
{t.settings.title}
{t.settings.mfa}
{profile && profile.mfa_enabled ? t.settings.mfaOn : (t.settings.mfaOff || "Not configured")}
{profile && profile.mfa_enabled ? (
{
setMfaSetup("disable");
setMfaCode("");
setMfaMsg("");
}}>{t.settings.disable || "Disable"}
) : (
{
try {
const res = await ww.post("/account/mfa/enable");
setMfaSetup(res);
setMfaCode("");
setMfaMsg("");
} catch (e) { alert(e.message); }
}}>{t.settings.configure}
)}
{mfaSetup === "disable" && (
Enter your current MFA code to disable two-factor authentication.
setMfaCode(e.target.value)} placeholder="6-digit code" style={{ width: 200, fontFamily: "var(--mono)" }}/>
{mfaMsg &&
{mfaMsg}
}
{
try {
await ww.post("/account/mfa/disable", { mfa_code: mfaCode });
setMfaSetup(null);
setProfile({ ...profile, mfa_enabled: false });
} catch (e) { setMfaMsg(e.message); }
}}>{t.settings.disable || "Disable MFA"}
setMfaSetup(null)}>{t.common.cancel}
)}
{mfaSetup && mfaSetup !== "disable" && (
Scan this URI with your authenticator app, or enter the secret manually:
{mfaSetup.secret}
OTP URI: {mfaSetup.otp_uri}
MFA has been enabled. You will need your authenticator code on next login.
{ setMfaSetup(null); setProfile({ ...profile, mfa_enabled: true }); }}>{t.common.close || "Done"}
)}
{t.settings.password}
{t.settings.passwordHint}
setChangingPw(!changingPw)}>{t.settings.changePassword}
{changingPw && (
)}
setAddingToken(!addingToken)}>+ {t.tokens.add}} pad={0}>
{t.tokens.accountHint}
{addingToken && (
Scope
setAddingToken(false)}>{t.common.cancel}
{
if (!tokenForm.name.trim()) return;
try {
const body = { name: tokenForm.name.trim(), scope: tokenForm.scope };
if (tokenForm.expires_days) body.expires_days = parseInt(tokenForm.expires_days);
const r = await ww.post("/tokens", body);
setNewTokenValue(r.token);
setTokens([r, ...tokens]);
setTokenForm({ name: "", scope: "readonly", expires_days: "" });
setAddingToken(false);
} catch (e) { alert(e.message); }
}}>{t.common.save}
)}
{newTokenValue && (
New token:
{newTokenValue}
{ navigator.clipboard.writeText(newTokenValue); }}>Copy
setNewTokenValue("")}>Dismiss
)}
{[t.tokens.colName, t.tokens.colToken, t.tokens.colScopes, t.tokens.colCreated, t.tokens.colExpires, ""].map((c, i) =>
{c}
)}
{tokens.length === 0 && !addingToken && (
No API tokens.
)}
{tokens.map((tk) => (
{tk.name}
{tk.token_prefix || "ww_***"}
{tk.scope}
{new Date(tk.created_at * 1000).toISOString().slice(0, 10)}
{tk.expires_at ? new Date(tk.expires_at * 1000).toISOString().slice(0, 10) : "never"}
handleRevokeToken(tk.id)}>{t.tokens.revoke}
))}
{t.settings.deleteBody}
{t.settings.deleteMe}
);
}
Object.assign(window, { SupportPage, SettingsPage });