SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
2.5 KB · 51 lines tsx
Raw Blame History
1"use client";23import { KeyRound } from "lucide-react";4import { useActionState } from "react";5import { LogoMark } from "@/components/brand/logo";6import { Button } from "@/components/ui";7import { loginAction, type LoginState } from "@/app/admin/login/actions";89export function LoginForm({ next, reason }: { next: string; reason?: string }) {10  const [state, action, pending] = useActionState<LoginState, FormData>(loginAction, {});11  return (12    <div className="w-full max-w-[380px] rounded-sm border border-hair bg-surface">13      <div className="flex items-center gap-2.5 border-b border-hair px-4 py-3">14        <LogoMark size={22} />15        <div>16          <div className="text-[13.5px] font-semibold leading-tight text-ink">DataCenterIndex · Admin</div>17          <div className="text-[11.5px] text-ink-3">Operations console</div>18        </div>19      </div>20      <form action={action} className="space-y-3 px-4 py-4">21        <input type="hidden" name="next" value={next} />22        {reason === "expired" && !state.error && <p className="rounded-xs border border-warn/30 bg-warn/10 px-2.5 py-2 text-[12.5px] text-warn">Your session expired or the token was rejected. Sign in again.</p>}23        {reason === "logged-out" && !state.error && <p className="rounded-xs border border-hair bg-surface-2 px-2.5 py-2 text-[12.5px] text-ink-2">Signed out.</p>}24        <label className="block">25          <span className="label-caps mb-1 block">Admin token</span>26          <input27            name="token"28            type="password"29            autoComplete="current-password"30            required31            autoFocus32            spellCheck={false}33            className="h-10 w-full rounded-sm border border-hair-2 bg-paper px-3 font-mono text-[13px] text-ink outline-none focus:border-ink"34            placeholder="x-dci-admin-token"35          />36        </label>37        {state.error && (38          <p role="alert" className="rounded-xs border border-danger/30 bg-danger/10 px-2.5 py-2 text-[12.5px] text-danger">39            {state.error}40          </p>41        )}42        <Button type="submit" variant="primary" className="w-full" disabled={pending}>43          <KeyRound className="size-4" strokeWidth={1.75} />44          {pending ? "Checking…" : "Sign in"}45        </Button>46        <p className="text-[11.5px] leading-snug text-ink-4">The token is verified against the API and kept in an httpOnly cookie for 12 hours. It is never sent to the browser bundle.</p>47      </form>48    </div>49  );50}51