"use client"; import { useEffect, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { api } from "@/lib/api/client"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; export default function SettingsPage() { const router = useRouter(); const [me, setMe] = useState<{ display_name: string | null; email: string } | null>(null); const [current, setCurrent] = useState(""); const [next, setNext] = useState(""); const [confirm, setConfirm] = useState(""); const [msg, setMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null); const [busy, setBusy] = useState(false); // Data export / restore / delete. const [exporting, setExporting] = useState(false); const [restoreMsg, setRestoreMsg] = useState(null); const [restoring, setRestoring] = useState(false); const restoreRef = useRef(null); const [deleteConfirm, setDeleteConfirm] = useState(""); const [deleting, setDeleting] = useState(false); useEffect(() => { api.GET("/api/v1/users/me").then((r) => setMe(r.data ?? null)); }, []); async function exportData() { setExporting(true); const resp = await fetch("/api/v1/users/me/export", { credentials: "include" }); if (resp.ok) { const blob = await resp.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "provenance-export.zip"; a.click(); URL.revokeObjectURL(url); } setExporting(false); } async function restoreData(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (restoreRef.current) restoreRef.current.value = ""; if (!file) return; setRestoring(true); setRestoreMsg(null); const fd = new FormData(); fd.append("file", file); const resp = await fetch("/api/v1/users/me/import", { method: "POST", body: fd, credentials: "include", }); setRestoring(false); if (resp.ok) { const c = await resp.json(); setRestoreMsg(`Restored ${c.trees} tree(s), ${c.persons} people into new trees.`); } else { setRestoreMsg("That doesn't look like a valid Provenance export."); } } async function deleteAccount() { if (deleteConfirm !== me?.email) return; setDeleting(true); const fd = new FormData(); fd.append("confirm_email", deleteConfirm); const resp = await fetch("/api/v1/users/me", { method: "DELETE", body: fd, credentials: "include", }); setDeleting(false); if (resp.ok) { await api.POST("/api/v1/auth/logout"); router.push("/register"); } } async function changePassword(e: React.FormEvent) { e.preventDefault(); setMsg(null); if (next.length < 8) { setMsg({ kind: "err", text: "New password must be at least 8 characters." }); return; } if (next !== confirm) { setMsg({ kind: "err", text: "New passwords don't match." }); return; } setBusy(true); const { error } = await api.POST("/api/v1/auth/change-password", { body: { current_password: current, new_password: next }, }); setBusy(false); if (error) { setMsg({ kind: "err", text: "Current password is incorrect." }); return; } setCurrent(""); setNext(""); setConfirm(""); setMsg({ kind: "ok", text: "Password changed." }); } return (

Settings

Account
Name: {me?.display_name ?? "—"}
Email: {me?.email ?? "—"}
Change password
setCurrent(e.target.value)} /> setNext(e.target.value)} /> setConfirm(e.target.value)} /> {msg && (

{msg.text}

)}
Your data

Download a complete backup of every tree you own — people, sources, media, and all — as a zip.

Restore a backup. It’s imported into new trees — nothing existing is touched or overwritten.

{restoreMsg &&

{restoreMsg}

}
Delete account

This deletes your account, the trees you own, and signs you out everywhere. Export your data first if you might want it. Type {me?.email} to confirm.

setDeleteConfirm(e.target.value)} />
); }