pmtool / src /pages /login.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
11.1 kB
import React, { useState, useEffect } from "react";
import { useNavigate, Link, useLocation } from "react-router-dom";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import Logo from "@/components/ui/logo";
import { toast } from "@/lib/custom-toast";
import { authService, userSessionService } from "@/lib/api";
import { useAuth } from "@/lib/auth-context";
import { Download, Smartphone } from "lucide-react";
import CheckInBlockerDialog from "@/components/CheckInBlockerDialog";
import { attendanceApi } from "@/services/attendanceApi";
// Form schema and validation
const formSchema = z.object({
email: z.string().email({
message: "Please enter a valid email address",
}),
password: z.string().min(1, {
message: "Password is required",
}),
});
const Login = () => {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || "/";
const [isLoading, setIsLoading] = useState(false);
const [apiError, setApiError] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
const [showCheckInDialog, setShowCheckInDialog] = useState(false);
const [checkInLoading, setCheckInLoading] = useState(false);
const [pendingUserData, setPendingUserData] = useState<any>(null);
const { login } = useAuth();
// Redirect to dashboard if already logged in
useEffect(() => {
// Removing the auto-redirect so users can log in again if needed
}, []);
// Form setup with validation
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: "",
password: ""
},
});
const handleCheckIn = async () => {
if (!pendingUserData) return;
try {
setCheckInLoading(true);
await attendanceApi.checkIn(pendingUserData.employeeId);
toast.success("Checked in successfully!");
setShowCheckInDialog(false);
// Small delay to ensure backend has processed the check-in
await new Promise(resolve => setTimeout(resolve, 500));
login(pendingUserData);
} catch (error: any) {
toast.error(error?.response?.data || error?.message || "Failed to check in");
console.error('Check-in error:', error);
} finally {
setCheckInLoading(false);
}
};
const onSubmit = async (values: z.infer<typeof formSchema>) => {
try {
setIsLoading(true);
setApiError(null);
// Call the real API
const response = await authService.login({
email: values.email,
password: values.password
});
// Handle successful login
if (response && response.userId) {
// Log token details for debugging
console.log('Login successful - token received:', !!response.token);
if (response.token) {
console.log('Token starts with:', response.token.substring(0, 20) + '...');
} else {
console.error('WARNING: No token in the login response!');
}
// Show success message
toast.success(`Welcome back, ${response.firstName}!`);
// Check if user is a client - clients don't need to check in
if (response.roleName?.toLowerCase() === "client") {
login(response);
return;
}
// Check if user has checked in today
try {
const todayAttendance = await attendanceApi.getToday(response.employeeId);
if (!todayAttendance || !todayAttendance.inTime) {
// User hasn't checked in - show blocker dialog
setPendingUserData(response);
setShowCheckInDialog(true);
setIsLoading(false);
} else {
// User already checked in - proceed with login
login(response);
}
} catch (error) {
console.error('Error checking attendance:', error);
// If there's an error checking attendance, proceed with login anyway
login(response);
}
} else {
// If no userId in response
throw new Error('Invalid user credentials');
}
} catch (error: any) {
setIsLoading(false);
// Set error message
const errorMessage = error.message || "Login failed. Please check your credentials.";
setApiError(errorMessage);
toast.error(errorMessage);
console.error("Login error:", error);
} finally {
setIsLoading(false);
}
};
return (
<div className="h-full flex flex-col items-center justify-center bg-gradient-to-b from-orange-100 via-white to-white p-4 px-2 sm:px-4">
<div className="w-full max-w-md">
<div className="mb-6 sm:mb-10 text-center">
<div className="flex justify-center mb-4 sm:mb-8">
<div className="px-4 py-4 sm:px-8 sm:py-6 rounded-2xl bg-white shadow-md hover:shadow-lg transition-all duration-300 animate-fadeIn">
<Logo
size="md"
className="mb-0.5"
/>
</div>
</div>
<h1 className="text-2xl sm:text-3xl font-bold tracking-tight mb-2 animate-fadeIn animation-delay-200">Welcome back</h1>
<p className="text-sm sm:text-base text-muted-foreground animate-fadeIn animation-delay-300">
Log in to your account to continue
</p>
</div>
<Card className="shadow-lg border-none overflow-hidden">
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-orange-400 to-coral-600"></div>
<CardHeader className="pb-4">
<CardTitle className="text-xl">Sign in</CardTitle>
<CardDescription>
Enter your credentials below
</CardDescription>
</CardHeader>
<CardContent>
{apiError && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-2 rounded-md mb-4 text-sm">
{apiError}
</div>
)}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm">Email</FormLabel>
<FormControl>
<Input
placeholder="info@synthesys.co.in"
autoComplete="email"
inputMode="email"
{...field}
disabled={isLoading}
className="h-9"
/>
</FormControl>
<FormMessage className="text-xs" />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm">Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Password"
autoComplete="current-password"
{...field}
disabled={isLoading}
className="h-9"
/>
</FormControl>
<FormMessage className="text-xs" />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full mt-2"
disabled={isLoading}
>
{isLoading ? "Signing in..." : "Sign in"}
</Button>
</form>
</Form>
</CardContent>
<CardFooter className="flex flex-col items-center py-3 gap-2">
<Button variant="link" className="text-xs text-muted-foreground p-0 h-auto" asChild>
<Link to="/forgot-password">Forgot your password?</Link>
</Button>
</CardFooter>
</Card>
<div className="mt-4 sm:mt-6 text-center text-xs sm:text-sm text-muted-foreground">
<span>Need an account? </span>
<Button
variant="link"
className="p-0 h-auto text-xs sm:text-sm"
onClick={() => toast.info("Please contact administrator for registration.")}
>
Sign up
</Button>
</div>
{/* Mobile App Download Section */}
<div className="mt-8 pt-6 border-t border-gray-200 animate-fadeIn animation-delay-500">
<Card className="bg-gradient-to-r from-primary/5 to-primary/10 shadow-sm hover:shadow-md transition-all duration-300">
<CardContent className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row items-center gap-4">
<div className="bg-primary/10 p-3 rounded-full">
<Smartphone className="h-8 w-8 text-primary/80" />
</div>
<div className="flex-1 text-center sm:text-left">
<h3 className="text-sm sm:text-base font-medium">PM-Tool Mobile App</h3>
<p className="text-xs sm:text-sm text-muted-foreground mt-1">
Manage your projects on the go with our Android app
</p>
</div>
<Button
variant="outline"
size="sm"
className="gap-2 border-primary/20 hover:bg-primary/5"
onClick={() => navigate('/download-app')}
>
<Download className="h-4 w-4" />
<span>Download</span>
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
{/* Check-In Blocker Dialog */}
<CheckInBlockerDialog
isOpen={showCheckInDialog}
onCheckIn={handleCheckIn}
isLoading={checkInLoading}
userName={pendingUserData?.firstName || 'User'}
/>
</div>
);
};
export default Login;