Ensured consistent day calculation using stored days field and removed duplicate daysBetween logic. Implemented working-day-only calculation, excluding weekends (Saturday & Sunday), and blocked invalid date selections. Corrected handling of Unpaid leave so it does not affect leave balance. Added routing and integrated Sonner notifications for login, manager actions, and employee leave submission.
146 lines
5.2 KiB
TypeScript
146 lines
5.2 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 { CalendarDays, Loader2 } from "lucide-react";
|
|
import { ROUTES } from "@/lib/routes";
|
|
import { toast } from "sonner";
|
|
|
|
export default function LoginPage() {
|
|
const { login, user, isLoading } = useAuth();
|
|
const router = useRouter();
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isLoading && user) {
|
|
router.replace(user.role === "manager" ? ROUTES.manager : ROUTES.employee);
|
|
}
|
|
}, [user, isLoading, router]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!username || !password) {
|
|
toast.error("Please enter username and password");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
const result = await login(username, password);
|
|
setLoading(false);
|
|
if (!result.ok) {
|
|
toast.error(result.error ?? "Login failed");
|
|
}
|
|
};
|
|
|
|
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>
|
|
|
|
<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>
|
|
);
|
|
} |