Login: bare sign-in page at /login; Telegram bridge connection guide

This commit is contained in:
glass beadian 2026-10-13 09:49:57 +00:00
parent d313ce0bec
commit 22f9e12141
2 changed files with 157 additions and 5 deletions

View file

@ -1,9 +1,71 @@
import { redirect } from "next/navigation";
"use client";
import { useState, type FormEvent } from "react";
import Link from "next/link";
import { Button, Card, Input } from "../../components/ui/primitives";
import { api, errorMessage } from "../../lib/api-client";
import { useAuth } from "../../lib/auth-store";
/**
* /login — the sign-in door. The onboarding page holds both forms; this route
* deep-links into sign-in mode (register stays the default at /signup).
* /login — bare sign-in: username and password, nothing else. Account creation
* lives at /signup; collective joining happens with an invite link.
*/
export default function LoginRedirect(): React.ReactElement {
redirect("/onboarding?mode=signin");
export default function LoginPage(): React.ReactElement {
const { login } = useAuth();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
async function submit(e: FormEvent): Promise<void> {
e.preventDefault();
setBusy(true);
setError(null);
try {
await api.post<unknown>("/auth/login", { username: username.trim(), password }, { redirectOn401: false });
window.location.assign("/streams");
} catch (err) {
setError(errorMessage(err));
setBusy(false);
}
}
return (
<div className="flex min-h-full items-center justify-center bg-bg p-6">
<Card className="w-[420px] max-w-full p-6">
<div className="mb-5 text-center">
<h1 className="text-xl font-semibold tracking-tight">Sign in</h1>
<p className="mt-1 text-2xs text-text-faint">Tactical Media Broadcast Hub</p>
</div>
<form onSubmit={(e) => void submit(e)}>
<div className="space-y-2">
<Input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="username"
autoComplete="username"
autoFocus
/>
<Input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
autoComplete="current-password"
/>
</div>
{error !== null && <p className="mt-2 text-xs text-danger">{error}</p>}
<div className="mt-6 flex items-center justify-between">
<Link href="/signup" className="text-xs text-text-faint underline hover:text-text">
Create an account
</Link>
<Button type="submit" variant="primary" disabled={busy || username.trim() === "" || password === ""}>
{busy ? "Signing in…" : "Sign in"}
</Button>
</div>
</form>
</Card>
</div>
);
}