cameras/src/app/setup/setup-form.tsx
Michael Mainguy 190191df92 Add Lucide icons: camera status badge, actions and warnings
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>
2026-09-19 12:33:16 -05:00

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>
);
}