/** * Update Password Page - For users who clicked the reset link from email * This page is shown after clicking the password reset link * Supports both native auth flow and custom token-based flow */ import { useState, useEffect } from 'react'; import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { auth } from '../lib/auth-client'; import { motion } from 'framer-motion'; import { useUserStore } from '../store/userStore'; import { Sun, Moon, Lock, CheckCircle, AlertTriangle, Eye, EyeOff } from 'lucide-react'; import { api } from '../services/api'; export default function UpdatePassword() { const { isDark, toggleTheme } = useUserStore(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [validSession, setValidSession] = useState(null); // Custom token-based flow parameters - read directly from URL const customToken = searchParams.get('token'); const customEmail = searchParams.get('email'); const isCustomFlow = !!(customToken && customEmail); // Debug logging useEffect(() => { console.log('UpdatePassword: URL params check', { token: customToken ? 'present' : 'missing', email: customEmail, isCustomFlow, fullUrl: window.location.href }); }, [customToken, customEmail, isCustomFlow]); // Check if user has a valid recovery session useEffect(() => { const checkSession = async () => { console.log('UpdatePassword: Checking session, isCustomFlow:', isCustomFlow); // If we have custom token and email from our custom flow, it's valid if (customToken && customEmail) { console.log('UpdatePassword: Custom flow detected, setting validSession to true'); setValidSession(true); return; } try { const { data: { session }, error } = await auth.getSession(); if (error) { console.error('Session error:', error); setValidSession(false); return; } // Check if this is a recovery session (from email link) if (session?.user) { setValidSession(true); } else { // Try to get session from URL hash (recovery flow) const hashParams = new URLSearchParams(window.location.hash.substring(1)); const accessToken = hashParams.get('access_token'); const type = hashParams.get('type'); if (accessToken && type === 'recovery') { // Set session from recovery token const { error: setSessionError } = await auth.setSession({ access_token: accessToken, refresh_token: hashParams.get('refresh_token') || '' }); if (setSessionError) { console.error('Set session error:', setSessionError); setValidSession(false); } else { setValidSession(true); } } else { setValidSession(false); } } } catch (err) { console.error('Check session error:', err); setValidSession(false); } }; checkSession(); // Listen for auth state changes (handles recovery flow) const { data: { subscription } } = auth.onAuthStateChange((event, session) => { if (event === 'PASSWORD_RECOVERY') { setValidSession(true); } }); return () => subscription.unsubscribe(); }, [customToken, customEmail]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(''); // Validation if (password.length < 6) { setError('Password must be at least 6 characters long'); return; } if (password !== confirmPassword) { setError('Passwords do not match'); return; } setLoading(true); try { if (isCustomFlow) { // Use our custom backend endpoint for token-based password reset const response = await api.post('/api/v1/settings/auth/update-password-with-token', { token: customToken, email: customEmail, new_password: password }); if (response.data.success) { setSuccess(true); // Redirect to login after 3 seconds setTimeout(() => { navigate('/login'); }, 3000); } else { setError(response.data.message || 'Failed to update password'); } } else { // Use native auth flow const { error } = await auth.updateUser({ password: password }); if (error) { setError(error.message); } else { setSuccess(true); // Redirect to login after 3 seconds setTimeout(() => { navigate('/login'); }, 3000); } } } catch (err: any) { setError(err.response?.data?.detail || err.message || 'An error occurred'); } finally { setLoading(false); } }; const renderBackground = () => ( <> {/* Background Orbs */}
{/* Theme Toggle */} ); // Success state if (success) { return (
{renderBackground()}

Password Updated!

Your password has been successfully reset. You will be redirected to login shortly.

Go to Login
); } // Invalid/expired link state if (validSession === false) { return (
{renderBackground()}

Invalid or Expired Link

This password reset link is invalid or has expired. Please request a new one.

Request New Link ← Back to Login
); } // Loading/checking session if (validSession === null) { return (
{renderBackground()}

Verifying reset link...

); } // Main reset form return (
{renderBackground()} {/* Logo/Brand */}
DataVision Logo { (e.target as HTMLImageElement).src = '/logo.svg'; }} />
Data Vision

Create a new password

{/* Card */}

Set New Password

Enter your new password below

{/* Error Message */} {error && (

{error}

)}
setPassword(e.target.value)} required minLength={6} className="w-full px-5 py-3.5 pr-12 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-primary-500/50 glass-input" placeholder="Min 6 characters" />
setConfirmPassword(e.target.value)} required minLength={6} className="w-full px-5 py-3.5 pr-12 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-primary-500/50 glass-input" placeholder="Re-enter password" />
{/* Password match indicator */} {confirmPassword && (
{password === confirmPassword ? ( <> Passwords match ) : ( <> Passwords do not match )}
)}
← Back to Login
); }