SPB Git

spb/autoscholar

Public
3.0 KB · 99 lines tsx
1"use client";2import { useRouter } from "next/navigation";3import { type FormEvent, useState } from "react";4import { type ApiError, api, setCsrf } from "@/lib/client/api";56export default function ChangePasswordPage() {7  const router = useRouter();8  const [err, setErr] = useState<string | null>(null);9  const [busy, setBusy] = useState(false);10  async function submit(e: FormEvent<HTMLFormElement>) {11    e.preventDefault();12    setBusy(true);13    setErr(null);14    const fd = new FormData(e.currentTarget);15    try {16      const r = await api<{ csrfToken: string }>("/api/admin/auth/change-password", {17        json: {18          currentPassword: fd.get("currentPassword"),19          newPassword: fd.get("newPassword"),20          confirm: fd.get("confirm"),21        },22      });23      setCsrf(r.csrfToken);24      router.replace("/admin");25      router.refresh();26    } catch (e2) {27      const ae = e2 as ApiError;28      setErr(29        Array.isArray(ae.details) && typeof ae.details[0] === "string"30          ? `${ae.message}`31          : (ae.message ?? "Failed"),32      );33    } finally {34      setBusy(false);35    }36  }37  return (38    <main className="mx-auto flex min-h-dvh max-w-sm flex-col justify-center px-4">39      <p className="eyebrow">Security</p>40      <h1 className="title-2 mt-1">Set a new password</h1>41      <p className="muted mt-1 text-sm">42        The bootstrap password only works once. Choose at least 12 characters with three character classes; it43        must not be a well-known password or contain your username.44      </p>45      <form onSubmit={submit} className="mt-6 space-y-4">46        <div>47          <label htmlFor="currentPassword" className="label mb-1">48            Current password49          </label>50          <input51            id="currentPassword"52            name="currentPassword"53            type="password"54            autoComplete="current-password"55            required56            className="input"57          />58        </div>59        <div>60          <label htmlFor="newPassword" className="label mb-1">61            New password62          </label>63          <input64            id="newPassword"65            name="newPassword"66            type="password"67            autoComplete="new-password"68            minLength={12}69            required70            className="input"71          />72        </div>73        <div>74          <label htmlFor="confirm" className="label mb-1">75            Confirm new password76          </label>77          <input78            id="confirm"79            name="confirm"80            type="password"81            autoComplete="new-password"82            minLength={12}83            required84            className="input"85          />86        </div>87        {err && (88          <p role="alert" className="text-sm" style={{ color: "var(--color-danger-500)" }}>89            {err}90          </p>91        )}92        <button type="submit" className="btn btn-primary w-full" disabled={busy}>93          {busy ? "Saving…" : "Change password"}94        </button>95      </form>96    </main>97  );98}99
Files · main
Menu

Theme