"use client"; import { useState, useEffect, useRef } from "react"; import { BeadsLogo } from "@/components/BeadsLogo"; /** * Password login page for dashboard access control. * * Shown when heartbeads is started with --password flag. * Matches the design language of the 404 page and loading screen: * ECG flatline + animated heartbeat logo, clean centered form. * * This is NOT the ATProto/Bluesky sign-in (that's in the navbar). * This gate controls who can view the dashboard and API. */ export default function LoginPage() { const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const [from, setFrom] = useState("/"); const inputRef = useRef(null); // Read ?from= query param for post-login redirect (avoid useSearchParams / Suspense) useEffect(() => { const params = new URLSearchParams(window.location.search); const fromParam = params.get("from"); // Validate: only allow relative paths (prevent open redirect to external URLs) if (fromParam && fromParam.startsWith("/") && !fromParam.startsWith("//")) { setFrom(fromParam); } // Auto-focus the password input inputRef.current?.focus(); }, []); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!password.trim()) return; setError(""); setLoading(true); try { const res = await fetch("/api/auth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ password: password.trim() }), }); if (res.ok) { window.location.href = from; } else { const data = await res.json(); setError(data.error || "Invalid password"); } } catch { setError("Connection error"); } finally { setLoading(false); } } return (
{/* ECG flatline + animated heartbeat logo */}
{/* Title */}

heartbeads

This dashboard is password-protected.

{/* Login form */}
{ setPassword(e.target.value); if (error) setError(""); }} placeholder="Enter password" className="w-full rounded-lg border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-4 py-2.5 text-sm text-zinc-900 dark:text-zinc-100 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-emerald-500/30 focus:border-emerald-500 transition-colors" autoComplete="current-password" disabled={loading} /> {/* Error message */} {error && (

{error}

)}
{/* Footer */}

password protection enabled

); }