profile + authentication

This commit is contained in:
2025-11-30 15:34:37 +00:00
parent 7d64b6799b
commit e15a78e8b8
24 changed files with 2439 additions and 576 deletions

View File

@@ -1,5 +1,12 @@
import localFont from "next/font/local";
"use client";
import { useState, useRef } from "react";
import Image from "next/image";
import localFont from "next/font/local";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import { Envelope, Lock, ShieldCheck, ArrowRight, CircleNotch, Eye, EyeSlash } from "@phosphor-icons/react";
import logo from '../../../public/logo.png';
const figtree = localFont({
src: [
@@ -7,14 +14,379 @@ const figtree = localFont({
path: '../../../public/Fonts/figtree/figtree.ttf',
},
],
})
});
// Reusing styles for consistency
const inputStyle = `${figtree.className} w-full p-4 border-2 border-black text-lg outline-none focus:bg-[#F7F7F7] transition-colors placeholder:text-gray-400 bg-white`;
const buttonStyle = `${figtree.className} w-full flex items-center justify-center gap-2 px-8 py-4 border-2 border-black bg-[#E7FE78] text-lg font-bold hover:bg-[#dcfc4e] transition-colors disabled:opacity-50 disabled:cursor-not-allowed shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-y-[4px] active:shadow-none transition-all`;
const labelStyle = `${figtree.className} text-sm font-bold uppercase tracking-wider mb-2 block`;
function Login() {
const [step, setStep] = useState<'credentials' | '2fa' | 'forgot-password' | 'recovery-sent'>('credentials');
const [isLoading, setIsLoading] = useState(false);
const [formData, setFormData] = useState({
email: '',
password: '',
otp: ''
});
const [error, setError] = useState('');
const [showPassword, setShowPassword] = useState(false);
const formRef = useRef<HTMLDivElement>(null);
const imageRef = useRef<HTMLDivElement>(null);
useGSAP(() => {
// Animate image side
gsap.fromTo(imageRef.current,
{ x: -50, opacity: 0 },
{ x: 0, opacity: 1, duration: 1, ease: "power3.out" }
);
// Animate form side
gsap.fromTo(formRef.current,
{ x: 50, opacity: 0 },
{ x: 0, opacity: 1, duration: 1, delay: 0.2, ease: "power3.out" }
);
}, []);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
// Simulate API call
setTimeout(() => {
setIsLoading(false);
if (formData.email === 'asabizanjo@gmail.com' && formData.password === 'Balouch@12') {
// Transition to 2FA
gsap.to(formRef.current, {
x: -20,
opacity: 0,
duration: 0.3,
onComplete: () => {
setStep('2fa');
gsap.fromTo(formRef.current,
{ x: 20, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.3 }
);
}
});
} else {
setError('Invalid email or password');
}
}, 1000);
};
const handleVerify = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
// Simulate API call
setTimeout(() => {
setIsLoading(false);
if (formData.otp === '0000') {
alert('Login Successful! (JWT would be stored here)');
// Here you would store the JWT
} else {
setError('Invalid OTP. Please try again (Hint: 0000)');
}
}, 1000);
};
const handleForgotPasswordSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
// Simulate API call
setTimeout(() => {
setIsLoading(false);
if (formData.email) {
gsap.to(formRef.current, {
x: -20,
opacity: 0,
duration: 0.3,
onComplete: () => {
setStep('recovery-sent');
gsap.fromTo(formRef.current,
{ x: 20, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.3 }
);
}
});
} else {
setError('Please enter your email address');
}
}, 1000);
};
const Login = () => {
return (
<div>
<h1 className={figtree.className}>Login</h1>
<div className="flex min-h-screen w-full bg-[#FAFAFA] overflow-hidden">
{/* Left Side - Visual/Logo */}
<div className="hidden lg:flex w-1/2 bg-[#E7FE78] border-r-4 border-black flex-col items-center justify-center p-12 relative">
<div className="absolute inset-0 opacity-10"
style={{
backgroundImage: `radial-gradient(circle at 2px 2px, black 1px, transparent 0)`,
backgroundSize: '32px 32px'
}}
/>
<div ref={imageRef} className="relative z-10 flex flex-col items-center text-center">
<div className="w-64 h-64 bg-white border-4 border-black rounded-full flex items-center justify-center mb-12 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
<Image
src={logo}
alt="Logo"
width={180}
height={180}
className="object-contain"
/>
</div>
<h1 className={`${figtree.className} text-5xl font-black mb-6 tracking-tight`}>
Welcome Back
</h1>
<p className={`${figtree.className} text-xl font-medium max-w-md leading-relaxed`}>
Your Home Away From Home
</p>
</div>
</div>
{/* Right Side - Form */}
<div className="w-full lg:w-1/2 flex flex-col items-center justify-center p-8 lg:p-24 bg-white relative">
<div ref={formRef} className="w-full max-w-md">
{step === 'credentials' && (
<>
<div className="mb-10">
<h2 className={`${figtree.className} text-4xl font-bold mb-3`}>Sign In</h2>
<p className={`${figtree.className} text-gray-500 text-lg`}>Enter your details to access your account</p>
</div>
<form onSubmit={handleLogin} className="flex flex-col gap-6">
<div>
<label className={labelStyle}>Email Address</label>
<div className="relative">
<input
type="email"
placeholder="john@example.com"
className={inputStyle}
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
<Envelope className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400" size={24} />
</div>
</div>
<div>
<div className="flex justify-between items-center">
<label className={labelStyle}>Password</label>
<button
type="button"
className={`${figtree.className} text-sm font-bold text-gray-500 hover:text-black transition-colors mb-2`}
onClick={() => {
setError('');
gsap.to(formRef.current, {
x: -20,
opacity: 0,
duration: 0.3,
onComplete: () => {
setStep('forgot-password');
gsap.fromTo(formRef.current,
{ x: 20, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.3 }
);
}
});
}}
>
Forgot Password?
</button>
</div>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
placeholder="••••••••"
className={inputStyle}
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
>
{showPassword ? <EyeSlash size={24} /> : <Eye size={24} />}
</button>
</div>
</div>
{error && (
<div className={`${figtree.className} p-4 bg-red-50 border-2 border-red-500 text-red-500 font-bold flex items-center gap-2`}>
<ShieldCheck size={20} />
{error}
</div>
)}
<button
type="submit"
disabled={isLoading}
className={buttonStyle}
>
{isLoading ? (
<CircleNotch className="animate-spin" size={24} />
) : (
<>Sign In <ArrowRight size={20} weight="bold" /></>
)}
</button>
<div className="text-center mt-4">
<p className={`${figtree.className} text-gray-500`}>
Don't have an account?{' '}
<a href="/signup" className="text-black font-bold hover:underline">
Sign Up
</a>
</p>
</div>
</form>
</>
)}
{step === '2fa' && (
<>
<div className="mb-10">
<h2 className={`${figtree.className} text-4xl font-bold mb-3`}>Two-Factor Auth</h2>
<p className={`${figtree.className} text-gray-500 text-lg`}>Enter the code sent to your device</p>
</div>
<form onSubmit={handleVerify} className="flex flex-col gap-6">
<div>
<label className={labelStyle}>Authentication Code</label>
<div className="relative">
<input
type="text"
placeholder="0000"
maxLength={4}
className={`${inputStyle} text-center text-3xl tracking-[1em] font-bold`}
value={formData.otp}
onChange={(e) => setFormData({ ...formData, otp: e.target.value })}
/>
</div>
<p className={`${figtree.className} text-xs text-gray-400 mt-2 text-center`}>Use '0000' for testing</p>
</div>
{error && (
<div className={`${figtree.className} p-4 bg-red-50 border-2 border-red-500 text-red-500 font-bold flex items-center gap-2`}>
<ShieldCheck size={20} />
{error}
</div>
)}
<button
type="submit"
disabled={isLoading}
className={buttonStyle}
>
{isLoading ? (
<CircleNotch className="animate-spin" size={24} />
) : (
<>Verify & Login <ShieldCheck size={20} weight="bold" /></>
)}
</button>
<button
type="button"
onClick={() => {
setStep('credentials');
setError('');
}}
className={`${figtree.className} text-gray-500 hover:text-black hover:underline text-center mt-4`}
>
Back to Login
</button>
</form>
</>
)}
{step === 'forgot-password' && (
<>
<div className="mb-10">
<h2 className={`${figtree.className} text-4xl font-bold mb-3`}>Reset Password</h2>
<p className={`${figtree.className} text-gray-500 text-lg`}>Enter your email to receive a recovery link</p>
</div>
<form onSubmit={handleForgotPasswordSubmit} className="flex flex-col gap-6">
<div>
<label className={labelStyle}>Email Address</label>
<div className="relative">
<input
type="email"
placeholder="john@example.com"
className={inputStyle}
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
<Envelope className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400" size={24} />
</div>
</div>
{error && (
<div className={`${figtree.className} p-4 bg-red-50 border-2 border-red-500 text-red-500 font-bold flex items-center gap-2`}>
<ShieldCheck size={20} />
{error}
</div>
)}
<button
type="submit"
disabled={isLoading}
className={buttonStyle}
>
{isLoading ? (
<CircleNotch className="animate-spin" size={24} />
) : (
<>Send Recovery Link <ArrowRight size={20} weight="bold" /></>
)}
</button>
<button
type="button"
onClick={() => {
setStep('credentials');
setError('');
}}
className={`${figtree.className} text-gray-500 hover:text-black hover:underline text-center mt-4`}
>
Back to Login
</button>
</form>
</>
)}
{step === 'recovery-sent' && (
<div className="text-center">
<div className="w-20 h-20 bg-[#E7FE78] rounded-full flex items-center justify-center mx-auto mb-6 border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
<Envelope size={40} weight="fill" />
</div>
<h2 className={`${figtree.className} text-3xl font-bold mb-4`}>Check Your Email</h2>
<p className={`${figtree.className} text-gray-500 text-lg mb-8`}>
We've sent a password recovery link to <br />
<span className="font-bold text-black">{formData.email}</span>
</p>
<button
type="button"
onClick={() => {
setStep('credentials');
setError('');
}}
className={buttonStyle}
>
Back to Login
</button>
</div>
)}
</div>
</div>
</div>
)
);
}
export default Login
export default Login;