Adds lucide-react (vrek dec-1vnz5fw, chosen over Heroicons and React Icons) for the icons goal gol-m8je1wc. - Camera status badge on each card (iss-scj42jq): Live, Connecting, Needs login, Not activated, Offline, Session ended. Each is an icon plus a visible label, derived from existing query state with no extra device calls. Logic in src/app/camera-status.tsx. - Action icons (iss-chrdd0v): scan (radar, then spinner), refresh rate, change or forget login, retry, the camera web page link (replaces ↗), save, sign in and sign out. - Warning and error icons (iss-egy082x): unsecured banner, setup panel, inline errors, session-ended notice, login and setup headings. Icons are decorative (aria-hidden), spinners respect reduced motion, and every accessible name is unchanged (all 403 prior tests pass untouched). 416 tests, 99.9% line coverage. The vitest threshold comment now points to principle pri-be2smzk. Refreshes the vrek export. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { CircleAlert, LoaderCircle, ShieldCheck } from "lucide-react";
|
|
import { useActionState } from "react";
|
|
import { setupAdmin, type FormState } from "../auth-actions";
|
|
|
|
const inputClass =
|
|
"rounded border border-zinc-300 bg-transparent px-2 py-1.5 dark:border-zinc-700";
|
|
|
|
export default function SetupForm() {
|
|
const [state, action, pending] = useActionState<FormState, FormData>(setupAdmin, {});
|
|
|
|
return (
|
|
<form action={action} className="mt-6 flex flex-col gap-3 text-sm">
|
|
<label className="flex flex-col gap-1">
|
|
Setup code (from the server console)
|
|
<input
|
|
name="code"
|
|
autoComplete="off"
|
|
placeholder="XXXX-XXXX"
|
|
required
|
|
className={`${inputClass} font-mono uppercase`}
|
|
/>
|
|
</label>
|
|
<label className="flex flex-col gap-1">
|
|
Admin username
|
|
<input name="username" defaultValue="admin" autoComplete="username" required className={inputClass} />
|
|
</label>
|
|
<label className="flex flex-col gap-1">
|
|
Password (at least 12 characters)
|
|
<input
|
|
name="password"
|
|
type="password"
|
|
autoComplete="new-password"
|
|
minLength={12}
|
|
required
|
|
className={inputClass}
|
|
/>
|
|
</label>
|
|
<label className="flex flex-col gap-1">
|
|
Repeat password
|
|
<input
|
|
name="confirm"
|
|
type="password"
|
|
autoComplete="new-password"
|
|
minLength={12}
|
|
required
|
|
className={inputClass}
|
|
/>
|
|
</label>
|
|
{state.error && (
|
|
<p role="alert" className="flex items-center gap-1.5 text-red-600">
|
|
<CircleAlert aria-hidden className="size-4 shrink-0" />
|
|
{state.error}
|
|
</p>
|
|
)}
|
|
<button
|
|
type="submit"
|
|
disabled={pending}
|
|
className="inline-flex items-center justify-center gap-2 rounded-md bg-black px-4 py-2 font-medium text-white disabled:opacity-50 dark:bg-white dark:text-black"
|
|
>
|
|
{pending ? (
|
|
<LoaderCircle aria-hidden className="size-4 animate-spin motion-reduce:animate-none" />
|
|
) : (
|
|
<ShieldCheck aria-hidden className="size-4" />
|
|
)}
|
|
{pending ? "Securing…" : "Create admin and sign in"}
|
|
</button>
|
|
</form>
|
|
);
|
|
}
|