source: frontend/src/pages/Register.tsx@ ed8f998

main
Last change on this file since ed8f998 was 92db381, checked in by Filip Gavrilovski <filipgavrilovski28@…>, 5 months ago

style changes on login and register pages

  • Property mode set to 100644
File size: 10.9 KB
Line 
1import { useEffect, useState } from "react";
2import { Link, useNavigate } from "react-router-dom";
3import { toast } from "react-toastify";
4import axiosInstance, { scheduleTokenRefresh } from "../api/axiosInstance";
5import { useAuth } from "../context/authContext";
6import type { User, UserRegisterType } from "../utils/types";
7
8const Register = () => {
9 const { setUser } = useAuth();
10 const [username, setUsername] = useState("");
11 const [password, setPassword] = useState("");
12 const [fullname, setFullname] = useState("");
13 const [userType, setUserType] = useState<UserRegisterType[]>([]);
14 const [email, setEmail] = useState("");
15 const [profilePhotoFile, setProfilePhotoFile] = useState<File | null>(null);
16 const [previewProfilePhoto, setPreviewProfilePhoto] = useState<string | null>(
17 null,
18 );
19 const [error, setError] = useState<string | null>(null);
20
21 const navigate = useNavigate();
22
23 useEffect(() => {
24 return () => {
25 if (previewProfilePhoto) {
26 URL.revokeObjectURL(previewProfilePhoto);
27 }
28 };
29 }, [previewProfilePhoto]);
30
31 const handleRemoveFile = () => {
32 if (previewProfilePhoto) {
33 URL.revokeObjectURL(previewProfilePhoto);
34 }
35 setProfilePhotoFile(null);
36 setPreviewProfilePhoto(null);
37 };
38
39 const handleRegister = async (e: React.FormEvent) => {
40 e.preventDefault();
41 if (profilePhotoFile && profilePhotoFile.size > 5 * 1024 * 1024) {
42 toast.error("Max file size is 5MB");
43 return;
44 }
45
46 if (
47 username === "" ||
48 password === "" ||
49 fullname === "" ||
50 email === "" ||
51 userType.length === 0
52 ) {
53 setError("Please fill in all required fields.");
54 return;
55 }
56 if (profilePhotoFile && !profilePhotoFile.type.startsWith("image/")) {
57 toast.error("Only images allowed");
58 return;
59 }
60 const formData = new FormData();
61 if (profilePhotoFile) formData.append("profilePhoto", profilePhotoFile);
62 formData.append("username", username);
63 formData.append("fullname", fullname);
64 formData.append("email", email);
65 formData.append("password", password);
66 userType.forEach((type) => formData.append("userType", type));
67
68 try {
69 const response = await axiosInstance.post<{
70 user: User;
71 tokenExpiresIn: number;
72 }>("/auth/register", formData);
73 scheduleTokenRefresh(response.data.tokenExpiresIn);
74 setUser(response.data.user);
75 navigate("/");
76 toast.success("Registration successful!");
77 } catch (error: any) {
78 const errorMessage = error.response?.data?.error || "Please try again.";
79 setError(`Registration failed: ${errorMessage}`);
80 }
81 };
82
83 return (
84 <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center px-4 py-10">
85 <div className="w-full max-w-md">
86 <div className="text-center mb-8">
87 <h1 className="text-4xl font-extrabold text-white mb-2">
88 Create Account
89 </h1>
90 <p className="text-gray-400">Join FinkWave and discover music</p>
91 </div>
92
93 <form
94 className="bg-[#181818] rounded-xl p-8 space-y-6"
95 onSubmit={handleRegister}
96 >
97 {error && (
98 <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
99 <p className="text-red-400 text-sm">{error}</p>
100 </div>
101 )}
102
103 <div>
104 <label
105 className="block text-sm font-medium text-gray-300 mb-2"
106 htmlFor="username"
107 >
108 Username *
109 </label>
110 <input
111 type="text"
112 id="username"
113 className="w-full bg-[#282828] border border-white/10 rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
114 placeholder="Choose a username"
115 value={username}
116 onChange={(e) => setUsername(e.target.value)}
117 />
118 </div>
119
120 <div>
121 <label
122 className="block text-sm font-medium text-gray-300 mb-2"
123 htmlFor="password"
124 >
125 Password *
126 </label>
127 <input
128 type="password"
129 id="password"
130 className="w-full bg-[#282828] border border-white/10 rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
131 placeholder="Create a password"
132 value={password}
133 onChange={(e) => setPassword(e.target.value)}
134 />
135 </div>
136
137 <div>
138 <label
139 className="block text-sm font-medium text-gray-300 mb-2"
140 htmlFor="fullName"
141 >
142 Full Name *
143 </label>
144 <input
145 type="text"
146 id="fullName"
147 className="w-full bg-[#282828] border border-white/10 rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
148 placeholder="Enter your full name"
149 value={fullname}
150 onChange={(e) => setFullname(e.target.value)}
151 />
152 </div>
153
154 <div>
155 <label
156 className="block text-sm font-medium text-gray-300 mb-2"
157 htmlFor="email"
158 >
159 Email *
160 </label>
161 <input
162 type="email"
163 id="email"
164 className="w-full bg-[#282828] border border-white/10 rounded-lg py-3 px-4 text-white placeholder-gray-500 focus:outline-none focus:border-[#1db954] focus:ring-1 focus:ring-[#1db954] transition-all"
165 placeholder="Enter your email"
166 value={email}
167 onChange={(e) => setEmail(e.target.value)}
168 />
169 </div>
170
171 <div>
172 <label className="block text-sm font-medium text-gray-300 mb-3">
173 I want to be a... *
174 </label>
175 <div className="flex gap-3">
176 <label
177 className={`flex-1 flex items-center justify-center gap-2 p-3 rounded-lg border-2 cursor-pointer transition-all ${
178 userType.includes("ARTIST")
179 ? "border-[#1db954] bg-[#1db954]/10"
180 : "border-white/10 hover:border-white/20"
181 }`}
182 >
183 <input
184 type="checkbox"
185 value="ARTIST"
186 checked={userType.includes("ARTIST")}
187 onChange={(e) => {
188 if (e.target.checked) {
189 setUserType((prev) => [...prev, "ARTIST"]);
190 } else {
191 setUserType((prev) =>
192 prev.filter((type) => type !== "ARTIST"),
193 );
194 }
195 }}
196 className="hidden"
197 />
198 <svg
199 className={`w-5 h-5 ${userType.includes("ARTIST") ? "text-[#1db954]" : "text-gray-400"}`}
200 fill="none"
201 stroke="currentColor"
202 viewBox="0 0 24 24"
203 >
204 <path
205 strokeLinecap="round"
206 strokeLinejoin="round"
207 strokeWidth={1.5}
208 d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2z"
209 />
210 </svg>
211 <span
212 className={`text-sm font-medium ${userType.includes("ARTIST") ? "text-white" : "text-gray-400"}`}
213 >
214 Artist
215 </span>
216 </label>
217
218 <label
219 className={`flex-1 flex items-center justify-center gap-2 p-3 rounded-lg border-2 cursor-pointer transition-all ${
220 userType.includes("LISTENER")
221 ? "border-[#1db954] bg-[#1db954]/10"
222 : "border-white/10 hover:border-white/20"
223 }`}
224 >
225 <input
226 type="checkbox"
227 value="LISTENER"
228 checked={userType.includes("LISTENER")}
229 onChange={(e) => {
230 if (e.target.checked) {
231 setUserType((prev) => [...prev, "LISTENER"]);
232 } else {
233 setUserType((prev) =>
234 prev.filter((type) => type !== "LISTENER"),
235 );
236 }
237 }}
238 className="hidden"
239 />
240 <svg
241 className={`w-5 h-5 ${userType.includes("LISTENER") ? "text-[#1db954]" : "text-gray-400"}`}
242 fill="none"
243 stroke="currentColor"
244 viewBox="0 0 24 24"
245 >
246 <path
247 strokeLinecap="round"
248 strokeLinejoin="round"
249 strokeWidth={1.5}
250 d="M15.536 8.464a5 5 0 010 7.072M12 12h.01M18.364 5.636a9 9 0 010 12.728M5.636 18.364a9 9 0 010-12.728"
251 />
252 </svg>
253 <span
254 className={`text-sm font-medium ${userType.includes("LISTENER") ? "text-white" : "text-gray-400"}`}
255 >
256 Listener
257 </span>
258 </label>
259 </div>
260 </div>
261
262 {/* Profile Photo - PublishSong style */}
263 <div>
264 <label className="block text-sm font-medium text-gray-300 mb-2">
265 Profile Photo (optional)
266 </label>
267 <div className="flex items-center justify-start gap-4">
268 {previewProfilePhoto ? (
269 <div className="relative">
270 <img
271 src={previewProfilePhoto}
272 alt="Profile Preview"
273 className="w-24 h-24 rounded-full object-cover"
274 />
275 <button
276 type="button"
277 onClick={handleRemoveFile}
278 className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center text-white hover:bg-red-600 transition-colors"
279 >
280 <svg
281 className="w-4 h-4"
282 fill="none"
283 stroke="currentColor"
284 viewBox="0 0 24 24"
285 >
286 <path
287 strokeLinecap="round"
288 strokeLinejoin="round"
289 strokeWidth={2}
290 d="M6 18L18 6M6 6l12 12"
291 />
292 </svg>
293 </button>
294 </div>
295 ) : (
296 <label
297 htmlFor="profilePhoto"
298 className="w-24 h-24 rounded-full border-2 border-dashed border-white/20 flex flex-col items-center justify-center cursor-pointer hover:border-[#1db954] transition-colors"
299 >
300 <svg
301 className="w-8 h-8 text-gray-400 mb-1"
302 fill="none"
303 stroke="currentColor"
304 viewBox="0 0 24 24"
305 >
306 <path
307 strokeLinecap="round"
308 strokeLinejoin="round"
309 strokeWidth={1.5}
310 d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
311 />
312 </svg>
313 <span className="text-xs text-gray-400">Upload</span>
314 </label>
315 )}
316 <input
317 type="file"
318 accept="image/*"
319 id="profilePhoto"
320 className="hidden"
321 onChange={(e) => {
322 if (e.target.files && e.target.files[0]) {
323 setProfilePhotoFile(e.target.files[0]);
324 setPreviewProfilePhoto(
325 URL.createObjectURL(e.target.files[0]),
326 );
327 }
328 }}
329 />
330 <div className="text-sm text-gray-400 pt-2">
331 <p>Max size: 2MB</p>
332 <p>JPG, PNG, or WebP</p>
333 </div>
334 </div>
335 </div>
336
337 <button
338 type="submit"
339 className="w-full py-3 bg-[#1db954] rounded-full text-black font-semibold hover:bg-[#1ed760] transition-colors cursor-pointer"
340 >
341 Create Account
342 </button>
343
344 <p className="text-center text-sm text-gray-400">
345 Already have an account?{" "}
346 <Link
347 to="/login"
348 className="text-[#1db954] hover:underline font-medium"
349 >
350 Log in
351 </Link>
352 </p>
353 </form>
354 </div>
355 </div>
356 );
357};
358
359export default Register;
Note: See TracBrowser for help on using the repository browser.