11import { useState , useEffect } from 'react' ;
22import { Link , useNavigate } from 'react-router-dom' ;
33import { useMutation , gql } from '@apollo/client' ;
4- import { Eye , EyeOff , ArrowRight , CheckCircle , XCircle , Github } from 'lucide-react' ;
5- import { useAuth } from '../contexts/AuthContext' ;
4+ import { Eye , EyeOff , ArrowRight , CheckCircle , XCircle , Github , Mail } from 'lucide-react' ;
65import { TlsStatusIndicator } from '../components/TlsStatusIndicator' ;
76import { isValidEmail , getPasswordStrength } from '../utils/validation' ;
87
@@ -31,40 +30,61 @@ const CHECK_AVAILABILITY = gql`
3130 }
3231` ;
3332
33+ const RESEND_VERIFICATION_EMAIL = gql `
34+ mutation ResendVerificationEmail($email: String!) {
35+ resendVerificationEmail(email: $email) {
36+ success
37+ message
38+ }
39+ }
40+ ` ;
41+
3442export function Signup ( ) {
3543 const navigate = useNavigate ( ) ;
36- const { login : setAuthUser } = useAuth ( ) ;
37-
44+
3845 const [ formData , setFormData ] = useState ( {
3946 email : '' ,
4047 username : '' ,
4148 password : '' ,
4249 confirmPassword : '' ,
4350 name : ''
4451 } ) ;
45-
52+
4653 const [ showPassword , setShowPassword ] = useState ( false ) ;
4754 const [ showConfirmPassword , setShowConfirmPassword ] = useState ( false ) ;
4855 const [ errors , setErrors ] = useState < Record < string , string > > ( { } ) ;
4956 const [ isChecking , setIsChecking ] = useState < Record < string , boolean > > ( { } ) ;
5057 const [ availability , setAvailability ] = useState < Record < string , boolean > > ( { } ) ;
5158 const [ emailValid , setEmailValid ] = useState < boolean | null > ( null ) ;
5259 const [ passwordsMatch , setPasswordsMatch ] = useState < boolean | null > ( null ) ;
60+ const [ signupComplete , setSignupComplete ] = useState ( false ) ;
61+ const [ resendLoading , setResendLoading ] = useState ( false ) ;
62+ const [ resendMessage , setResendMessage ] = useState ( '' ) ;
5363
5464 const [ signup , { loading } ] = useMutation ( SIGNUP_MUTATION , {
5565 onCompleted : ( data ) => {
56- // Store token in localStorage
57- localStorage . setItem ( 'authToken' , data . signup . token ) ;
58- localStorage . setItem ( 'currentUser' , JSON . stringify ( data . signup . user ) ) ;
59-
60- // Navigate to main app
61- navigate ( '/' ) ;
66+ setSignupComplete ( true ) ;
6267 } ,
6368 onError : ( error ) => {
6469 setErrors ( { submit : error . message } ) ;
6570 }
6671 } ) ;
6772
73+ const [ resendVerificationEmail ] = useMutation ( RESEND_VERIFICATION_EMAIL , {
74+ onCompleted : ( data ) => {
75+ setResendLoading ( false ) ;
76+ if ( data . resendVerificationEmail . success ) {
77+ setResendMessage ( 'Verification email sent! Check your inbox.' ) ;
78+ } else {
79+ setResendMessage ( data . resendVerificationEmail . message || 'Failed to send email. Please try again.' ) ;
80+ }
81+ } ,
82+ onError : ( ) => {
83+ setResendLoading ( false ) ;
84+ setResendMessage ( 'Failed to send email. Please try again.' ) ;
85+ }
86+ } ) ;
87+
6888 const checkAvailability = async ( field : 'email' | 'username' , value : string ) => {
6989 if ( ! value ) return ;
7090
@@ -198,15 +218,27 @@ export function Signup() {
198218 }
199219 } ;
200220
221+ const handleResendVerificationEmail = async ( ) => {
222+ setResendLoading ( true ) ;
223+ setResendMessage ( '' ) ;
224+
225+ await resendVerificationEmail ( {
226+ variables : {
227+ email : formData . email
228+ }
229+ } ) ;
230+ } ;
231+
201232 useEffect ( ( ) => {
202233 const handleKeyPress = ( e : KeyboardEvent ) => {
203234 if ( e . key === 'Enter' && ! loading && ! Object . values ( isChecking ) . some ( checking => checking ) ) {
204- handleSubmit ( e as any ) ;
235+ const submitEvent = e as unknown as React . FormEvent ;
236+ void handleSubmit ( submitEvent ) ;
205237 }
206238 } ;
207239 window . addEventListener ( 'keydown' , handleKeyPress ) ;
208240 return ( ) => window . removeEventListener ( 'keydown' , handleKeyPress ) ;
209- } , [ formData , loading , isChecking ] ) ;
241+ } , [ formData , loading , isChecking , handleSubmit ] ) ;
210242
211243 const passwordStrength = getPasswordStrength ( formData . password ) ;
212244
@@ -225,8 +257,64 @@ export function Signup() {
225257 < p className = "text-gray-400 text-lg" > Join the decentralized project management revolution</ p >
226258 </ div >
227259
228- { /* Signup Form */ }
229- < form onSubmit = { handleSubmit } 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" >
260+ { /* Email Verification Screen or Signup Form */ }
261+ { signupComplete ? (
262+ < 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" >
263+ < div className = "p-6 bg-teal-900/20 border border-teal-500/30 rounded-xl" >
264+ < div className = "flex items-center justify-center mb-4" >
265+ < Mail className = "h-16 w-16 text-teal-400" />
266+ </ div >
267+ < h3 className = "text-2xl font-semibold text-teal-300 text-center mb-3" > Check Your Email!</ h3 >
268+ < p className = "text-sm text-teal-200/80 text-center mb-4" >
269+ We've sent a verification link to < strong > { formData . email } </ strong >
270+ </ p >
271+ < p className = "text-xs text-teal-300/60 text-center mb-6" >
272+ Click the link in the email to verify your account and complete registration. The link expires in 24 hours.
273+ </ p >
274+
275+ < button
276+ type = "button"
277+ onClick = { handleResendVerificationEmail }
278+ disabled = { resendLoading }
279+ className = "w-full bg-gray-700/50 hover:bg-gray-600/80 backdrop-blur-sm border border-gray-600/50 hover:border-teal-500 text-teal-400 font-semibold py-3 px-6 rounded-xl transition-all duration-200 hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 flex items-center justify-center space-x-2"
280+ >
281+ { resendLoading ? (
282+ < >
283+ < div className = "animate-spin rounded-full h-5 w-5 border-b-2 border-teal-400" > </ div >
284+ < span > Sending...</ span >
285+ </ >
286+ ) : (
287+ < >
288+ < Mail className = "h-5 w-5" />
289+ < span > Resend Verification Email</ span >
290+ </ >
291+ ) }
292+ </ button >
293+
294+ { resendMessage && (
295+ < p className = { `mt-3 text-xs text-center ${ resendMessage . includes ( 'sent' ) ? 'text-teal-400' : 'text-red-400' } ` } >
296+ { resendMessage }
297+ </ p >
298+ ) }
299+ </ div >
300+
301+ < div className = "p-4 bg-blue-900/20 border border-blue-500/30 rounded-lg" >
302+ < p className = "text-xs text-blue-300 text-center" >
303+ < strong > Didn't receive the email?</ strong > Check your spam folder or click the button above to resend.
304+ </ p >
305+ </ div >
306+
307+ < div className = "text-center" >
308+ < Link
309+ to = "/login"
310+ className = "text-gray-300 hover:text-teal-400 transition-colors text-sm"
311+ >
312+ Back to login
313+ </ Link >
314+ </ div >
315+ </ div >
316+ ) : (
317+ < form onSubmit = { handleSubmit } 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" >
230318 { /* Social Signup Buttons */ }
231319 < div className = "grid grid-cols-3 gap-3" >
232320 < button
@@ -505,26 +593,30 @@ export function Signup() {
505593 and contribute to the collective intelligence.
506594 </ p >
507595 </ form >
596+ ) }
597+
598+ { ! signupComplete && (
599+ < >
600+ { /* Login Link */ }
601+ < div className = "mt-6 text-center" >
602+ < p className = "text-gray-300" >
603+ Already have an account?{ ' ' }
604+ < Link to = "/login" className = "text-teal-400 hover:text-teal-300 font-medium" >
605+ Sign in
606+ </ Link >
607+ </ p >
608+ </ div >
508609
509-
510- { /* Login Link */ }
511- < div className = "mt-6 text-center" >
512- < p className = "text-gray-300" >
513- Already have an account?{ ' ' }
514- < Link to = "/login" className = "text-teal-400 hover:text-teal-300 font-medium" >
515- Sign in
516- </ Link >
517- </ p >
518- </ div >
519-
520- { /* Role Information */ }
521- < div className = "mt-8 p-4 bg-gray-800/50 backdrop-blur-xl border border-gray-700/50 rounded-2xl shadow-lg" >
522- < h3 className = "text-sm font-semibold text-gray-100 mb-2" > Your Journey Begins as a Viewer</ h3 >
523- < p className = "text-xs text-gray-400" >
524- All new members start with read-only access. As you contribute and demonstrate value,
525- the community may elevate your role to User or even Admin.
526- </ p >
527- </ div >
610+ { /* Role Information */ }
611+ < div className = "mt-8 p-4 bg-gray-800/50 backdrop-blur-xl border border-gray-700/50 rounded-2xl shadow-lg" >
612+ < h3 className = "text-sm font-semibold text-gray-100 mb-2" > Your Journey Begins as a Viewer</ h3 >
613+ < p className = "text-xs text-gray-400" >
614+ All new members start with read-only access. As you contribute and demonstrate value,
615+ the community may elevate your role to User or even Admin.
616+ </ p >
617+ </ div >
618+ </ >
619+ ) }
528620 </ div >
529621
530622 { /* TLS/SSL Status Indicator */ }
0 commit comments