1- import React , { useState } from "react" ;
1+ import React , { useState , useContext } from "react" ;
22import axios from "axios" ;
33import { useNavigate , Link } from "react-router-dom" ;
4+ import { motion } from "framer-motion" ;
45import { User , Mail , Lock , Eye , EyeOff } from "lucide-react" ;
6+ import { ThemeContext } from "../../context/ThemeContext" ;
7+ import type { ThemeContextType } from "../../context/ThemeContext" ;
58
69const backendUrl = import . meta. env . VITE_BACKEND_URL ;
710
@@ -24,218 +27,150 @@ const SignUp: React.FC = () => {
2427 password : "" ,
2528 } ) ;
2629 const [ showPassword , setShowPassword ] = useState ( false ) ;
30+ const [ isLoading , setIsLoading ] = useState ( false ) ;
2731 const navigate = useNavigate ( ) ;
32+ const themeContext = useContext ( ThemeContext ) as ThemeContextType ;
33+ const { mode } = themeContext ;
2834
2935 const handleChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
3036 const { name, value } = e . target ;
31-
3237 setFormData ( { ...formData , [ name ] : value } ) ;
33-
3438 let errorMessage = "" ;
35-
3639 if ( name === "username" ) {
3740 if ( ! value . trim ( ) ) {
3841 errorMessage = "Username is required" ;
3942 } else if ( ! / ^ [ A - Z a - z \s ] + $ / . test ( value ) ) {
4043 errorMessage = "Only letters are allowed" ;
4144 }
4245 }
43-
4446 if ( name === "email" ) {
4547 if ( ! value . trim ( ) ) {
4648 errorMessage = "Email is required" ;
4749 } else if ( ! / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( value . trim ( ) ) ) {
4850 errorMessage = "Enter a valid email" ;
4951 }
5052 }
51-
5253 if ( name === "password" ) {
5354 if ( ! value . trim ( ) ) {
5455 errorMessage = "Password is required" ;
55- } else if (
56- ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test ( value )
57- ) {
56+ } else if ( ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test ( value ) ) {
5857 errorMessage = "Password must be 8+ characters with letters and numbers" ;
5958 }
6059 }
61-
62- setErrors ( ( prev ) => ( {
63- ...prev ,
64- [ name ] : errorMessage ,
65- } ) ) ;
60+ setErrors ( ( prev ) => ( { ...prev , [ name ] : errorMessage } ) ) ;
6661 } ;
6762
6863 const handleSubmit = async ( e : React . FormEvent ) => {
6964 e . preventDefault ( ) ;
70-
7165 const usernameError = ! formData . username . trim ( )
7266 ? "Username is required"
7367 : ! / ^ [ A - Z a - z \s ] + $ / . test ( formData . username )
7468 ? "Only letters are allowed"
7569 : "" ;
76-
7770 const emailError = ! formData . email . trim ( )
7871 ? "Email is required"
7972 : ! / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( formData . email . trim ( ) )
8073 ? "Enter a valid email"
8174 : "" ;
82-
8375 const passwordError = ! formData . password . trim ( )
8476 ? "Password is required"
85- : ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test (
86- formData . password
87- )
77+ : ! / ^ (? = .* [ A - Z a - z ] ) (? = .* \d ) [ A - Z a - z \d @ $ ! % * # ? & ] { 8 , } $ / . test ( formData . password )
8878 ? "Password must be 8+ characters with letters and numbers"
8979 : "" ;
90-
9180 if ( usernameError || emailError || passwordError ) {
92- setErrors ( {
93- username : usernameError ,
94- email : emailError ,
95- password : passwordError ,
96- } ) ;
81+ setErrors ( { username : usernameError , email : emailError , password : passwordError } ) ;
9782 return ;
9883 }
99-
84+ setIsLoading ( true ) ;
10085 try {
101- const response = await axios . post (
102- `${ backendUrl } /api/auth/signup` ,
103- formData
104- ) ;
105-
86+ const response = await axios . post ( `${ backendUrl } /api/auth/signup` , formData ) ;
10687 setMessage ( response . data . message ) ;
107-
10888 if ( response . data . message === "User created successfully" ) {
10989 navigate ( "/login" ) ;
11090 }
111- } catch ( error ) {
112- setMessage ( "Something went wrong. Please try again." ) ;
91+ } catch ( error : any ) {
92+ setMessage ( error . response ?. data ?. message || "Something went wrong. Please try again." ) ;
93+ } finally {
94+ setIsLoading ( false ) ;
11395 }
11496 } ;
11597
11698 return (
117- < div className = "relative h-screen w-screen bg-white dark:bg-black flex items-center justify-center px-4 overflow-hidden" >
118- < div className = "relative w-full max-w-md" >
119- { /* Logo and Title */ }
120- < div className = "text-center mb-8" >
99+ < div className = { `relative h-screen w-screen flex items-center justify-center px-4 overflow-hidden ${ mode === "dark" ? "bg-black" : "bg-white" } ` } >
100+ < div className = "relative w-full max-w-md px-6" >
101+ < motion . div initial = { { opacity : 0 , y : - 20 } } animate = { { opacity : 1 , y : 0 } } transition = { { duration : 0.6 } } className = "text-center mb-10" >
121102 < 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" >
122103 < img src = "/crl-icon.png" alt = "Logo" className = "w-14 h-14 object-contain" />
123104 </ div >
124- < h1 className = " text-4xl font-bold text-black dark: text-white mb-2" > GitHubTracker</ h1 >
125- < p className = " text-gray-600 dark: text-gray -300 text-lg" > Join your GitHub journey</ p >
126- </ div >
105+ < h1 className = { ` text-4xl font-bold mb-2 ${ mode === " dark" ? " text-white" : "text-black" } ` } > GitHubTracker</ h1 >
106+ < p className = { ` text-lg font-medium ${ mode === " dark" ? " text-slate -300" : " text-gray-700" } ` } > Join your GitHub journey</ p >
107+ </ motion . div >
127108
128- { /* Sign Up Form */ }
129- < div className = "bg-white dark:bg-gray-900 rounded-3xl p-8 border border-gray-200 dark:border-gray-700 shadow-2xl" >
130- < h2 className = "text-2xl font-semibold text-black dark:text-white text-center mb-8" >
131- Create Account
132- </ h2 >
133- < div className = "space-y-6" >
109+ < motion . div initial = { { opacity : 0 , y : 30 } } animate = { { opacity : 1 , y : 0 } } transition = { { duration : 0.6 , delay : 0.2 } } className = { `rounded-3xl p-10 shadow-2xl border ${ mode === "dark" ? "bg-white/10 backdrop-blur-xl border-white/20 text-white" : "bg-white border-gray-200 text-black" } ` } >
110+ < h2 className = { `text-2xl font-bold text-center mb-8 ${ mode === "dark" ? "text-white" : "text-gray-800" } ` } > Create Account</ h2 >
134111
135- { /* Username */ }
112+ < form onSubmit = { handleSubmit } className = "space-y-6" >
136113 < div >
137114 < div className = "relative" >
138115 < div className = "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none" >
139116 < User className = "h-5 w-5 text-gray-400" />
140117 </ div >
141- < input
142- type = "text"
143- name = "username"
144- placeholder = "Enter your username"
145- value = { formData . username }
146- onChange = { handleChange }
147- required
148- className = "w-full pl-12 pr-4 py-4 bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-2xl text-black dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300"
118+ < input type = "text" name = "username" placeholder = "Enter your username" value = { formData . username } onChange = { handleChange } required
119+ 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" } ` }
149120 />
150121 </ div >
151- { errors . username && (
152- < p className = "text-red-500 text-sm mt-2" > { errors . username } </ p >
153- ) }
122+ { errors . username && < p className = "text-red-500 text-sm mt-2" > { errors . username } </ p > }
154123 </ div >
155124
156- { /* Email */ }
157125 < div >
158126 < div className = "relative" >
159127 < div className = "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none" >
160128 < Mail className = "h-5 w-5 text-gray-400" />
161129 </ div >
162- < input
163- type = "email"
164- name = "email"
165- placeholder = "Enter your email"
166- value = { formData . email }
167- onChange = { handleChange }
168- required
169- className = "w-full pl-12 pr-4 py-4 bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-2xl text-black dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300"
130+ < input type = "email" name = "email" placeholder = "Enter your email" value = { formData . email } onChange = { handleChange } required
131+ 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" } ` }
170132 />
171133 </ div >
172- { errors . email && (
173- < p className = "text-red-500 text-sm mt-2" > { errors . email } </ p >
174- ) }
134+ { errors . email && < p className = "text-red-500 text-sm mt-2" > { errors . email } </ p > }
175135 </ div >
176136
177- { /* Password */ }
178137 < div >
179138 < div className = "relative" >
180139 < div className = "absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none" >
181140 < Lock className = "h-5 w-5 text-gray-400" />
182141 </ div >
183- < input
184- type = { showPassword ? "text" : "password" }
185- name = "password"
186- placeholder = "Enter your password"
187- value = { formData . password }
188- onChange = { handleChange }
189- required
190- className = "w-full pl-12 pr-4 py-4 bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-2xl text-black dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent transition-all duration-300"
142+ < input type = { showPassword ? "text" : "password" } name = "password" placeholder = "Enter your password" value = { formData . password } onChange = { handleChange } required
143+ 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" } ` }
191144 />
192- < button
193- type = "button"
194- onClick = { ( ) => setShowPassword ( ! showPassword ) }
195- aria-label = { showPassword ? "Hide password" : "Show password" }
196- aria-pressed = { showPassword }
197- className = "absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
198- >
199- { showPassword ? < EyeOff size = { 20 } /> : < Eye size = { 20 } /> }
145+ < button type = "button" onClick = { ( ) => setShowPassword ( ! showPassword ) } aria-label = { showPassword ? "Hide password" : "Show password" } aria-pressed = { showPassword }
146+ 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" } ` } >
147+ { showPassword ? < EyeOff className = "h-5 w-5" /> : < Eye className = "h-5 w-5" /> }
200148 </ button >
201149 </ div >
202- { errors . password && (
203- < p className = "text-red-500 text-sm mt-2" > { errors . password } </ p >
204- ) }
150+ { errors . password && < p className = "text-red-500 text-sm mt-2" > { errors . password } </ p > }
205151 </ div >
206152
207- { /* Submit Button */ }
208- < button
209- onClick = { handleSubmit }
210- className = "w-full bg-black dark:bg-white text-white dark:text-black font-semibold py-4 rounded-2xl hover:bg-gray-800 dark:hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-400 transition-all duration-300 shadow-lg"
211- >
212- Create Account
153+ < button type = "submit" disabled = { isLoading }
154+ className = { `w-full font-semibold py-4 rounded-2xl focus:outline-none focus:ring-2 focus:ring-gray-400 transition-all duration-300 shadow-lg ${ mode === "dark" ? "bg-white text-black hover:bg-gray-100" : "bg-black text-white hover:bg-gray-800" } ${ isLoading ? "opacity-60 cursor-not-allowed" : "" } ` } >
155+ { isLoading ? "Creating Account..." : "Create Account" }
213156 </ button >
214- </ div >
157+ </ form >
215158
216159 { message && (
217- < div
218- className = { `text-center mt-6 p-3 rounded-xl ${
219- message . includes ( "successfully" )
220- ? "text-green-600 bg-green-100 dark:text-green-400 dark:bg-green-900/20"
221- : "text-red-600 bg-red-100 dark:text-red-400 dark:bg-red-900/20"
222- } `}
223- >
160+ < div className = { `text-center mt-6 p-3 rounded-xl ${ message . includes ( "successfully" ) ? "text-green-600 bg-green-100" : "text-red-600 bg-red-100" } ` } >
224161 { message }
225162 </ div >
226163 ) }
227164
228165 < div className = "text-center mt-8" >
229- < p className = " text-gray-600 dark: text-gray-300" >
166+ < p className = { mode === "dark" ? " text-gray-300" : " text-gray-600" } >
230167 Already have an account?{ " " }
231- < Link to = "/login" className = "inline-flex items-center" >
232- < button className = "text-black dark:text-white hover:underline font-medium transition-colors duration-300" >
233- Sign in here
234- </ button >
168+ < Link to = "/login" className = { `font-medium hover:underline transition-colors duration-300 ${ mode === "dark" ? "text-white" : "text-black" } ` } >
169+ Sign in here
235170 </ Link >
236171 </ p >
237172 </ div >
238- </ div >
173+ </ motion . div >
239174 </ div >
240175 </ div >
241176 ) ;
0 commit comments