daat-locus 0.4.0

A long-running local agent runtime with memory, workflows, apps, and sleep-time self-improvement.
import { type FormEvent, useState } from "react";
import { useTranslation } from "react-i18next";

import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardAction,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  Field,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import {
  clearStoredDaemonToken,
  getStoredDaemonToken,
  storeDaemonToken,
  verifyDaemonToken,
} from "@/lib/daemon-auth";

type LoginState = "idle" | "checking" | "authenticated" | "error";

export function LoginPage({
  onAuthenticated,
}: {
  onAuthenticated: () => void;
}) {
  const { t } = useTranslation();
  const [token, setToken] = useState(() => getStoredDaemonToken());
  const [loginState, setLoginState] = useState<LoginState>("idle");
  const [message, setMessage] = useState("");

  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const trimmedToken = token.trim();

    if (!trimmedToken) {
      setLoginState("error");
      setMessage(t("login.enterToken"));
      return;
    }

    setLoginState("checking");
    setMessage(t("login.verifyingToken"));

    const result = await verifyDaemonToken(trimmedToken);
    if (result.ok) {
      storeDaemonToken(trimmedToken);
      setToken(trimmedToken);
      setLoginState("authenticated");
      setMessage(t("login.verifiedToken"));
      onAuthenticated();
      return;
    }

    clearStoredDaemonToken();
    setLoginState("error");
    setMessage(result.message);
  }

  const isChecking = loginState === "checking";
  const isError = loginState === "error";

  return (
    <section
      id="login"
      className="flex min-h-screen w-full items-center justify-center bg-background px-6 py-10"
    >
      <Card className="w-full max-w-md">
        <CardHeader>
          <CardTitle>Daat Locus</CardTitle>
          <CardAction>
            <Badge variant="secondary">WebUI</Badge>
          </CardAction>
        </CardHeader>
        <CardContent>
          <form onSubmit={handleSubmit}>
            <FieldGroup>
              <Field data-invalid={isError} data-disabled={isChecking}>
                <FieldLabel htmlFor="daemon-token">{t("login.daemonToken")}</FieldLabel>
                <Input
                  id="daemon-token"
                  aria-invalid={isError}
                  value={token}
                  onChange={(event) => {
                    setToken(event.target.value);
                    setMessage("");
                    if (loginState !== "checking") {
                      setLoginState("idle");
                    }
                  }}
                  placeholder={t("login.tokenPlaceholder")}
                  type="password"
                  autoComplete="current-password"
                  spellCheck={false}
                  disabled={isChecking}
                  required
                />
                <FieldError>{isError ? message : null}</FieldError>
              </Field>
              <Button type="submit" disabled={isChecking}>
                {isChecking ? <Spinner data-icon="inline-start" /> : null}
                {isChecking ? t("login.verifying") : t("login.submit")}
              </Button>
            </FieldGroup>
          </form>
        </CardContent>
      </Card>
    </section>
  );
}