Skip to content

Commit ca5a2fe

Browse files
committed
feat: edit profile modal
1 parent ff3e5d6 commit ca5a2fe

File tree

8 files changed

+304
-123
lines changed

8 files changed

+304
-123
lines changed

backend/src/routes/profile.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,7 @@ router.get("/", async (req, res) => {
2525
res.status(500).json({ success: false, message: "User not found!" });
2626
return;
2727
}
28-
res.status(200).json({
29-
name,
30-
email,
31-
pfpUrl,
32-
cfHandle,
33-
cfRating: userData.cfRating,
34-
});
28+
res.status(200).json(userData);
3529
} catch (err: any) {
3630
res.status(500).json({ success: false, message: err.message });
3731
}
35.9 KB
Loading
36.8 KB
Loading
19.7 KB
Loading

frontend/public/logos/leetcode.png

39.8 KB
Loading
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { useState } from "react";
2+
import { X, Loader, CheckCircle } from "lucide-react";
3+
import axios from "axios";
4+
5+
interface EditProfileModalProps {
6+
initialValues: {
7+
pfpUrl: string;
8+
atcoderHandle: string;
9+
leetcodeHandle: string;
10+
codechefHandle: string;
11+
};
12+
onClose: () => void;
13+
onSuccess: (updatedUser: any) => void;
14+
}
15+
16+
export default function EditProfileModal({
17+
initialValues,
18+
onClose,
19+
onSuccess,
20+
}: EditProfileModalProps) {
21+
const [formData, setFormData] = useState(initialValues);
22+
const [loading, setLoading] = useState(false);
23+
const [error, setError] = useState<string | null>(null);
24+
const [success, setSuccess] = useState(false);
25+
26+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
27+
const { name, value } = e.target;
28+
setFormData((prev) => ({ ...prev, [name]: value }));
29+
};
30+
31+
const handleSubmit = async (e: React.FormEvent) => {
32+
e.preventDefault();
33+
setLoading(true);
34+
setError(null);
35+
36+
try {
37+
await axios.patch(
38+
`${import.meta.env.VITE_API_BASE_URL}/profile/edit`,
39+
formData,
40+
{ withCredentials: true }
41+
);
42+
43+
const res = await axios.get(
44+
`${import.meta.env.VITE_API_BASE_URL}/profile`,
45+
{ withCredentials: true }
46+
);
47+
48+
setSuccess(true);
49+
setTimeout(() => {
50+
onSuccess(res.data);
51+
}, 1500);
52+
} catch (err: any) {
53+
setError(err.response?.data?.message || "Failed to update profile.");
54+
} finally {
55+
setLoading(false);
56+
}
57+
};
58+
59+
return (
60+
<div className="fixed inset-0 bg-dark-background flex items-center justify-center z-50 shadow-5xl">
61+
<div className="bg-highlight-dark text-white p-6 rounded-lg w-[90%] max-w-lg shadow-xl relative">
62+
<button
63+
className="absolute top-3 right-3 text-gray-400 hover:text-white hover:cursor-pointer"
64+
onClick={onClose}
65+
>
66+
<X size={20} />
67+
</button>
68+
69+
<h2 className="text-xl mb-6 font-semibold">Edit Your Profile</h2>
70+
71+
{success ? (
72+
<div className="flex flex-col items-center text-center">
73+
<CheckCircle className="w-12 h-12 text-green-400 mb-4" />
74+
<p className="text-lg font-semibold text-green-300">
75+
Profile updated successfully!
76+
</p>
77+
<p className="text-sm text-gray-300 mt-2">Closing shortly...</p>
78+
</div>
79+
) : (
80+
<form onSubmit={handleSubmit} className="space-y-4">
81+
<InputField
82+
label="Profile Picture URL"
83+
name="pfpUrl"
84+
value={formData.pfpUrl}
85+
onChange={handleChange}
86+
/>
87+
<InputField
88+
label="AtCoder Handle"
89+
name="atcoderHandle"
90+
value={formData.atcoderHandle}
91+
onChange={handleChange}
92+
/>
93+
<InputField
94+
label="LeetCode Handle"
95+
name="leetcodeHandle"
96+
value={formData.leetcodeHandle}
97+
onChange={handleChange}
98+
/>
99+
<InputField
100+
label="CodeChef Handle"
101+
name="codechefHandle"
102+
value={formData.codechefHandle}
103+
onChange={handleChange}
104+
/>
105+
106+
{error && <p className="text-red-400 text-sm">{error}</p>}
107+
108+
<button
109+
type="submit"
110+
className="bg-highlight-light hover:bg-gray-700 hover:cursor-pointer text-white px-4 py-2 rounded w-full transition"
111+
disabled={loading}
112+
>
113+
{loading ? (
114+
<span className="flex items-center justify-center gap-2">
115+
<Loader className="w-4 h-4 animate-spin" /> Saving...
116+
</span>
117+
) : (
118+
"Save Changes"
119+
)}
120+
</button>
121+
</form>
122+
)}
123+
</div>
124+
</div>
125+
);
126+
}
127+
128+
function InputField({
129+
label,
130+
name,
131+
value,
132+
onChange,
133+
}: {
134+
label: string;
135+
name: string;
136+
value: string;
137+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
138+
}) {
139+
return (
140+
<div>
141+
<label className="block text-sm text-gray-400 mb-1" htmlFor={name}>
142+
{label}
143+
</label>
144+
<input
145+
id={name}
146+
name={name}
147+
value={value}
148+
onChange={onChange}
149+
className="w-full px-3 py-2 bg-dark-background text-white rounded border border-gray-700 focus:outline-none focus:ring-2 focus:ring-highlight-light"
150+
/>
151+
</div>
152+
);
153+
}

0 commit comments

Comments
 (0)