"use client";
import NextLink from "next/link";
import { useActionState } from "react";
import { Link } from "@unlocalhosted/metalui";
import { forgotPasswordAction, loginAction, resetPasswordAction, signupAction, twoFactorLoginAction } from "@/app/actions/auth";
import { TextField } from "./settings-fields";
import { Alert, Button } from "./ui";
/** With `canReset`, links to resetting a forgotten password (only when the server can send email). */
export function LoginForm({ next, canReset }: { next: string; canReset?: boolean }) {
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"
description={canReset && <Link render={<NextLink href="/login/forgot-password" />}>Forgot your password?</Link>}
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>
);
}
export function ForgotPasswordForm() {
const [state, action, pending] = useActionState(forgotPasswordAction, undefined);
if (state?.success) return <Alert kind="success">{state.success}</Alert>;
return (
<form action={action} className="flex flex-col gap-4">
{state?.error && <Alert>{state.error}</Alert>}
<TextField label="Username or email address" name="login" required autoFocus autoComplete="username" />
<Button variant="primary" className="mt-1 w-full" disabled={pending}>
{pending ? "Sending…" : "Send Link"}
</Button>
</form>
);
}
export function ResetPasswordForm({ token, username }: { token: string; username: string }) {
const [state, action, pending] = useActionState(resetPasswordAction, undefined);
return (
<form action={action} className="flex flex-col gap-4">
<input type="hidden" name="token" value={token} />
{/* For password managers. */}
<input type="hidden" name="username" value={username} autoComplete="username" />
{state?.error && <Alert>{state.error}</Alert>}
<TextField
label="New password"
description="At least 8 characters."
name="password"
type="password"
required
minLength={8}
autoFocus
autoComplete="new-password"
/>
<TextField label="Confirm new password" name="confirm" type="password" required minLength={8} autoComplete="new-password" />
<Button variant="primary" className="mt-1 w-full" disabled={pending}>
{pending ? "Saving…" : "Set Password"}
</Button>
</form>
);
}