@@ -51,8 +51,8 @@ const SignUp: React.FC = () => {
5151 if ( name === "username" ) {
5252 if ( ! value . trim ( ) ) {
5353 errorMessage = "Username is required" ;
54- } else if ( ! / ^ [ A - Z a - z \s ] + $ / . test ( value ) ) {
55- errorMessage = "Only letters are allowed " ;
54+ } else if ( ! / ^ [ A - Z a - z 0 - 9 ] (?: [ A - Z a - z 0 - 9 - ] * [ A - Z a - z 0 - 9 ] ) ? $ / . test ( value ) ) {
55+ errorMessage = "Enter a valid GitHub username " ;
5656 }
5757 }
5858
@@ -67,8 +67,11 @@ const SignUp: React.FC = () => {
6767 if ( name === "password" ) {
6868 if ( ! value . trim ( ) ) {
6969 errorMessage = "Password is required" ;
70- } else if ( ! / ^ (? = .* [ a - z ] ) (? = .* [ A - Z ] ) (? = .* \d ) (? = .* [ @ $ ! % * ? & ] ) [ A - Z a - z \d @ $ ! % * ? & ] { 8 , } $ / . test ( value ) ) {
71- errorMessage = "Password must contain uppercase, lowercase, number, and special character" ;
70+ } else if (
71+ ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test ( value )
72+ ) {
73+ errorMessage =
74+ "Password must be 8+ characters with letters and numbers" ;
7275 }
7376 }
7477
@@ -80,8 +83,8 @@ const SignUp: React.FC = () => {
8083
8184 const usernameError = ! formData . username . trim ( )
8285 ? "Username is required"
83- : ! / ^ [ A - Z a - z \s ] + $ / . test ( formData . username )
84- ? "Only letters are allowed "
86+ : ! / ^ [ A - Z a - z 0 - 9 ] (?: [ A - Z a - z 0 - 9 - ] * [ A - Z a - z 0 - 9 ] ) ? $ / . test ( formData . username )
87+ ? "Enter a valid GitHub username "
8588 : "" ;
8689 const emailError = ! formData . email . trim ( )
8790 ? "Email is required"
@@ -90,30 +93,37 @@ const SignUp: React.FC = () => {
9093 : "" ;
9194 const passwordError = ! formData . password . trim ( )
9295 ? "Password is required"
93- : ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test ( formData . password )
96+ : ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test (
97+ formData . password ,
98+ )
9499 ? "Password must be 8+ characters with letters and numbers"
95100 : "" ;
96-
97101 if ( usernameError || emailError || passwordError ) {
98- setErrors ( { username : usernameError , email : emailError , password : passwordError } ) ;
102+ setErrors ( {
103+ username : usernameError ,
104+ email : emailError ,
105+ password : passwordError ,
106+ } ) ;
99107 return ;
100108 }
101109
102110 setIsLoading ( true ) ;
103111 try {
104- const response = await axios . post ( `${ backendUrl } /api/auth/signup` , formData , { withCredentials : true } ) ;
105- setMessage ( response . data . message ) ;
112+ const response = await axios . post (
113+ `${ backendUrl } /api/auth/signup` ,
114+ formData , // Include cookies for session
115+ ) ;
116+ setMessage ( response . data . message ) ; // Show success message from backend
106117
107118 // Navigate to login page after successful signup
108- if ( response . status === 201 ) {
119+ if ( response . data . message === "User created successfully" ) {
109120 navigate ( "/login" ) ;
110121 }
111- } catch ( error : unknown ) {
112- if ( axios . isAxiosError ( error ) ) {
113- setMessage ( error . response ?. data ?. message || "Something went wrong. Please try again." ) ;
114- } else {
115- setMessage ( "Something went wrong. Please try again." ) ;
116- }
122+ } catch ( error : any ) {
123+ setMessage (
124+ error . response ?. data ?. message ||
125+ "Something went wrong. Please try again." ,
126+ ) ;
117127 } finally {
118128 setIsLoading ( false ) ;
119129 }
@@ -135,105 +145,176 @@ const SignUp: React.FC = () => {
135145 </ p >
136146 }
137147 >
138- < div className = "space-y-8" >
139- < div className = "space-y-3 text-center sm:text-left" >
140- < div className = "inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.22em] text-cyan-300/90" >
141- < Sparkles className = "h-3.5 w-3.5" />
142- New account
143- </ div >
144- < div >
145- < h2 className = { `text-3xl font-semibold tracking-tight ${ mode === "dark" ? "text-white" : "text-slate-950" } ` } >
146- Build your profile
147- </ h2 >
148- < p className = { `mt-2 text-sm leading-6 ${ mode === "dark" ? "text-slate-300" : "text-slate-600" } ` } >
149- Use a simple, guided signup flow with instant feedback while you type.
150- </ p >
148+ < div className = "absolute inset-0" >
149+ < div
150+ className = { `absolute -top-40 -right-40 w-96 h-96 ${
151+ mode === "dark" ? "bg-purple-500" : "bg-purple-300"
152+ } rounded-full blur-3xl opacity-30 animate-pulse`}
153+ />
154+ < div
155+ className = { `absolute -bottom-40 -left-40 w-96 h-96 ${
156+ mode === "dark" ? "bg-blue-500" : "bg-blue-300"
157+ } rounded-full blur-3xl opacity-30 animate-pulse`}
158+ />
159+ < div
160+ className = { `absolute top-40 left-40 w-96 h-96 ${
161+ mode === "dark" ? "bg-pink-500" : "bg-pink-300"
162+ } rounded-full blur-3xl opacity-30 animate-pulse`}
163+ />
164+ < div
165+ className = { `absolute top-1/2 right-1/4 w-64 h-64 ${
166+ mode === "dark" ? "bg-indigo-500" : "bg-indigo-300"
167+ } rounded-full blur-2xl opacity-20 animate-pulse delay-1000`}
168+ />
169+ </ div >
170+
171+ < div className = "relative w-full max-w-md px-4 sm:px-6" >
172+ < motion . div
173+ initial = { { opacity : 0 , y : - 20 } }
174+ animate = { { opacity : 1 , y : 0 } }
175+ transition = { { duration : 0.6 } }
176+ className = "text-center mb-10"
177+ >
178+ < div className = "inline-flex items-center justify-center w-20 h-20 bg-white rounded-3xl mb-6 shadow-2xl transform hover:scale-105 transition-transform duration-300 overflow-hidden" >
179+ < img
180+ src = "/crl-icon.png"
181+ alt = "Logo"
182+ className = "w-14 h-14 object-contain"
183+ />
151184 </ div >
152- </ div >
153-
154- < form onSubmit = { handleSubmit } className = "space-y-5" >
155- < label className = "block space-y-2" >
156- < span className = { `text-sm font-medium ${ mode === "dark" ? "text-slate-200" : "text-slate-700" } ` } > Username</ span >
157- < div className = { `flex items-center gap-3 rounded-2xl border px-4 py-3 transition focus-within:ring-2 ${ mode === "dark" ? "border-white/10 bg-white/5 focus-within:ring-cyan-400/50" : "border-slate-200 bg-slate-50 focus-within:ring-cyan-500/30" } ` } >
158- < User className = { `h-5 w-5 shrink-0 ${ mode === "dark" ? "text-slate-400" : "text-slate-500" } ` } />
159- < input
160- type = "text"
161- name = "username"
162- placeholder = "Your display name"
163- value = { formData . username }
164- onChange = { handleChange }
165- required
166- className = { `w-full bg-transparent text-sm outline-none ${ mode === "dark" ? "text-white placeholder-slate-500" : "text-slate-900 placeholder-slate-400" } ` }
167- />
185+ < h1
186+ className = { `text-4xl font-bold mb-2 ${ mode === "dark" ? "text-white" : "text-black" } ` }
187+ >
188+ GitHubTracker
189+ </ h1 >
190+ < p
191+ className = { `text-lg font-medium ${ mode === "dark" ? "text-slate-300" : "text-gray-700" } ` }
192+ >
193+ Join your GitHub journey
194+ </ p >
195+ </ motion . div >
196+
197+ < motion . div
198+ initial = { { opacity : 0 , y : 30 } }
199+ animate = { { opacity : 1 , y : 0 } }
200+ transition = { { duration : 0.6 , delay : 0.2 } }
201+ className = { `rounded-3xl p-6 sm:p-10 shadow-2xl border ${
202+ mode === "dark"
203+ ? "bg-white/10 backdrop-blur-xl border-white/20 text-white"
204+ : "bg-white border-gray-200 text-black"
205+ } `}
206+ >
207+ < h2
208+ className = { `text-2xl font-bold text-center mb-8 ${
209+ mode === "dark" ? "text-white" : "text-gray-800"
210+ } `}
211+ >
212+ Create Account
213+ </ h2 >
214+
215+ < form onSubmit = { handleSubmit } className = "space-y-6" >
216+ < div >
217+ < div className = "relative" >
218+ < div className = "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none" >
219+ < User className = "h-5 w-5 text-gray-400" />
220+ </ div >
221+ < input
222+ type = "text"
223+ name = "username"
224+ placeholder = "Enter your username"
225+ value = { formData . username }
226+ onChange = { handleChange }
227+ required
228+ className = { `w-full pl-12 pr-4 py-4 rounded-2xl border focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300 ${ mode === "dark" ? "bg-white/10 border-white/20 text-white placeholder-gray-400" : "bg-gray-100 border-gray-300 text-black placeholder-gray-400" } ` }
229+ />
230+ </ div >
231+ { errors . username && (
232+ < p className = "text-red-500 text-sm mt-2" > { errors . username } </ p >
233+ ) }
168234 </ div >
169- { errors . username && < p className = "text-sm text-rose-600 dark:text-rose-300" > { errors . username } </ p > }
170- </ label >
171235
172- < label className = "block space-y-2" >
173- < span className = { `text-sm font-medium ${ mode === "dark" ? "text-slate-200" : "text-slate-700" } ` } > Email address</ span >
174- < div className = { `flex items-center gap-3 rounded-2xl border px-4 py-3 transition focus-within:ring-2 ${ mode === "dark" ? "border-white/10 bg-white/5 focus-within:ring-cyan-400/50" : "border-slate-200 bg-slate-50 focus-within:ring-cyan-500/30" } ` } >
175- < Mail className = { `h-5 w-5 shrink-0 ${ mode === "dark" ? "text-slate-400" : "text-slate-500" } ` } />
176- < input
177- type = "email"
178- name = "email"
179- placeholder = "name@example.com"
180- value = { formData . email }
181- onChange = { handleChange }
182- required
183- className = { `w-full bg-transparent text-sm outline-none ${ mode === "dark" ? "text-white placeholder-slate-500" : "text-slate-900 placeholder-slate-400" } ` }
184- />
236+ < div >
237+ < div className = "relative" >
238+ < div className = "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none" >
239+ < Mail className = "h-5 w-5 text-gray-400" />
240+ </ div >
241+ < input
242+ type = "email"
243+ name = "email"
244+ placeholder = "Enter your email"
245+ value = { formData . email }
246+ onChange = { handleChange }
247+ required
248+ className = { `w-full pl-12 pr-4 py-4 rounded-2xl border focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300 ${ mode === "dark" ? "bg-white/10 border-white/20 text-white placeholder-gray-400" : "bg-gray-100 border-gray-300 text-black placeholder-gray-400" } ` }
249+ />
250+ </ div >
251+ { errors . email && (
252+ < p className = "text-red-500 text-sm mt-2" > { errors . email } </ p >
253+ ) }
185254 </ div >
186- { errors . email && < p className = "text-sm text-rose-600 dark:text-rose-300" > { errors . email } </ p > }
187- </ label >
188255
189- < label className = "block space-y-2" >
190- < span className = { `text-sm font-medium ${ mode === "dark" ? "text-slate-200" : "text-slate-700" } ` } > Password</ span >
191- < div className = { `flex items-center gap-3 rounded-2xl border px-4 py-3 transition focus-within:ring-2 ${ mode === "dark" ? "border-white/10 bg-white/5 focus-within:ring-cyan-400/50" : "border-slate-200 bg-slate-50 focus-within:ring-cyan-500/30" } ` } >
192- < Lock className = { `h-5 w-5 shrink-0 ${ mode === "dark" ? "text-slate-400" : "text-slate-500" } ` } />
193- < input
194- type = { showPassword ? "text" : "password" }
195- name = "password"
196- placeholder = "At least 8 characters"
197- value = { formData . password }
198- onChange = { handleChange }
199- required
200- className = { `w-full bg-transparent text-sm outline-none ${ mode === "dark" ? "text-white placeholder-slate-500" : "text-slate-900 placeholder-slate-400" } ` }
201- />
202- < button
203- type = "button"
204- onClick = { ( ) => setShowPassword ( ( previous ) => ! previous ) }
205- aria-label = { showPassword ? "Hide password" : "Show password" }
206- aria-pressed = { showPassword }
207- className = { `shrink-0 transition-colors ${ mode === "dark" ? "text-slate-400 hover:text-white" : "text-slate-500 hover:text-slate-900" } ` }
208- >
209- { showPassword ? < EyeOff className = "h-5 w-5" /> : < Eye className = "h-5 w-5" /> }
210- </ button >
256+ < div >
257+ < div className = "relative" >
258+ < div className = "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none" >
259+ < Lock className = "h-5 w-5 text-gray-400" />
260+ </ div >
261+ < input
262+ type = { showPassword ? "text" : "password" }
263+ name = "password"
264+ placeholder = "Enter your password"
265+ value = { formData . password }
266+ onChange = { handleChange }
267+ required
268+ className = { `w-full pl-12 pr-12 py-4 rounded-2xl border focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300 ${ mode === "dark" ? "bg-white/10 border-white/20 text-white placeholder-gray-400" : "bg-gray-100 border-gray-300 text-black placeholder-gray-400" } ` }
269+ />
270+ < button
271+ type = "button"
272+ onClick = { ( ) => setShowPassword ( ! showPassword ) }
273+ aria-label = { showPassword ? "Hide password" : "Show password" }
274+ aria-pressed = { showPassword }
275+ className = { `absolute inset-y-0 right-0 pr-4 flex items-center transition-colors duration-200 ${ mode === "dark" ? "text-slate-400 hover:text-white" : "text-gray-500 hover:text-gray-800" } ` }
276+ >
277+ { showPassword ? (
278+ < EyeOff className = "h-5 w-5" />
279+ ) : (
280+ < Eye className = "h-5 w-5" />
281+ ) }
282+ </ button >
283+ </ div >
284+ { errors . password && (
285+ < p className = "text-red-500 text-sm mt-2" > { errors . password } </ p >
286+ ) }
211287 </ div >
212288 { errors . password && < p className = "text-sm text-rose-600 dark:text-rose-300" > { errors . password } </ p > }
213289 </ label >
214290
215- < button
216- type = "submit"
217- disabled = { isLoading }
218- className = "inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-cyan-500 via-blue-500 to-indigo-600 px-6 py-4 text-sm font-semibold text-white shadow-lg shadow-cyan-500/20 transition duration-300 hover:-translate-y-0.5 hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-60"
219- >
220- { isLoading ? "Creating account..." : "Create account" }
221- { ! isLoading && < ArrowRight className = "h-4 w-4" /> }
222- </ button >
223- </ form >
224-
225- { message && (
226- < div
227- className = { `rounded-2xl border px-4 py-3 text-sm ${
228- message . includes ( "successfully" )
229- ? "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/30 dark:bg-emerald-500/10 dark:text-emerald-300"
230- : "border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-300"
231- } `}
232- >
233- < div className = "flex items-start gap-2" >
234- < ShieldCheck className = "mt-0.5 h-4 w-4 shrink-0" />
235- < p > { message } </ p >
291+ < button
292+ type = "submit"
293+ disabled = { isLoading }
294+ className = "w-full bg-gradient-to-r from-purple-600 via-pink-600 to-indigo-600 text-white py-4 px-6 rounded-2xl font-semibold focus:ring-4 focus:ring-purple-500/50 transition-all duration-300 hover:scale-[1.02] hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed"
295+ >
296+ { isLoading ? "Creating account..." : "Create Account" }
297+ </ button >
298+ </ form >
299+
300+ { message && (
301+ < div
302+ className = { `text-center mt-6 p-3 rounded-xl ${ message . includes ( "successfully" ) ? "text-green-600 bg-green-100" : "text-red-600 bg-red-100" } ` }
303+ >
304+ { message }
236305 </ div >
306+ ) }
307+
308+ < div className = "text-center mt-8" >
309+ < p className = { mode === "dark" ? "text-gray-300" : "text-gray-600" } >
310+ Already have an account?{ " " }
311+ < Link
312+ to = "/login"
313+ className = { `font-medium hover:underline transition-colors duration-300 ${ mode === "dark" ? "text-white" : "text-black" } ` }
314+ >
315+ Sign in here
316+ </ Link >
317+ </ p >
237318 </ div >
238319 )}
239320 </ div >
0 commit comments