Skip to content

Commit cdfd4ad

Browse files
committed
fix: resolve conflicts, add ThemeContext, fix validation and accessibility
1 parent aa21afb commit cdfd4ad

1 file changed

Lines changed: 47 additions & 112 deletions

File tree

src/pages/Signup/Signup.tsx

Lines changed: 47 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useContext } from "react";
22
import axios from "axios";
33
import { useNavigate, Link } from "react-router-dom";
4+
import { motion } from "framer-motion";
45
import { User, Mail, Lock, Eye, EyeOff } from "lucide-react";
6+
import { ThemeContext } from "../../context/ThemeContext";
7+
import type { ThemeContextType } from "../../context/ThemeContext";
58

69
const 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-Za-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-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(value)
57-
) {
56+
} else if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-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-Za-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-Za-z])(?=.*\d)[A-Za-z\d@$!%*#?&]{8,}$/.test(
86-
formData.password
87-
)
77+
: !/^(?=.*[A-Za-z])(?=.*\d)[A-Za-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

Comments
 (0)