Employees can apply for leave and track balance, status, and history. Managers can view all requests and approve/reject them. Login with separate dashboards for employees and managers with the dummy json. Tables with filters (All, Pending, Approved, Rejected) for easy management.
157 lines
5.8 KiB
TypeScript
157 lines
5.8 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";
|
|
|
|
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");
|
|
}
|
|
}, [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>
|
|
);
|
|
} |