11import { useState } from 'react' ;
22import { Link } from 'react-router-dom' ;
3- import { Mail , ArrowLeft , CheckCircle , XCircle , Github } from 'lucide-react' ;
3+ import { Mail , ArrowLeft , CheckCircle , XCircle } from 'lucide-react' ;
44import { TlsStatusIndicator } from '../components/TlsStatusIndicator' ;
5+ import { isValidEmail } from '../utils/validation' ;
56
67export function ForgotPassword ( ) {
78 const [ email , setEmail ] = useState ( '' ) ;
89 const [ loading , setLoading ] = useState ( false ) ;
910 const [ resetSent , setResetSent ] = useState ( false ) ;
10- const [ userExists , setUserExists ] = useState < boolean | null > ( null ) ;
1111 const [ error , setError ] = useState ( '' ) ;
1212 const [ emailValid , setEmailValid ] = useState < boolean | null > ( null ) ;
1313
14- const isValidEmail = ( email : string ) : boolean => {
15- const emailRegex = / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / ;
16- return emailRegex . test ( email ) ;
17- } ;
18-
1914 const handleSubmit = async ( e : React . FormEvent ) => {
2015 e . preventDefault ( ) ;
2116
@@ -24,8 +19,7 @@ export function ForgotPassword() {
2419 return ;
2520 }
2621
27- const emailRegex = / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / ;
28- if ( ! emailRegex . test ( email ) ) {
22+ if ( ! isValidEmail ( email ) ) {
2923 setError ( 'Please enter a valid email address' ) ;
3024 return ;
3125 }
@@ -47,12 +41,10 @@ export function ForgotPassword() {
4741
4842 if ( response . ok ) {
4943 setResetSent ( true ) ;
50- setUserExists ( data . userExists ) ;
5144 } else {
5245 setError ( data . error || 'Failed to send reset link' ) ;
5346 }
5447 } catch ( error ) {
55- console . error ( 'Forgot password error:' , error ) ;
5648 setError ( 'Failed to send reset link. Please try again.' ) ;
5749 } finally {
5850 setLoading ( false ) ;
@@ -76,119 +68,29 @@ export function ForgotPassword() {
7668
7769 { /* Reset Form */ }
7870 < div className = "bg-gray-800/50 backdrop-blur-xl border border-gray-700/50 rounded-2xl p-8 space-y-5 shadow-2xl animate-in fade-in slide-in-from-bottom-4 duration-700" >
79- { /* Social Login Buttons */ }
80- < div className = "grid grid-cols-3 gap-3" >
81- < button
82- type = "button"
83- onClick = { ( ) => window . location . href = `${ import . meta. env . VITE_API_URL || 'https://localhost:4128' } /auth/google` }
84- className = "group relative flex items-center justify-center p-3 bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 hover:shadow-lg hover:shadow-teal-500/30 rounded-lg transition-all duration-200"
85- aria-label = "Sign in with Google"
86- >
87- < svg className = "h-5 w-5 transition-all duration-200 group-hover:scale-110" viewBox = "0 0 24 24" >
88- < path fill = "#EA4335" d = "M5.266 9.765A7.077 7.077 0 0 1 12 4.909c1.69 0 3.218.6 4.418 1.582L19.91 3C17.782 1.145 15.055 0 12 0 7.27 0 3.198 2.698 1.24 6.65l4.026 3.115Z" />
89- < path fill = "#34A853" d = "M16.04 18.013c-1.09.703-2.474 1.078-4.04 1.078a7.077 7.077 0 0 1-6.723-4.823l-4.04 3.067A11.965 11.965 0 0 0 12 24c2.933 0 5.735-1.043 7.834-3l-3.793-2.987Z" />
90- < path fill = "#4A90E2" d = "M19.834 21c2.195-2.048 3.62-5.096 3.62-9 0-.71-.109-1.473-.272-2.182H12v4.637h6.436c-.317 1.559-1.17 2.766-2.395 3.558L19.834 21Z" />
91- < path fill = "#FBBC05" d = "M5.277 14.268A7.12 7.12 0 0 1 4.909 12c0-.782.125-1.533.357-2.235L1.24 6.65A11.934 11.934 0 0 0 0 12c0 1.92.445 3.73 1.237 5.335l4.04-3.067Z" />
92- </ svg >
93- < span className = "absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-teal-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10" >
94- Sign in with Google
95- </ span >
96- </ button >
97-
98- < button
99- type = "button"
100- onClick = { ( ) => window . location . href = `${ import . meta. env . VITE_API_URL || 'https://localhost:4128' } /auth/linkedin` }
101- className = "group relative flex items-center justify-center p-3 bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 hover:shadow-lg hover:shadow-teal-500/30 rounded-lg transition-all duration-200"
102- aria-label = "Sign in with LinkedIn"
103- >
104- < svg className = "h-5 w-5 transition-all duration-200 group-hover:scale-110" viewBox = "0 0 24 24" fill = "#60A5FA" >
105- < path d = "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
106- </ svg >
107- < span className = "absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-teal-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10" >
108- Sign in with LinkedIn
109- </ span >
110- </ button >
111-
112- < button
113- type = "button"
114- onClick = { ( ) => window . location . href = `${ import . meta. env . VITE_API_URL || 'https://localhost:4128' } /auth/github` }
115- className = "group relative flex items-center justify-center p-3 bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 hover:shadow-lg hover:shadow-teal-500/30 rounded-lg transition-all duration-200"
116- aria-label = "Sign in with GitHub"
117- >
118- < Github className = "h-5 w-5 text-gray-300 transition-all duration-200 group-hover:scale-110" />
119- < span className = "absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-teal-400 text-xs opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10" >
120- Sign in with GitHub
121- </ span >
122- </ button >
123- </ div >
124-
125- < div className = "relative" >
126- < div className = "absolute inset-0 flex items-center" >
127- < div className = "w-full border-t border-gray-600" />
128- </ div >
129- < div className = "relative flex justify-center text-sm" >
130- < span className = "px-2 bg-gray-800 text-gray-400" > Or reset your password</ span >
131- </ div >
132- </ div >
133-
13471 { resetSent ? (
135- userExists ? (
136- < div className = "p-6 bg-teal-900/20 border border-teal-500/30 rounded-xl" >
137- < div className = "flex items-center justify-center mb-3" >
138- < CheckCircle className = "h-12 w-12 text-teal-400" />
139- </ div >
140- < h3 className = "text-lg font-semibold text-teal-300 text-center mb-2" > Check Your Email!</ h3 >
141- < p className = "text-sm text-teal-200/80 text-center mb-4" >
142- We've sent a password reset link to < strong > { email } </ strong >
143- </ p >
144- < p className = "text-xs text-teal-300/60 text-center mb-4" >
145- Click the link in the email to reset your password. The link expires in 1 hour.
146- </ p >
147- < button
148- type = "button"
149- onClick = { ( ) => {
150- setResetSent ( false ) ;
151- setEmail ( '' ) ;
152- setUserExists ( null ) ;
153- } }
154- className = "mt-2 w-full text-sm text-teal-400 hover:text-teal-300 transition-colors"
155- >
156- Try a different email
157- </ button >
72+ < div className = "p-6 bg-teal-900/20 border border-teal-500/30 rounded-xl" >
73+ < div className = "flex items-center justify-center mb-3" >
74+ < CheckCircle className = "h-12 w-12 text-teal-400" />
15875 </ div >
159- ) : (
160- < div className = "p-6 bg-red-900/20 border border-red-500/30 rounded-xl" >
161- < div className = "flex items-center justify-center mb-3" >
162- < XCircle className = "h-12 w-12 text-red-400" />
163- </ div >
164- < h3 className = "text-lg font-semibold text-red-300 text-center mb-2" > Email Not Found</ h3 >
165- < p className = "text-sm text-red-200/80 text-center mb-4" >
166- The email < strong > { email } </ strong > is not registered in our system.
167- </ p >
168- < p className = "text-xs text-red-300/60 text-center mb-4" >
169- Please check the email address or create a new account.
170- </ p >
171- < div className = "space-y-2" >
172- < button
173- type = "button"
174- onClick = { ( ) => {
175- setResetSent ( false ) ;
176- setEmail ( '' ) ;
177- setUserExists ( null ) ;
178- } }
179- className = "mt-2 w-full text-sm text-red-400 hover:text-red-300 transition-colors"
180- >
181- Try a different email
182- </ button >
183- < Link
184- to = "/signup"
185- className = "block w-full text-center text-sm text-red-400 hover:text-red-300 transition-colors"
186- >
187- Create a new account
188- </ Link >
189- </ div >
190- </ div >
191- )
76+ < h3 className = "text-lg font-semibold text-teal-300 text-center mb-2" > Check Your Email!</ h3 >
77+ < p className = "text-sm text-teal-200/80 text-center mb-4" >
78+ If an account exists for < strong > { email } </ strong > , you'll receive a password reset link shortly.
79+ </ p >
80+ < p className = "text-xs text-teal-300/60 text-center mb-4" >
81+ Click the link in the email to reset your password. The link expires in 1 hour. If you don't receive an email, please check your spam folder or verify the email address.
82+ </ p >
83+ < button
84+ type = "button"
85+ onClick = { ( ) => {
86+ setResetSent ( false ) ;
87+ setEmail ( '' ) ;
88+ } }
89+ className = "mt-2 w-full text-sm text-teal-400 hover:text-teal-300 transition-colors"
90+ >
91+ Try a different email
92+ </ button >
93+ </ div >
19294 ) : (
19395 < form onSubmit = { handleSubmit } className = "space-y-5" >
19496 { /* Email Field */ }
@@ -215,6 +117,7 @@ export function ForgotPassword() {
215117 setEmailValid ( isValidEmail ( value ) ) ;
216118 }
217119 } }
120+ autoComplete = "email"
218121 autoFocus
219122 className = { `w-full pl-10 py-3 bg-gray-700/50 backdrop-blur-sm border rounded-xl text-gray-100 focus:outline-none focus:ring-2 transition-all ${
220123 emailValid === false
0 commit comments