@@ -382,11 +470,24 @@ export default function Register() {
{/* Fixed Bottom Button */}
+ {error && (
+
+ {error}
+
+ )}
diff --git a/web/src/pages/ResetPassword.jsx b/web/src/pages/ResetPassword.jsx
new file mode 100644
index 0000000..586303c
--- /dev/null
+++ b/web/src/pages/ResetPassword.jsx
@@ -0,0 +1,245 @@
+import React, { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { createPageUrl } from '@/utils';
+import { motion } from 'framer-motion';
+import { Lock, Eye, EyeOff, CheckCircle } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { supabase } from '@/lib/supabaseClient';
+
+export default function ResetPassword() {
+ const navigate = useNavigate();
+ const [newPassword, setNewPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [isSuccess, setIsSuccess] = useState(false);
+ const [hasRecoverySession, setHasRecoverySession] = useState(false);
+
+ useEffect(() => {
+ // Check if we have a recovery session
+ const checkRecoverySession = async () => {
+ try {
+ const { data: { session } } = await supabase.auth.getSession();
+
+ // Check if this is a recovery session (password reset)
+ if (session) {
+ setHasRecoverySession(true);
+ } else {
+ setError('Invalid or expired password reset link. Please request a new one.');
+ }
+ } catch (err) {
+ console.error('Error checking recovery session:', err);
+ setError('Failed to verify password reset link.');
+ }
+ };
+
+ checkRecoverySession();
+
+ // Listen for auth state changes (handles recovery events)
+ const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
+ if (event === 'PASSWORD_RECOVERY') {
+ setHasRecoverySession(true);
+ }
+ });
+
+ return () => {
+ subscription.unsubscribe();
+ };
+ }, []);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+
+ // Validation
+ if (newPassword.length < 6) {
+ setError('Password must be at least 6 characters long');
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ setError('Passwords do not match');
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ // Update user's password
+ const { error: updateError } = await supabase.auth.updateUser({
+ password: newPassword
+ });
+
+ if (updateError) {
+ throw updateError;
+ }
+
+ setIsSuccess(true);
+
+ // Sign out and redirect to login after a brief delay
+ setTimeout(async () => {
+ await supabase.auth.signOut();
+ navigate(createPageUrl('Auth'));
+ }, 2000);
+ } catch (err) {
+ console.error('Password reset error:', err);
+ setError(err.message || 'Failed to reset password. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Success state
+ if (isSuccess) {
+ return (
+
+
+
+
+
+
+ Password Reset Successful
+
+
+
+ Your password has been successfully reset. You will be redirected to the login page shortly.
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ Create New Password
+
+
+
+ Please enter your new password below.
+
+
+ {!hasRecoverySession ? (
+
+ {error || 'Invalid or expired password reset link.'}
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+}