202 lines
7.1 KiB
TypeScript
Raw Normal View History

'use client';
import { useState, FormEvent } from 'react';
import { useRouter } from 'next/navigation';
import { Lock, User, Loader2 } from 'lucide-react';
import { motion } from 'framer-motion';
import Image from 'next/image';
export default function AdminLogin() {
const router = useRouter();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const response = await fetch('/api/admin/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
const data = await response.json();
if (!response.ok) {
setError(data.error || 'Login failed');
setIsLoading(false);
return;
}
// Successful login - redirect to dashboard
router.push('/admin/dashboard');
} catch (err) {
setError('Network error. Please try again.');
setIsLoading(false);
}
};
return (
<div className="min-h-screen w-full flex items-center justify-center p-4 relative overflow-hidden">
{/* Monaco Background Image */}
<div
className="absolute inset-0 w-full h-full bg-cover bg-center"
style={{
backgroundImage: 'url(/monaco_high_res.jpg)',
filter: 'brightness(0.6) contrast(1.1)'
}}
/>
{/* Overlay gradient for better readability */}
<div className="absolute inset-0 bg-gradient-to-b from-black/50 via-black/30 to-black/50" />
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="relative w-full max-w-md z-10"
>
{/* Logo and Title */}
<div className="text-center mb-8">
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ delay: 0.2, duration: 0.5 }}
className="flex justify-center mb-4"
>
<div className="w-20 h-20 bg-white rounded-full flex items-center justify-center shadow-lg p-2">
<Image
src="/puffinOffset.png"
alt="Puffin Offset Logo"
width={64}
height={64}
className="object-contain"
/>
</div>
</motion.div>
<motion.h1
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.3, duration: 0.5 }}
className="text-3xl font-bold mb-2 text-off-white"
>
Admin Portal
</motion.h1>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4, duration: 0.5 }}
className="font-medium text-off-white/80"
>
Puffin Offset Management
</motion.p>
</div>
{/* Login Card */}
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.5, duration: 0.5 }}
className="bg-white border border-light-gray-border rounded-xl p-8 shadow-2xl"
>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Error Message */}
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="bg-red-500/20 border border-red-500/50 rounded-lg p-3 text-red-200 text-sm"
>
{error}
</motion.div>
)}
{/* Username Field */}
<div>
<label htmlFor="username" className="block text-sm font-medium text-deep-sea-blue mb-2">
Username
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<User className="h-5 w-5 text-maritime-teal" />
</div>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full pl-10 pr-4 py-3 bg-white border border-light-gray-border rounded-lg text-deep-sea-blue placeholder-deep-sea-blue/40 focus:outline-none focus:ring-2 focus:ring-maritime-teal focus:border-maritime-teal transition-all"
placeholder="Enter your username"
required
disabled={isLoading}
/>
</div>
</div>
{/* Password Field */}
<div>
<label htmlFor="password" className="block text-sm font-medium text-deep-sea-blue mb-2">
Password
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-maritime-teal" />
</div>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-10 pr-4 py-3 bg-white border border-light-gray-border rounded-lg text-deep-sea-blue placeholder-deep-sea-blue/40 focus:outline-none focus:ring-2 focus:ring-maritime-teal focus:border-maritime-teal transition-all"
placeholder="Enter your password"
required
disabled={isLoading}
/>
</div>
</div>
{/* Submit Button */}
<motion.button
type="submit"
disabled={isLoading}
whileHover={{ scale: isLoading ? 1 : 1.02 }}
whileTap={{ scale: isLoading ? 1 : 0.98 }}
className={`w-full py-3 px-4 rounded-lg font-semibold text-white shadow-lg transition-all ${
isLoading
? 'bg-maritime-teal/50 cursor-not-allowed'
: 'bg-gradient-to-r from-maritime-teal to-sea-green hover:from-sea-green hover:to-maritime-teal'
}`}
>
{isLoading ? (
<span className="flex items-center justify-center">
<Loader2 className="animate-spin mr-2" size={20} />
Signing in...
</span>
) : (
'Sign In'
)}
</motion.button>
</form>
</motion.div>
{/* Footer */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.6, duration: 0.5 }}
className="text-center mt-6 text-sm font-medium text-off-white/70"
>
© 2024 Puffin Offset. Secure admin access.
</motion.p>
</motion.div>
</div>
);
}