profile + authentication
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user