Redesign login screen with the app's ledgr theme

Navy brand panel (ledger-grid texture, purple glow, product pitch) +
sign-in form on the canvas: input icons, show/hide password, inline
error, entrance animation (motion-safe). Mobile collapses to the form
with a compact brand header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
main
Talha Ahmed 2026-08-19 19:12:01 +05:00
parent 11bceedaca
commit e984cd09ed
1 changed files with 141 additions and 29 deletions

View File

@ -1,12 +1,18 @@
import { FormEvent, useState } from "react";
import { Landmark, LogIn } from "lucide-react";
import { BadgeCheck, Eye, EyeOff, Landmark, Lock, LogIn, ShieldCheck, User } from "lucide-react";
import { Spinner } from "../components/ui";
import { useAuth } from "../auth";
/**
* Sign-in screen: navy brand panel (desktop) + form on the app canvas.
* Uses the app's "ledgr" tokens periwinkle primary, navy, lavender canvas so the
* login feels like the first screen of the dashboard, not a bolt-on.
*/
export default function Login() {
const { login } = useAuth();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [showPw, setShowPw] = useState(false);
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
@ -24,41 +30,147 @@ export default function Login() {
};
return (
<div className="h-full bg-canvas flex items-center justify-center p-4">
<div className="card w-full max-w-sm p-6 shadow-pop">
<div className="flex items-center gap-2.5 mb-5">
<span className="inline-flex items-center justify-center w-9 h-9 rounded-xl bg-primary text-white">
<Landmark size={18} />
<div className="min-h-full flex bg-canvas">
{/* ---------------- brand panel (desktop) ---------------- */}
<aside
className="hidden lg:flex flex-col justify-between w-[44%] max-w-xl p-12 text-white relative overflow-hidden bg-navy"
style={{
backgroundImage: `
radial-gradient(52rem 30rem at -10% -20%, rgba(109,93,232,0.45), transparent 60%),
radial-gradient(40rem 26rem at 110% 115%, rgba(201,195,245,0.22), transparent 60%),
linear-gradient(rgba(255,255,255,0.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.045) 1px, transparent 1px)`,
backgroundSize: "auto, auto, 100% 3.25rem, 3.25rem 100%",
}}
>
<div className="flex items-center gap-3">
<span className="inline-flex items-center justify-center w-11 h-11 rounded-2xl bg-primary text-white shadow-pop">
<Landmark size={22} />
</span>
<div>
<div className="text-sm font-semibold leading-tight text-ink">Amazon A/R Aging</div>
<div className="text-[11px] text-muted">Month-End Closing · sign in</div>
<div className="text-base font-semibold leading-tight">Amazon A/R Aging</div>
<div className="text-xs text-lavender">Month-End Closing · Utopia Brands Finance</div>
</div>
</div>
<form onSubmit={submit} className="space-y-3">
<div>
<label className="label" htmlFor="login-user">Username</label>
<input id="login-user" className="input" autoComplete="username" autoFocus
value={username} onChange={(e) => setUsername(e.target.value)} />
</div>
<div>
<label className="label" htmlFor="login-pw">Password</label>
<input id="login-pw" className="input" type="password" autoComplete="current-password"
value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
{error && <p className="text-sm text-bad">{error}</p>}
<button className="btn-primary w-full justify-center" type="submit"
disabled={busy || !username.trim() || !password}>
{busy ? <Spinner /> : <LogIn size={16} />} Sign in
</button>
</form>
<div className="max-w-md">
<h1 className="text-3xl xl:text-4xl font-semibold leading-tight [text-wrap:balance]">
Every month closed, checked, and accounted for.
</h1>
<p className="mt-4 text-sm leading-relaxed text-lavender-soft/90">
Upload the month's Amazon transaction files and get the full receivable
position settlements, aging, journal entry, and the audit trail behind
every figure.
</p>
<p className="text-[11px] text-muted mt-4">
Accounts are created by the administrator on the server
(<span className="num">manage.py add-user</span>) there is no self-signup.
<ul className="mt-8 space-y-3 text-sm">
{[
{ Icon: ShieldCheck, text: "Processed on the company server — financial data never leaves" },
{ Icon: BadgeCheck, text: "Six month-end controls gate every published figure" },
].map(({ Icon, text }) => (
<li key={text} className="flex items-start gap-3">
<span className="mt-0.5 inline-flex items-center justify-center w-6 h-6 rounded-lg bg-white/10 text-lavender shrink-0">
<Icon size={14} />
</span>
<span className="text-lavender-soft/90 leading-snug">{text}</span>
</li>
))}
</ul>
</div>
<p className="text-[11px] text-lavender/60">
Amazon-only · 13 marketplaces · reconciles to the cent
</p>
</div>
</aside>
{/* ---------------- sign-in form ---------------- */}
<main
className="flex-1 flex items-center justify-center p-6 sm:p-10"
style={{
backgroundImage:
"radial-gradient(38rem 24rem at 85% -10%, rgba(109,93,232,0.10), transparent 65%)",
}}
>
<div className="w-full max-w-sm motion-safe:animate-[login-in_.45s_ease-out]">
{/* compact brand header for mobile, where the panel is hidden */}
<div className="lg:hidden flex items-center gap-2.5 mb-8">
<span className="inline-flex items-center justify-center w-9 h-9 rounded-xl bg-primary text-white shadow-card">
<Landmark size={18} />
</span>
<div>
<div className="text-sm font-semibold leading-tight text-ink">Amazon A/R Aging</div>
<div className="text-[11px] text-muted">Month-End Closing</div>
</div>
</div>
<h2 className="text-2xl font-semibold text-ink">Welcome back</h2>
<p className="mt-1 text-sm text-subink">Sign in to continue to this month's closing.</p>
<form onSubmit={submit} className="mt-8 space-y-4">
<div>
<label className="label" htmlFor="login-user">Username</label>
<div className="relative">
<User size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-faint pointer-events-none" />
<input
id="login-user"
className="input pl-10 py-2.5"
placeholder="you@utopiabrands.com"
autoComplete="username"
autoFocus
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
</div>
<div>
<label className="label" htmlFor="login-pw">Password</label>
<div className="relative">
<Lock size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-faint pointer-events-none" />
<input
id="login-pw"
className="input pl-10 pr-11 py-2.5"
type={showPw ? "text" : "password"}
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
type="button"
aria-label={showPw ? "Hide password" : "Show password"}
className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-lg text-faint hover:text-subink hover:bg-neutralbg transition-colors"
onClick={() => setShowPw((v) => !v)}
tabIndex={-1}
>
{showPw ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
{error && (
<div className="rounded-xl border border-bad/30 bg-badbg/60 px-3.5 py-2.5 text-sm text-bad">
{error}
</div>
)}
<button
className="btn-primary w-full justify-center py-2.5 text-[15px]"
type="submit"
disabled={busy || !username.trim() || !password}
>
{busy ? <Spinner /> : <LogIn size={16} />} Sign in
</button>
</form>
<p className="mt-8 text-xs text-muted leading-relaxed">
No account or forgot your password? Ask the administrator accounts are
created and reset on the server, never self-service.
</p>
</div>
</main>
{/* one-time entrance; browsers honouring reduced motion skip it via motion-safe */}
<style>{`@keyframes login-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }`}</style>
</div>
);
}