LMS-BGN/src/app/login/page.tsx

300 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { Eye, EyeOff, Mail, Lock, AlertCircle, CheckCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { cn } from '@/utils/cn';
import { useAuth } from '@/contexts/AuthContext';
interface LoginForm {
email: string;
password: string;
}
interface FormErrors {
email?: string;
password?: string;
general?: string;
}
export default function LoginPage() {
const router = useRouter();
const { login, isLoading: authLoading } = useAuth();
const [form, setForm] = useState<LoginForm>({
email: '',
password: ''
});
const [errors, setErrors] = useState<FormErrors>({});
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
// Email validation
if (!form.email) {
newErrors.email = 'Email wajib diisi';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
newErrors.email = 'Format email tidak valid';
}
// Password validation
if (!form.password) {
newErrors.password = 'Password wajib diisi';
} else if (form.password.length < 6) {
newErrors.password = 'Password minimal 6 karakter';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleInputChange = (field: keyof LoginForm, value: string) => {
setForm(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }));
}
};
const handleQuickLogin = (email: string, password: string) => {
setForm({ email, password });
setErrors({});
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setIsLoading(true);
setErrors({});
try {
const success = await login(form.email, form.password);
if (!success) {
setErrors({ general: 'Email atau password salah' });
}
// If successful, AuthContext will handle the redirect
} catch (error) {
setErrors({ general: 'Terjadi kesalahan. Silakan coba lagi.' });
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50 flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo and Title */}
<div className="text-center mb-8">
<div className="mx-auto mb-4">
<Image
src="https://upload.wikimedia.org/wikipedia/id/thumb/2/29/Logo_Badan_Gizi_Nasional.svg/480px-Logo_Badan_Gizi_Nasional.svg.png"
alt="Logo Badan Gizi Nasional"
width={64}
height={64}
className="rounded-lg"
priority
/>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Selamat Datang</h1>
<p className="text-gray-600">Masuk ke akun Learning Management System Anda</p>
</div>
<Card className="shadow-xl border-0">
<CardHeader className="space-y-1 pb-6">
<CardTitle className="text-2xl font-semibold text-center">Masuk</CardTitle>
<CardDescription className="text-center">
Masukkan email dan password untuk mengakses dashboard
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
{/* General Error */}
{errors.general && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{errors.general}</AlertDescription>
</Alert>
)}
{/* Email Field */}
<div className="space-y-2">
<label htmlFor="email" className="text-sm font-medium text-gray-700">
Email
</label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
id="email"
type="email"
placeholder="nama@email.com"
value={form.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className={cn(
"pl-10",
errors.email && "border-red-500 focus:border-red-500"
)}
disabled={isLoading}
/>
</div>
{errors.email && (
<p className="text-sm text-red-600 flex items-center gap-1">
<AlertCircle className="h-3 w-3" />
{errors.email}
</p>
)}
</div>
{/* Password Field */}
<div className="space-y-2">
<label htmlFor="password" className="text-sm font-medium text-gray-700">
Password
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
id="password"
type={showPassword ? 'text' : 'password'}
placeholder="Masukkan password"
value={form.password}
onChange={(e) => handleInputChange('password', e.target.value)}
className={cn(
"pl-10 pr-10",
errors.password && "border-red-500 focus:border-red-500"
)}
disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
disabled={isLoading}
>
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
{errors.password && (
<p className="text-sm text-red-600 flex items-center gap-1">
<AlertCircle className="h-3 w-3" />
{errors.password}
</p>
)}
</div>
{/* Remember Me & Forgot Password */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<input
id="remember"
type="checkbox"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
disabled={isLoading}
/>
<label htmlFor="remember" className="text-sm text-gray-600">
Ingat saya
</label>
</div>
<Link
href="/forgot-password"
className="text-sm text-blue-600 hover:text-blue-700 hover:underline"
>
Lupa password?
</Link>
</div>
{/* Submit Button */}
<Button
type="submit"
className="w-full"
disabled={isLoading}
>
{isLoading ? (
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
Memproses...
</div>
) : (
'Masuk'
)}
</Button>
</form>
{/* Demo Credentials */}
<div className="mt-6 p-4 bg-blue-50 rounded-lg border border-blue-200">
<div className="flex items-start gap-2 mb-3">
<CheckCircle className="h-4 w-4 text-blue-600 mt-0.5" />
<div className="text-sm">
<p className="font-medium text-blue-800 mb-2">Demo Credentials:</p>
{/* Admin Credentials */}
<div className="mb-3 p-3 bg-white rounded border border-blue-100">
<div className="flex items-center justify-between mb-2">
<p className="font-medium text-blue-800">👨💼 Admin:</p>
<button
type="button"
onClick={() => handleQuickLogin('admin@lms.com', 'admin123')}
className="text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 rounded transition-colors"
disabled={isLoading}
>
Quick Fill
</button>
</div>
<p className="text-blue-700 text-xs">Email: admin@lms.com</p>
<p className="text-blue-700 text-xs">Password: admin123</p>
</div>
{/* Student Credentials */}
<div className="p-3 bg-white rounded border border-blue-100">
<div className="flex items-center justify-between mb-2">
<p className="font-medium text-blue-800">👨🎓 Student:</p>
<button
type="button"
onClick={() => handleQuickLogin('student@lms.com', 'student123')}
className="text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 rounded transition-colors"
disabled={isLoading}
>
Quick Fill
</button>
</div>
<p className="text-blue-700 text-xs">Email: student@lms.com</p>
<p className="text-blue-700 text-xs">Password: student123</p>
</div>
</div>
</div>
</div>
{/* Register Link */}
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
Belum punya akun?{' '}
<Link
href="/register"
className="text-blue-600 hover:text-blue-700 font-medium hover:underline"
>
Daftar sekarang
</Link>
</p>
</div>
</CardContent>
</Card>
{/* Footer */}
<div className="mt-8 text-center text-sm text-gray-500">
<p>© 2024 Learning Management System. All rights reserved.</p>
</div>
</div>
</div>
);
}