betterNAS/apps/web/app/login/page.tsx
2026-04-01 20:26:44 -04:00

152 lines
4.9 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { login, register, ApiError } from "@/lib/api";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export default function LoginPage() {
const router = useRouter();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [mode, setMode] = useState<"login" | "register">("login");
async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
setError(null);
setLoading(true);
try {
if (mode === "login") {
await login(username, password);
} else {
await register(username, password);
}
router.push("/");
} catch (err) {
if (err instanceof ApiError) {
setError(err.message);
} else {
setError("Something went wrong");
}
} finally {
setLoading(false);
}
}
return (
<main className="flex min-h-screen items-center justify-center bg-background px-4">
<Card className="w-full max-w-sm">
<CardHeader className="text-center">
<p className="text-xs font-medium uppercase tracking-widest text-muted-foreground">
betterNAS
</p>
<CardTitle className="text-xl">
{mode === "login" ? "Sign in" : "Create account"}
</CardTitle>
<CardDescription>
{mode === "login"
? "Sign in to your betterNAS control plane with the same credentials you use for the node agent and Finder."
: "Create your betterNAS account. You will use the same username and password for the web app, node agent, and Finder."}
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label
htmlFor="username"
className="text-sm font-medium text-foreground"
>
Username
</label>
<input
id="username"
type="text"
autoComplete="username"
required
minLength={3}
maxLength={64}
value={username}
onChange={(e) => setUsername(e.target.value)}
className="rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none ring-ring focus:ring-2"
placeholder="admin"
/>
</div>
<div className="flex flex-col gap-1.5">
<label
htmlFor="password"
className="text-sm font-medium text-foreground"
>
Password
</label>
<input
id="password"
type="password"
autoComplete={
mode === "login" ? "current-password" : "new-password"
}
required
minLength={8}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none ring-ring focus:ring-2"
/>
</div>
{error && <p className="text-sm text-destructive">{error}</p>}
<Button type="submit" disabled={loading} className="w-full">
{loading
? "..."
: mode === "login"
? "Sign in"
: "Create account"}
</Button>
<p className="text-center text-sm text-muted-foreground">
{mode === "login" ? (
<>
No account?{" "}
<button
type="button"
onClick={() => {
setMode("register");
setError(null);
}}
className="text-foreground underline underline-offset-2"
>
Create one
</button>
</>
) : (
<>
Already have an account?{" "}
<button
type="button"
onClick={() => {
setMode("login");
setError(null);
}}
className="text-foreground underline underline-offset-2"
>
Sign in
</button>
</>
)}
</p>
</form>
</CardContent>
</Card>
</main>
);
}