Vectrum
Sign In
84 lines · 3.1 KB · TypeScript
"use client";

import NextLink from "next/link";
import { useActionState } from "react";
import { Link } from "@unlocalhosted/metalui";
import { loginAction, signupAction, twoFactorLoginAction } from "@/app/actions/auth";
import { TextField } from "./settings-fields";
import { Alert, Button } from "./ui";

export function LoginForm({ next }: { next: string }) {
  const [state, action, pending] = useActionState(loginAction, undefined);
  return (
    <form action={action} className="flex flex-col gap-4">
      <input type="hidden" name="next" value={next} />
      {state?.error && <Alert>{state.error}</Alert>}
      <TextField label="Username or email address" name="login" required autoFocus autoComplete="username" />
      <TextField label="Password" name="password" type="password" required autoComplete="current-password" />
      <Button variant="primary" className="mt-1 w-full" disabled={pending}>
        {pending ? "Signing in…" : "Sign In"}
      </Button>
    </form>
  );
}

export function TwoFactorLoginForm({ next }: { next: string }) {
  const [state, action, pending] = useActionState(twoFactorLoginAction, undefined);
  return (
    <form action={action} className="flex flex-col gap-4">
      <input type="hidden" name="next" value={next} />
      {state?.error && <Alert>{state.error}</Alert>}
      <TextField
        label="Authentication code"
        description="From your authenticator app. Lost your device? Enter one of your recovery codes."
        name="code"
        required
        autoFocus
        autoComplete="one-time-code"
        autoCapitalize="none"
        spellCheck={false}
        maxLength={20}
        mono
      />
      <Button variant="primary" className="mt-1 w-full" disabled={pending}>
        {pending ? "Verifying…" : "Verify"}
      </Button>
    </form>
  );
}

export function SignupForm({ next }: { next: string }) {
  const [state, action, pending] = useActionState(signupAction, undefined);
  return (
    <form action={action} className="flex flex-col gap-4">
      <input type="hidden" name="next" value={next} />
      {state?.error && <Alert>{state.error}</Alert>}
      <TextField label="Email" name="email" type="email" required autoFocus autoComplete="email" />
      <TextField
        label="Password"
        description="At least 8 characters."
        name="password"
        type="password"
        required
        minLength={8}
        autoComplete="new-password"
      />
      <TextField
        label="Username"
        description="Letters, numbers and hyphens. This becomes your URL."
        name="username"
        required
        pattern="[A-Za-z0-9][A-Za-z0-9\-]{0,38}"
        autoComplete="username"
      />
      <TextField label="Name" optional name="name" autoComplete="name" />
      <Button variant="primary" className="mt-1 w-full" disabled={pending}>
        {pending ? "Creating account…" : "Create Account"}
      </Button>
      <p className="text-center type-body text-ink2">
        Already have an account?{" "}
        <Link render={<NextLink href={`/login${next !== "/" ? `?next=${encodeURIComponent(next)}` : ""}`} />}>Sign in</Link>
      </p>
    </form>
  );
}