import React, { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { clearError } from '../store/reducers/authSlice'; // Import the Supabase client import { supabase } from '../services/supabaseClient'; const ResetPassword = () => { const dispatch = useDispatch(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { loading, error } = useSelector(state => state.auth); const [formData, setFormData] = useState({ token: '', // This is likely unused in the v2 flow, but kept for structure password: '', confirmPassword: '' }); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [passwordStrength, setPasswordStrength] = useState(0); const [isFocused, setIsFocused] = useState({ password: false, confirmPassword: false }); const [authState, setAuthState] = useState({ event: null, session: null }); // --- Listen for auth state changes specifically on this page --- // This helps confirm if PASSWORD_RECOVERY was detected useEffect(() => { console.log("ResetPassword component mounted. Setting up auth listener."); const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => { console.log('Auth state changed within ResetPassword component:', event, session ? `User: ${session.user?.id || 'N/A'}` : 'No session'); setAuthState({ event, session }); if (event === 'PASSWORD_RECOVERY') { console.log('*** PASSWORD_RECOVERY event confirmed in ResetPassword component. ***'); // Optional: You could set some state here to indicate the recovery flow is active } }); // Cleanup listener on unmount return () => { console.log("ResetPassword component unmounting. Removing auth listener."); authListener?.subscription.unsubscribe(); }; }, []); // Get token from URL params (old v1 flow, kept for reference but likely not used) useEffect(() => { const token = searchParams.get('token'); if (token) { console.warn("Token found in search params (v1 flow?). This might be unexpected in v2 flow:", token); setFormData(prev => ({ ...prev, token })); } }, [searchParams]); const calculatePasswordStrength = (password) => { let strength = 0; // Length check if (password.length >= 8) strength += 1; if (password.length >= 12) strength += 1; // Character variety checks if (/[a-z]/.test(password)) strength += 1; if (/[A-Z]/.test(password)) strength += 1; if (/[0-9]/.test(password)) strength += 1; if (/[^A-Za-z0-9]/.test(password)) strength += 1; setPasswordStrength(Math.min(strength, 6)); }; const handleChange = (e) => { const { name, value } = e.target; setFormData({ ...formData, [name]: value }); // Calculate password strength if (name === 'password') { calculatePasswordStrength(value); } }; const handleFocus = (field) => { setIsFocused({ ...isFocused, [field]: true }); }; const handleBlur = (field) => { setIsFocused({ ...isFocused, [field]: false }); }; const togglePasswordVisibility = () => { setShowPassword(!showPassword); }; const toggleConfirmPasswordVisibility = () => { setShowConfirmPassword(!showConfirmPassword); }; const handleSubmit = async (e) => { e.preventDefault(); // Basic validation if (formData.password !== formData.confirmPassword) { alert('Passwords do not match'); return; } if (formData.password.length < 8) { alert('Password must be at least 8 characters long'); return; } try { console.log("Attempting to get current session before password update..."); // --- Check for existing session (recovery session) --- // It's crucial to verify the session was established by the Supabase client // when it processed the URL fragments on app load. const { data: { session: currentSession }, error: sessionError } = await supabase.auth.getSession(); console.log("Current session check result:", currentSession ? `Session for user: ${currentSession.user?.id || 'N/A'}` : 'No session', sessionError); if (sessionError) { console.error('Error getting current session:', sessionError); alert('Failed to get session. Please try resetting your password again.'); return; } if (!currentSession || !currentSession.user) { // This is the likely cause of AuthSessionMissingError console.warn('No active session found in handleSubmit. The URL might not have been processed correctly or the session expired.'); console.log("Auth state at time of submission:", authState); alert('Password reset link may be invalid or expired. Please request a new one.'); // Optionally, redirect to /forgot-password // navigate('/forgot-password'); return; } console.log('Resetting password with Supabase client for user:', currentSession.user?.email); const { data, error } = await supabase.auth.updateUser({ password: formData.password }); console.log("supabase.auth.updateUser result:", data, error); if (error) { console.error('Supabase password reset error:', error); let message = 'Failed to reset password.'; if (error.message.toLowerCase().includes('password')) { message = 'Password does not meet requirements. Please use at least 8 characters.'; } else if (error.message.toLowerCase().includes('session') || error.message.includes("Auth session missing")) { message = 'Password reset session expired. Please request a new reset link.'; } else { message = error.message; } alert(message); return; } console.log('Password updated successfully:', data); alert('Password reset successfully! You can now log in with your new password.'); // After successful reset, sign the user out of the temporary recovery session. await supabase.auth.signOut(); navigate('/login'); } catch (err) { console.error('Unexpected error during password reset:', err); alert('An unexpected error occurred. Please try again.'); } }; const handleBackToLogin = () => { dispatch(clearError()); navigate('/login'); }; return (
{/* Logo and Brand */}
Lin

Reset Password

Enter your new password below

{/* Auth Card */}
{/* --- Debug Info Display (Remove in production) --- */}
Debug Info:

Auth Event: {authState.event || 'None'}

Auth Session User ID: {authState.session?.user?.id || 'None'}

{/* --- End Debug Info --- */} {/* Error Message */} {error && (
{error}
)}
{/* Password Field */}
handleFocus('password')} onBlur={() => handleBlur('password')} className={`w-full px-3 sm:px-4 py-2 sm:py-3 rounded-xl border-2 transition-all duration-200 ${ isFocused.password ? 'border-primary-500 shadow-md' : 'border-gray-200 hover:border-gray-300' } ${formData.password ? 'text-gray-900' : 'text-gray-500'} focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 touch-manipulation`} placeholder="Create a new password" required aria-required="true" aria-label="New password" />
{/* Password Strength Indicator */} {formData.password && (
Password strength {passwordStrength <= 2 ? 'Weak' : passwordStrength <= 4 ? 'Fair' : passwordStrength === 5 ? 'Good' : 'Strong'}
Use 8+ characters with uppercase, lowercase, numbers, and symbols
)}
{/* Confirm Password Field */}
handleFocus('confirmPassword')} onBlur={() => handleBlur('confirmPassword')} className={`w-full px-3 sm:px-4 py-2 sm:py-3 rounded-xl border-2 transition-all duration-200 ${ isFocused.confirmPassword ? 'border-primary-500 shadow-md' : 'border-gray-200 hover:border-gray-300' } ${formData.confirmPassword ? 'text-gray-900' : 'text-gray-500'} focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 touch-manipulation`} placeholder="Confirm your new password" required aria-required="true" aria-label="Confirm new password" />
{formData.confirmPassword && formData.password !== formData.confirmPassword && (

Passwords do not match

)}
{/* Submit Button */}
{/* Back to Login Link */}
{/* Footer */}

© 2024 Lin. All rights reserved.

); }; export default ResetPassword;