1+ import React , { useState , useEffect } from 'react' ;
2+ import { useSearchParams , useNavigate } from 'react-router-dom' ;
3+ import { toast } from 'react-toastify' ;
4+ import api from '../api/api' ;
5+
6+ const ResetPasswordPage : React . FC = ( ) => {
7+ const [ searchParams ] = useSearchParams ( ) ;
8+ const navigate = useNavigate ( ) ;
9+ const [ formData , setFormData ] = useState ( {
10+ newPassword : '' ,
11+ confirmPassword : '' ,
12+ } ) ;
13+ const [ loading , setLoading ] = useState ( false ) ;
14+ const [ isValidToken , setIsValidToken ] = useState < boolean | null > ( null ) ;
15+ const token = searchParams . get ( 'token' ) ;
16+
17+ useEffect ( ( ) => {
18+ if ( ! token ) {
19+ toast . error ( 'Invalid reset link' ) ;
20+ navigate ( '/auth' ) ;
21+ return ;
22+ }
23+ setIsValidToken ( true ) ;
24+ } , [ token , navigate ] ) ;
25+
26+ const handleSubmit = async ( e : React . FormEvent ) => {
27+ e . preventDefault ( ) ;
28+
29+ if ( ! token ) {
30+ toast . error ( 'Invalid reset token' ) ;
31+ return ;
32+ }
33+
34+ if ( formData . newPassword !== formData . confirmPassword ) {
35+ toast . error ( 'Passwords do not match' ) ;
36+ return ;
37+ }
38+
39+ if ( formData . newPassword . length < 8 ) {
40+ toast . error ( 'Password must be at least 8 characters long' ) ;
41+ return ;
42+ }
43+
44+ setLoading ( true ) ;
45+ try {
46+ await api . post ( '/v1/auth/reset-password' , {
47+ token,
48+ new_password : formData . newPassword ,
49+ confirm_password : formData . confirmPassword ,
50+ } ) ;
51+
52+ toast . success ( 'Password reset successfully! You can now log in with your new password.' ) ;
53+ navigate ( '/auth' ) ;
54+ } catch ( error : any ) {
55+ const errorMessage = error . response ?. data ?. message || 'Failed to reset password' ;
56+ toast . error ( errorMessage ) ;
57+ } finally {
58+ setLoading ( false ) ;
59+ }
60+ } ;
61+
62+ const handleChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
63+ setFormData ( {
64+ ...formData ,
65+ [ e . target . name ] : e . target . value ,
66+ } ) ;
67+ } ;
68+
69+ if ( isValidToken === null ) {
70+ return (
71+ < div className = "min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center" >
72+ < div className = "text-center" >
73+ < div className = "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto" > </ div >
74+ < p className = "mt-4 text-gray-600" > Validating reset link...</ p >
75+ </ div >
76+ </ div >
77+ ) ;
78+ }
79+
80+ return (
81+ < div className = "min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8" >
82+ < div className = "max-w-md w-full space-y-8" >
83+ < div className = "bg-white rounded-xl shadow-lg p-8" >
84+ < div className = "text-center mb-8" >
85+ < img
86+ src = "/open-container-engine-logo.png"
87+ alt = "Open Container Engine"
88+ className = "h-12 w-auto mx-auto mb-4"
89+ />
90+ < h2 className = "text-3xl font-bold text-gray-900" > Reset Password</ h2 >
91+ < p className = "mt-2 text-gray-600" >
92+ Enter your new password below
93+ </ p >
94+ </ div >
95+
96+ < form onSubmit = { handleSubmit } className = "space-y-6" >
97+ < div >
98+ < label htmlFor = "newPassword" className = "block text-sm font-medium text-gray-700 mb-2" >
99+ New Password
100+ </ label >
101+ < input
102+ id = "newPassword"
103+ name = "newPassword"
104+ type = "password"
105+ required
106+ value = { formData . newPassword }
107+ onChange = { handleChange }
108+ className = "w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
109+ placeholder = "Enter your new password"
110+ minLength = { 8 }
111+ />
112+ </ div >
113+
114+ < div >
115+ < label htmlFor = "confirmPassword" className = "block text-sm font-medium text-gray-700 mb-2" >
116+ Confirm Password
117+ </ label >
118+ < input
119+ id = "confirmPassword"
120+ name = "confirmPassword"
121+ type = "password"
122+ required
123+ value = { formData . confirmPassword }
124+ onChange = { handleChange }
125+ className = "w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
126+ placeholder = "Confirm your new password"
127+ minLength = { 8 }
128+ />
129+ </ div >
130+
131+ < div className = "space-y-4" >
132+ < button
133+ type = "submit"
134+ disabled = { loading }
135+ className = "w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-all duration-200 font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
136+ >
137+ { loading ? (
138+ < >
139+ < div className = "animate-spin rounded-full h-5 w-5 border-b-2 border-white mr-2" > </ div >
140+ Resetting Password...
141+ </ >
142+ ) : (
143+ 'Reset Password'
144+ ) }
145+ </ button >
146+
147+ < div className = "text-center" >
148+ < button
149+ type = "button"
150+ onClick = { ( ) => navigate ( '/auth' ) }
151+ className = "text-blue-600 hover:text-blue-800 font-medium transition-colors duration-200"
152+ >
153+ Back to Login
154+ </ button >
155+ </ div >
156+ </ div >
157+ </ form >
158+
159+ < div className = "mt-6 p-4 bg-blue-50 rounded-lg" >
160+ < h4 className = "text-sm font-medium text-blue-800 mb-2" > Password Requirements:</ h4 >
161+ < ul className = "text-sm text-blue-700 space-y-1" >
162+ < li > • At least 8 characters long</ li >
163+ < li > • Must match the confirmation password</ li >
164+ </ ul >
165+ </ div >
166+ </ div >
167+ </ div >
168+ </ div >
169+ ) ;
170+ } ;
171+
172+ export default ResetPasswordPage ;
0 commit comments