105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
// components/account/PasswordChange.tsx
|
|
"use client";
|
|
|
|
import { useState } from "react";
|
|
|
|
export default function PasswordChange() {
|
|
const [current, setCurrent] = useState("");
|
|
const [next, setNext] = useState("");
|
|
const [next2, setNext2] = useState("");
|
|
const [busy, setBusy] = useState(false);
|
|
const [msg, setMsg] = useState<string | null>(null);
|
|
|
|
const onSave = async () => {
|
|
setMsg(null);
|
|
if (next !== next2) {
|
|
setMsg("New passwords do not match.");
|
|
return;
|
|
}
|
|
if (next.length < 8) {
|
|
setMsg("Password must be at least 8 characters.");
|
|
return;
|
|
}
|
|
setBusy(true);
|
|
try {
|
|
const r = await fetch("/api/account/password", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ current, next }),
|
|
});
|
|
|
|
// ⬇️ Parse JSON **then** use debug if present
|
|
const j = await r.json().catch(() => ({} as any));
|
|
|
|
if (!r.ok) {
|
|
// ⬇️ This is the "debug block" so you can see the upstream reason
|
|
setMsg(
|
|
j?.error
|
|
? j?.debug
|
|
? `${j.error} (${j.debug})`
|
|
: j.error
|
|
: "Password change failed"
|
|
);
|
|
return;
|
|
}
|
|
|
|
setMsg("Password updated.");
|
|
setCurrent("");
|
|
setNext("");
|
|
setNext2("");
|
|
} catch (e: any) {
|
|
setMsg(e?.message || "Password change failed");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div id="security" className="rounded-md border p-4 space-y-3">
|
|
<h3 className="font-semibold">Change Password</h3>
|
|
|
|
<div className="grid sm:grid-cols-2 gap-3 text-sm">
|
|
<label className="grid gap-1 sm:col-span-2">
|
|
<span className="opacity-60">Current Password</span>
|
|
<input
|
|
className="rounded-md border px-2 py-1"
|
|
type="password"
|
|
value={current}
|
|
onChange={(e) => setCurrent(e.target.value)}
|
|
/>
|
|
</label>
|
|
|
|
<label className="grid gap-1">
|
|
<span className="opacity-60">New Password</span>
|
|
<input
|
|
className="rounded-md border px-2 py-1"
|
|
type="password"
|
|
value={next}
|
|
onChange={(e) => setNext(e.target.value)}
|
|
/>
|
|
</label>
|
|
|
|
<label className="grid gap-1">
|
|
<span className="opacity-60">Confirm New Password</span>
|
|
<input
|
|
className="rounded-md border px-2 py-1"
|
|
type="password"
|
|
value={next2}
|
|
onChange={(e) => setNext2(e.target.value)}
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<button
|
|
onClick={onSave}
|
|
disabled={busy}
|
|
className="rounded-md bg-black text-white px-3 py-2 text-sm disabled:opacity-60"
|
|
>
|
|
{busy ? "Saving…" : "Update Password"}
|
|
</button>
|
|
{msg && <div className="text-sm opacity-80">{msg}</div>}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|