web/src/components/login.tsx
3.8 KB · sha256:05c2780c81731cb276fb0ec990caf83d8f2fc9a23cf92a5b4db37378eca6bd03
import { useState } from "react";
import { useAuth } from "../lib/auth";
import { useTheme } from "../lib/theme";
import { Moon, Sun, Terminal } from "lucide-react";
import { Button, Card, CardBody, CardHeader, Input, Logo } from "./ui";
export function LoginScreen() {
const { signIn, error, loading } = useAuth();
const { theme, toggle } = useTheme();
const [token, setToken] = useState("");
const [submitting, setSubmitting] = useState(false);
return (
<div className="bg-surface-2 relative flex min-h-screen items-center justify-center px-4">
<button
type="button"
onClick={toggle}
aria-label="Toggle theme"
className="text-fg-muted hover:text-fg hover:bg-surface-3 absolute top-4 right-4 inline-flex h-9 w-9 items-center justify-center rounded-lg transition"
>
{theme === "dark" ? <Sun size={18} /> : <Moon size={18} />}
</button>
<div className="w-full max-w-md">
<div className="mb-6 flex flex-col items-center text-center">
<Logo size={56} />
<h1 className="text-fg mt-4 text-2xl font-semibold tracking-tight">
Ward Admin
</h1>
<p className="text-fg-muted mt-1 text-sm">
Sign in with a server-issued token to manage your network.
</p>
</div>
<Card>
<CardHeader
title="Token sign-in"
description="Generated by your Minecraft account."
/>
<CardBody>
<form
className="space-y-4"
onSubmit={async (e) => {
e.preventDefault();
if (!token.trim() || submitting) return;
setSubmitting(true);
try {
await signIn(token);
setToken("");
} finally {
setSubmitting(false);
}
}}
>
<div>
<label className="text-fg-muted text-xs font-medium">
Bearer token
</label>
<Input
autoFocus
type="password"
value={token}
onChange={(e) => setToken(e.target.value)}
placeholder="paste your token…"
className="mt-1 font-mono"
spellCheck={false}
autoComplete="off"
/>
</div>
{error && (
<div className="text-red-600 dark:text-red-300 text-sm">
{error}
</div>
)}
<Button
type="submit"
variant="primary"
size="lg"
className="w-full"
disabled={!token.trim() || submitting || loading}
>
{loading || submitting ? "Verifying…" : "Sign in"}
</Button>
</form>
<div className="border-border bg-surface-2 mt-6 rounded-lg border p-3 text-sm">
<div className="text-fg-muted flex items-start gap-2">
<Terminal size={14} className="text-fg-subtle mt-0.5 shrink-0" />
<div>
<p>
Need a token? Run{" "}
<code className="bg-surface-3 text-fg rounded px-1.5 py-0.5 font-mono text-xs">
/ward web token
</code>{" "}
in-game on the server. Tokens last 24h. The permission{" "}
<code className="bg-surface-3 text-fg rounded px-1.5 py-0.5 font-mono text-xs">
ward.web
</code>{" "}
is required to sign in.
</p>
</div>
</div>
</div>
</CardBody>
</Card>
</div>
</div>
);
}