159 lines
5.9 KiB
TypeScript
159 lines
5.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useAuth } from "@/context/AuthContext";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
import { CalendarDays, AlertCircle, Loader2 } from "lucide-react";
|
|
import { ROUTES } from "@/lib/routes";
|
|
|
|
export default function LoginPage() {
|
|
const { login, user, isLoading } = useAuth();
|
|
const router = useRouter();
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [error, setError] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isLoading && user) {
|
|
// router.replace(user.role === "manager" ? "/manager" : "/employee");
|
|
router.replace(user.role === "manager" ? ROUTES.manager : ROUTES.employee);
|
|
}
|
|
}, [user, isLoading, router]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!username || !password) {
|
|
setError("Please enter username and password");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setError("");
|
|
const result = await login(username, password);
|
|
setLoading(false);
|
|
if (!result.ok) {
|
|
setError(result.error ?? "Login failed");
|
|
} else if (result.ok) {
|
|
// redirect handled by useEffect
|
|
}
|
|
};
|
|
|
|
if (isLoading) return null;
|
|
|
|
return (
|
|
<main className="min-h-screen flex items-center justify-center p-4" style={{ background: "var(--surface-base)" }}>
|
|
{/* Decorative blobs */}
|
|
<div
|
|
className="fixed top-0 left-0 w-96 h-96 rounded-full pointer-events-none"
|
|
style={{
|
|
background: "radial-gradient(circle, var(--brand-200) 0%, transparent 70%)",
|
|
opacity: 0.4,
|
|
transform: "translate(-30%, -30%)",
|
|
}}
|
|
/>
|
|
<div
|
|
className="fixed bottom-0 right-0 w-80 h-80 rounded-full pointer-events-none"
|
|
style={{
|
|
background: "radial-gradient(circle, var(--brand-300) 0%, transparent 70%)",
|
|
opacity: 0.3,
|
|
transform: "translate(30%, 30%)",
|
|
}}
|
|
/>
|
|
|
|
<div className="w-full max-w-md relative z-10">
|
|
{/* Logo */}
|
|
<div className="flex flex-col items-center mb-8">
|
|
<div
|
|
className="w-14 h-14 rounded-2xl flex items-center justify-center mb-4"
|
|
style={{ background: "linear-gradient(135deg, var(--brand-600), var(--brand-400))", boxShadow: "0 8px 24px -4px rgb(99 102 241 / 0.35)" }}
|
|
>
|
|
<CalendarDays className="w-7 h-7" style={{ color: "var(--text-on-brand)" }} />
|
|
</div>
|
|
<h1 className="text-2xl font-bold tracking-tight" style={{ color: "var(--text-primary)" }}>
|
|
LeaveFlow
|
|
</h1>
|
|
|
|
</div>
|
|
|
|
<Card
|
|
style={{
|
|
border: "1px solid var(--surface-border)",
|
|
boxShadow: "var(--shadow-modal)",
|
|
background: "var(--surface-card)",
|
|
borderRadius: "var(--radius-lg)",
|
|
}}
|
|
>
|
|
<CardHeader className="pb-4">
|
|
<CardTitle className="text-lg" style={{ color: "var(--text-primary)" }}>
|
|
Sign in to your account
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<Label htmlFor="username" style={{ color: "var(--text-secondary)", fontSize: "0.8125rem", fontWeight: 500 }}>
|
|
Username
|
|
</Label>
|
|
<Input
|
|
id="username"
|
|
value={username}
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
placeholder="e.g. emp1"
|
|
autoComplete="username"
|
|
style={{ borderColor: "var(--surface-border)", borderRadius: "var(--radius-sm)" }}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label htmlFor="password" style={{ color: "var(--text-secondary)", fontSize: "0.8125rem", fontWeight: 500 }}>
|
|
Password
|
|
</Label>
|
|
<Input
|
|
id="password"
|
|
type="password"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="••••••••"
|
|
autoComplete="current-password"
|
|
style={{ borderColor: "var(--surface-border)", borderRadius: "var(--radius-sm)" }}
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<Alert style={{ borderColor: "var(--color-danger-border)", background: "var(--color-danger-bg)", borderRadius: "var(--radius-sm)" }}>
|
|
<AlertCircle className="h-4 w-4" style={{ color: "var(--color-danger)" }} />
|
|
<AlertDescription style={{ color: "var(--color-danger)" }}>{error}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
className="w-full font-semibold"
|
|
disabled={loading}
|
|
style={{
|
|
background: "linear-gradient(135deg, var(--brand-600), var(--brand-500))",
|
|
borderRadius: "var(--radius-sm)",
|
|
boxShadow: "0 4px 12px -2px rgb(99 102 241 / 0.3)",
|
|
border: "none",
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Signing in…
|
|
</>
|
|
) : (
|
|
"Sign In"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</main>
|
|
);
|
|
} |