feat: redesign login page with split layout, feature highlights, and improved UX
CI / backend (push) Failing after 3s
CI / frontend (push) Failing after 27s

Add two-column layout with left panel showcasing product features including inventory sync, policy audit mode, and preview capabilities with animated decorative elements, redesign right panel with larger form inputs and focus states, remove hardcoded demo credentials to require manual entry, add autocomplete attributes for username and password fields, implement disabled state for submit button when fields are empty, add animations
This commit is contained in:
2026-07-09 13:16:50 +02:00
parent 0aaaa12ba1
commit 701835e9f3
+81 -24
View File
@@ -1,14 +1,14 @@
import { FormEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Moon, ShieldCheck, Sun } from "lucide-react";
import { Activity, LockKeyhole, Moon, Network, ShieldCheck, Sun } from "lucide-react";
import { login } from "../api/client";
import { useTheme } from "../stores/theme";
export function Login() {
const navigate = useNavigate();
const [email, setEmail] = useState("admin@nexafabric.local");
const [password, setPassword] = useState("ChangeMe_UseEnvInstead");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const { dark, toggle } = useTheme();
@@ -24,36 +24,93 @@ export function Login() {
}
return (
<div className="grid min-h-screen place-items-center bg-canvas px-4">
<div className="min-h-screen bg-canvas text-slate-900 dark:text-slate-100">
<button
className="fixed right-4 top-4 rounded-md border border-border bg-panel p-2 text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800"
className="fixed right-4 top-4 z-10 rounded-md border border-border bg-panel p-2 text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-slate-800"
onClick={toggle}
type="button"
aria-label="Toggle theme"
>
{dark ? <Sun size={18} /> : <Moon size={18} />}
</button>
<form onSubmit={submit} className="w-full max-w-sm rounded-md border border-border bg-panel p-6 shadow-sm">
<div className="mb-6 flex items-center gap-3">
<div className="rounded-md bg-accent p-2 text-white">
<ShieldCheck size={22} />
<div className="grid min-h-screen lg:grid-cols-[1.1fr_0.9fr]">
<section className="relative hidden overflow-hidden border-r border-border bg-panel lg:block">
<div className="absolute inset-0 opacity-40">
<div className="absolute left-16 top-24 h-48 w-48 rounded-full border border-accent/40" />
<div className="absolute right-20 top-48 h-72 w-72 rounded-full border border-slate-400/30" />
<div className="absolute bottom-24 left-1/3 h-56 w-56 rounded-full border border-accent/30" />
</div>
<div>
<h1 className="text-xl font-semibold">NexaFabric</h1>
<p className="text-sm text-slate-500">Sign in to the control plane</p>
<div className="relative flex h-full flex-col justify-between p-12">
<div className="flex items-center gap-3">
<div className="rounded-md bg-accent p-3 text-white">
<ShieldCheck size={26} />
</div>
<div>
<div className="text-2xl font-semibold">NexaFabric</div>
<div className="text-sm text-slate-500 dark:text-slate-400">Network and security control plane</div>
</div>
</div>
<div className="max-w-xl animate-[slideUp_520ms_ease-out]">
<h1 className="mb-4 text-5xl font-semibold leading-tight tracking-normal">
Operate Proxmox networks with intent.
</h1>
<p className="text-base leading-7 text-slate-500 dark:text-slate-400">
Centralize inventory, IPAM, segmentation policy, firewall preview, and audit history without patching Proxmox.
</p>
<div className="mt-8 grid gap-3">
{[
["Inventory sync", Network],
["Policy audit mode", Activity],
["Preview before apply", LockKeyhole],
].map(([label, Icon]) => {
const LucideIcon = Icon as typeof Network;
return (
<div key={String(label)} className="flex items-center gap-3 rounded-md border border-border bg-canvas/70 p-3 text-sm">
<LucideIcon size={18} className="text-accent" />
<span>{String(label)}</span>
</div>
);
})}
</div>
</div>
<div className="text-xs text-slate-500 dark:text-slate-400">Read first. Simulate second. Apply only after confirmation.</div>
</div>
</div>
<label className="mb-4 block text-sm">
Email
<input className="mt-1 w-full rounded-md border border-border bg-transparent px-3 py-2" value={email} onChange={(event) => setEmail(event.target.value)} />
</label>
<label className="mb-4 block text-sm">
Password
<input className="mt-1 w-full rounded-md border border-border bg-transparent px-3 py-2" type="password" value={password} onChange={(event) => setPassword(event.target.value)} />
</label>
{error ? <div className="mb-4 rounded-md border border-danger px-3 py-2 text-sm text-danger">{error}</div> : null}
<button className="h-10 w-full rounded-md bg-accent text-sm font-medium text-white">Sign In</button>
</form>
</section>
<section className="grid place-items-center px-4 py-12">
<form onSubmit={submit} className="w-full max-w-md animate-[fadeIn_420ms_ease-out] rounded-md border border-border bg-panel p-7 shadow-sm">
<div className="mb-7">
<div className="mb-4 inline-flex rounded-md bg-accent p-3 text-white lg:hidden">
<ShieldCheck size={24} />
</div>
<h1 className="text-2xl font-semibold">Sign in</h1>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">Use the administrator account created during setup.</p>
</div>
<label className="mb-4 block text-sm">
Email
<input
className="mt-1 h-11 w-full rounded-md border border-border bg-transparent px-3 outline-none focus:border-accent"
value={email}
autoComplete="username"
onChange={(event) => setEmail(event.target.value)}
/>
</label>
<label className="mb-5 block text-sm">
Password
<input
className="mt-1 h-11 w-full rounded-md border border-border bg-transparent px-3 outline-none focus:border-accent"
type="password"
value={password}
autoComplete="current-password"
onChange={(event) => setPassword(event.target.value)}
/>
</label>
{error ? <div className="mb-4 rounded-md border border-danger px-3 py-2 text-sm text-danger">{error}</div> : null}
<button className="h-11 w-full rounded-md bg-accent text-sm font-medium text-white disabled:opacity-50" disabled={!email || !password}>
Sign In
</button>
</form>
</section>
</div>
</div>
);
}