Files
prayer-app/src/components/Login.tsx
T

414 lines
19 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useAuth } from '@/context/AuthContext';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [firstName, setFirstName] = useState('');
const [isSignup, setIsSignup] = useState(false);
const [rememberMe, setRememberMe] = useState(false);
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const [showForgotPassword, setShowForgotPassword] = useState(false);
const [forgotEmail, setForgotEmail] = useState('');
const [forgotLoading, setForgotLoading] = useState(false);
const [forgotMessage, setForgotMessage] = useState('');
const [showVerificationBanner, setShowVerificationBanner] = useState(false);
const [verificationSent, setVerificationSent] = useState(false);
const [needsVerification, setNeedsVerification] = useState(false);
const [resendEmail, setResendEmail] = useState('');
const [resendPassword, setResendPassword] = useState('');
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false);
const { login, signup, sendPasswordReset, sendVerificationEmailToUser } = useAuth();
// Persist verification state across re-renders
useEffect(() => {
const savedEmail = sessionStorage.getItem('needsVerification');
if (savedEmail) {
setNeedsVerification(true);
setResendEmail(savedEmail);
setError('Please verify your email address before logging in. Check your inbox for the verification link.');
}
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
if (isSignup) {
if (!firstName.trim()) {
setError('Please enter your first name');
setLoading(false);
return;
}
if (password !== confirmPassword) {
setError('Passwords do not match');
setLoading(false);
return;
}
try {
const newUser = await signup(email, password, firstName);
// Send verification email to the newly created user
if (newUser) {
try {
await sendVerificationEmailToUser(newUser);
// Sign them out immediately - they must verify before login
const { signOut } = await import('firebase/auth');
const { auth } = await import('@/lib/firebase');
await signOut(auth);
setVerificationSent(true);
setShowVerificationBanner(true);
} catch (err) {
console.error('Error sending verification email:', err);
}
}
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'An error occurred');
}
} else {
try {
await login(email, password);
// Store remember me timestamp if checked
if (rememberMe) {
localStorage.setItem('rememberMeTimestamp', Date.now().toString());
} else {
localStorage.removeItem('rememberMeTimestamp');
}
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
const errorCode = err instanceof Error && 'code' in err ? (err as any).code : '';
console.log('Login error:', errorMessage, 'Code:', errorCode); // Debug log
setError(errorMessage);
}
}
setLoading(false);
};
const handleResendVerification = async () => {
if (!resendPassword) {
setShowPasswordPrompt(true);
return;
}
setLoading(true);
setError('');
try {
// Try to login temporarily to send verification
const { signInWithEmailAndPassword } = await import('firebase/auth');
const { auth } = await import('@/lib/firebase');
const userCred = await signInWithEmailAndPassword(auth, resendEmail, resendPassword);
await sendVerificationEmailToUser(userCred.user);
// Sign them back out
const { signOut } = await import('firebase/auth');
await signOut(auth);
setError('');
setNeedsVerification(false);
sessionStorage.removeItem('needsVerification');
setShowVerificationBanner(true);
setVerificationSent(true);
setResendPassword('');
setShowPasswordPrompt(false);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to resend verification email. Check your password.');
setResendPassword('');
}
setLoading(false);
};
const handleForgotPassword = async () => {
if (!forgotEmail) {
setForgotMessage('Please enter your email address');
return;
}
setForgotLoading(true);
setForgotMessage('');
try {
await sendPasswordReset(forgotEmail);
setForgotMessage('Password reset email sent! Check your inbox.');
} catch (err: unknown) {
setForgotMessage(err instanceof Error ? err.message : 'Failed to send reset email');
}
setForgotLoading(false);
};
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-black text-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold mb-2">Haver</h1>
<p className="text-gray-400">Your prayer companion</p>
</div>
<form onSubmit={handleSubmit} className="bg-gray-800 p-8 rounded-xl shadow-2xl space-y-6">
<h2 className="text-2xl font-semibold text-center">
{isSignup ? 'Create Account' : 'Welcome Back'}
</h2>
{error && (
<div className="bg-red-900 border border-red-700 text-red-200 px-4 py-3 rounded-lg">
{error}
</div>
)}
{needsVerification && (
<div className="bg-yellow-900 border-2 border-yellow-600 text-yellow-100 px-5 py-4 rounded-lg">
<div className="flex items-start gap-3">
<svg className="w-6 h-6 text-yellow-400 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
<div className="flex-1">
<p className="font-bold text-lg mb-2">⚠️ Email Not Verified</p>
<p className="mb-2">Your account exists, but <strong>you haven't verified your email yet</strong>.</p>
<p className="text-sm text-yellow-300 mb-3">Check your inbox for <strong>{resendEmail}</strong> and click the verification link. Don't forget to check your spam folder!</p>
<div className="bg-yellow-950 border border-yellow-700 rounded p-3 mb-3 text-sm">
<p className="font-semibold mb-1">📧 Can't find the email?</p>
<p className="text-yellow-300 text-xs">Click the button below to send a new verification email.</p>
</div>
{showPasswordPrompt && (
<div className="mb-3">
<label className="block text-sm font-medium text-yellow-300 mb-2">Enter your password to resend:</label>
<input
type="password"
value={resendPassword}
onChange={(e) => setResendPassword(e.target.value)}
placeholder="Your password"
className="w-full p-2 bg-yellow-950 border border-yellow-700 rounded-lg text-white placeholder-yellow-600 focus:ring-2 focus:ring-yellow-500 focus:border-transparent"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleResendVerification();
}
}}
/>
</div>
)}
<button
type="button"
onClick={handleResendVerification}
disabled={loading}
className="w-full px-4 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:bg-yellow-800 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors"
>
{loading ? 'Sending...' : '📨 Resend Verification Email'}
</button>
</div>
</div>
</div>
)}
{isSignup && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">First Name</label>
<input
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Enter your first name"
required={isSignup}
/>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoFocus
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Enter your email"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Enter your password"
required
/>
</div>
{isSignup && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Confirm Password</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Confirm your password"
required
/>
</div>
)}
{!isSignup && (
<div className="space-y-2">
<div className="flex items-center">
<input
type="checkbox"
id="rememberMe"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
className="w-4 h-4 rounded border-gray-600 bg-gray-700 text-blue-600 focus:ring-2 focus:ring-blue-500 cursor-pointer"
/>
<label htmlFor="rememberMe" className="ml-2 text-sm text-gray-300 cursor-pointer">
Remember me for 15 days
</label>
</div>
<div className="text-right">
<button
type="button"
onClick={() => {
setShowForgotPassword(true);
setForgotEmail(email);
setForgotMessage('');
}}
className="text-sm text-blue-400 hover:text-blue-300 transition-colors"
>
Forgot password?
</button>
</div>
</div>
)}
<button
type="submit"
disabled={loading}
className="w-full p-3 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-800 disabled:cursor-not-allowed rounded-lg font-medium transition-colors flex items-center justify-center"
>
{loading ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing...
</>
) : (
isSignup ? 'Create Account' : 'Sign In'
)}
</button>
{showVerificationBanner && (
<div className="bg-blue-900 border-2 border-blue-600 text-blue-100 px-5 py-4 rounded-lg">
<div className="flex items-start gap-3 mb-3">
<svg className="w-6 h-6 text-blue-400 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
<div className="flex-1">
<p className="font-bold text-lg mb-2">⚠️ Email Verification Required</p>
<p className="mb-2">Your account has been created, but <strong>you must verify your email before you can sign in</strong>.</p>
<p className="text-sm text-blue-300 mb-3">A verification email has been sent to <strong>{email || resendEmail}</strong>. Please check your inbox (and spam folder) and click the verification link.</p>
<div className="bg-blue-950 border border-blue-700 rounded p-3 mb-3 text-sm">
<p className="font-semibold mb-1">📧 Didn't receive the email?</p>
<p className="text-blue-300 text-xs">It may take a few minutes to arrive. Check your spam/junk folder.</p>
</div>
<button
type="button"
onClick={async () => {
setLoading(true);
try {
const { signInWithEmailAndPassword, signOut } = await import('firebase/auth');
const { auth } = await import('@/lib/firebase');
const userCred = await signInWithEmailAndPassword(auth, email, password);
await sendVerificationEmailToUser(userCred.user);
await signOut(auth);
setError('');
alert('✓ Verification email resent! Check your inbox.');
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to resend email');
}
setLoading(false);
}}
disabled={loading}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-800 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors"
>
{loading ? 'Resending...' : '📨 Resend Verification Email'}
</button>
</div>
</div>
</div>
)}
<div className="text-center">
<button
type="button"
onClick={() => {
setIsSignup(!isSignup);
setError('');
setConfirmPassword('');
setFirstName('');
}}
className="text-blue-400 hover:text-blue-300 transition-colors"
>
{isSignup ? 'Already have an account? Sign In' : 'Need an account? Sign Up'}
</button>
</div>
</form>
</div>
{/* Forgot Password Modal */}
{showForgotPassword && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-gray-800 rounded-xl p-6 max-w-sm w-full">
<h2 className="text-xl font-bold text-white mb-2">Reset Password</h2>
<p className="text-gray-400 mb-4">Enter your email and we'll send you a link to reset your password.</p>
{forgotMessage && (
<div className={`px-4 py-2 rounded-lg mb-4 text-sm ${
forgotMessage.includes('sent')
? 'bg-green-900 border border-green-700 text-green-200'
: 'bg-red-900 border border-red-700 text-red-200'
}`}>
{forgotMessage}
</div>
)}
<input
type="email"
value={forgotEmail}
onChange={(e) => setForgotEmail(e.target.value)}
placeholder="Enter your email"
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors mb-4"
/>
<div className="flex gap-3">
<button
onClick={() => {
setShowForgotPassword(false);
setForgotEmail('');
setForgotMessage('');
}}
className="flex-1 px-4 py-2 rounded-lg bg-gray-700 text-white font-medium hover:bg-gray-600 transition-colors"
>
Cancel
</button>
<button
onClick={handleForgotPassword}
disabled={forgotLoading || !forgotEmail}
className="flex-1 px-4 py-2 rounded-lg bg-blue-600 text-white font-medium hover:bg-blue-700 disabled:bg-blue-800 disabled:cursor-not-allowed transition-colors flex items-center justify-center"
>
{forgotLoading ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Sending...
</>
) : (
'Send Reset Link'
)}
</button>
</div>
</div>
</div>
)}
</div>
);
}