Files
ContainerFlow/src/client/components/LoginScreen.tsx
T
2026-04-30 04:05:03 +00:00

141 lines
5.4 KiB
TypeScript

import { useState } from "react";
import { Lock, Eye, EyeOff, Terminal } from "lucide-react";
interface LoginScreenProps {
onAuth: (token: string) => void;
}
export function LoginScreen({ onAuth }: LoginScreenProps) {
const [token, setToken] = useState("");
const [error, setError] = useState("");
const [showToken, setShowToken] = useState(false);
const [connecting, setConnecting] = useState(false);
const [connected, setConnected] = useState(false);
const [logLines, setLogLines] = useState<string[]>([]);
const hackerLog = (lines: string[], onDone: () => void) => {
lines.forEach((line, i) => {
setTimeout(() => {
setLogLines((prev) => [...prev, line]);
if (i === lines.length - 1) setTimeout(onDone, 400);
}, i * 180);
});
};
const submit = async (e: React.FormEvent) => {
e.preventDefault();
if (connecting) return;
setConnecting(true);
setError("");
setLogLines([]);
hackerLog([
"$ flowteon connect --auth",
"> Establishing secure connection...",
"> Validating AUTH_TOKEN...",
], async () => {
try {
const res = await fetch("/api/health", {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
hackerLog([
"> Token accepted",
"> Loading Docker socket...",
"> Connection established!",
], () => {
localStorage.setItem("df:token", token);
setConnected(true);
setTimeout(() => onAuth(token), 800);
});
} else {
hackerLog(["> ERROR: Invalid token", "> Connection refused"], () => {
setError("Token invalido");
setConnecting(false);
});
}
} catch {
hackerLog(["> ERROR: Connection failed"], () => {
setError("No se pudo conectar");
setConnecting(false);
});
}
});
};
return (
<div className={`h-screen w-screen bg-slate-950 flex items-center justify-center transition-opacity duration-700 ${connected ? "opacity-0" : "opacity-100"}`}>
<div className="flex flex-col items-center gap-6 w-80">
{/* Logo + Title */}
<img
src="/alteonx-logo.png"
alt="Flowteon"
className={`w-16 h-16 transition-all duration-700 ${connected ? "scale-110" : ""}`}
style={{ filter: "brightness(0) saturate(100%) invert(45%) sepia(85%) saturate(2000%) hue-rotate(200deg) brightness(1.1)" }}
/>
<div className="text-center">
<h1 className="text-2xl font-bold text-white tracking-wide">Flowteon</h1>
<span className="text-xs text-cyan-400 tracking-widest uppercase">AlteonX</span>
</div>
{/* Form */}
<form onSubmit={submit} className={`flex flex-col gap-3 w-full transition-opacity duration-300 ${connecting ? "opacity-50 pointer-events-none" : ""}`}>
<div className="relative">
<Lock size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
<input
type={showToken ? "text" : "password"}
value={token}
onChange={(e) => { setToken(e.target.value); setError(""); }}
placeholder="AUTH_TOKEN"
className="w-full bg-slate-900 border border-slate-700 rounded-lg pl-9 pr-10 py-2.5 text-sm text-white font-mono placeholder:text-slate-600 focus:outline-none focus:border-cyan-500 transition-colors"
autoFocus
disabled={connecting}
/>
<button
type="button"
onClick={() => setShowToken((v) => !v)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300 transition-colors"
>
{showToken ? <EyeOff size={14} /> : <Eye size={14} />}
</button>
</div>
{error && <span className="text-red-400 text-xs font-mono">{error}</span>}
<button
type="submit"
disabled={connecting || !token}
className={`w-full flex items-center justify-center gap-2 text-sm font-medium py-2.5 rounded-lg transition-all duration-300 ${
connecting
? "bg-slate-800 text-slate-500 cursor-wait"
: "bg-cyan-600 hover:bg-cyan-500 text-white hover:shadow-lg hover:shadow-cyan-500/20"
}`}
>
<Terminal size={14} />
{connecting ? "Connecting..." : "Connect"}
</button>
</form>
{/* Terminal log */}
{logLines.length > 0 && (
<div className="w-full bg-slate-900/80 border border-slate-800 rounded-lg p-3 font-mono text-[11px] space-y-0.5 max-h-32 overflow-y-auto">
{logLines.map((line, i) => (
<div
key={i}
className={`${
line.includes("ERROR") ? "text-red-400" :
line.includes("accepted") || line.includes("established") ? "text-emerald-400" :
line.startsWith("$") ? "text-cyan-400" : "text-slate-400"
} animate-[fadeIn_0.15s_ease-out]`}
>
{line}
{i === logLines.length - 1 && !connected && (
<span className="inline-block w-1.5 h-3 bg-cyan-400 ml-1 animate-pulse" />
)}
</div>
))}
</div>
)}
</div>
</div>
);
}