- Timestamp:
- 02/10/26 22:10:32 (5 months ago)
- Branches:
- main
- Children:
- db5fb23
- Parents:
- 1579b4f
- Location:
- frontend/src/pages
- Files:
-
- 2 edited
-
Login.tsx (modified) (2 diffs)
-
Register.tsx (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/pages/Login.tsx
r1579b4f r92db381 1 1 import { useState } from "react"; 2 import { useNavigate } from "react-router-dom";2 import { Link, useNavigate } from "react-router-dom"; 3 3 import { toast } from "react-toastify"; 4 4 import axiosInstance, { scheduleTokenRefresh } from "../api/axiosInstance"; … … 32 32 }; 33 33 return ( 34 <div className="flex flex-col items-center justify-center min-h-[90vh] bg-gray-100"> 35 <h2 className="text-2xl mb-4">Login</h2> 36 <form 37 onSubmit={handleLogin} 38 className="bg-white p-6 rounded shadow-md w-80" 39 > 40 {error && <p className="text-red-500 mb-4">{error}</p>} 41 <div className="mb-4"> 42 <label className="block text-gray-700 mb-2" htmlFor="username"> 43 Username 44 </label> 45 <input 46 type="text" 47 id="username" 48 className="w-full p-2 border border-gray-300 rounded" 49 value={username} 50 onChange={(e) => setUsername(e.target.value)} 51 /> 34 <div className="min-h-screen bg-linear-to-br from-[#1e1e2e] to-[#0f0f1e] flex items-center justify-center px-4"> 35 <div className="w-full max-w-md"> 36 <div className="text-center mb-8"> 37 <h1 className="text-4xl font-extrabold text-white mb-2"> 38 Welcome Back 39 </h1> 40 <p className="text-gray-400">Log in to continue to FinkWave</p> 52 41 </div> 53 <div className="mb-4"> 54 <label className="block text-gray-700 mb-2" htmlFor="password"> 55 Password 56 </label> 57 <input 58 type="password" 59 id="password" 60 className="w-full p-2 border border-gray-300 rounded" 61 value={password} 62 onChange={(e) => setPassword(e.target.value)} 63 /> 64 </div> 65 <button 66 type="submit" 67 className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors duration-200 w-full cursor-pointer" 42 43 <form 44 onSubmit={handleLogin} 45 className="bg-[#181818] rounded-xl p-8 space-y-6" 68 46 > 69 Login 70 </button> 71 </form> 47 {error && ( 48 <div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3"> 49 <p className="text-red-400 text-sm">{error}</p> 50 </div> 51 )} 52 53 <div> 54 <label 55 className="block text-sm font-medium text-gray-300 mb-2" 56 htmlFor="username" 57 > 58 Username 59 </label> 60 <input 61 type="text" 62 id="username" 63 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" 64 placeholder="Enter your username" 65 value={username} 66 onChange={(e) => setUsername(e.target.value)} 67 /> 68 </div> 69 70 <div> 71 <label 72 className="block text-sm font-medium text-gray-300 mb-2" 73 htmlFor="password" 74 > 75 Password 76 </label> 77 <input 78 type="password" 79 id="password" 80 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" 81 placeholder="Enter your password" 82 value={password} 83 onChange={(e) => setPassword(e.target.value)} 84 /> 85 </div> 86 87 <button 88 type="submit" 89 className="w-full py-3 bg-[#1db954] rounded-full text-black font-semibold hover:bg-[#1ed760] transition-colors cursor-pointer" 90 > 91 Log In 92 </button> 93 94 <p className="text-center text-sm text-gray-400"> 95 Don't have an account?{" "} 96 <Link 97 to="/register" 98 className="text-[#1db954] hover:underline font-medium" 99 > 100 Sign up 101 </Link> 102 </p> 103 </form> 104 </div> 72 105 </div> 73 106 ); -
frontend/src/pages/Register.tsx
r1579b4f r92db381 1 1 import { useEffect, useState } from "react"; 2 import { useNavigate } from "react-router";2 import { Link, useNavigate } from "react-router-dom"; 3 3 import { toast } from "react-toastify"; 4 4 import axiosInstance, { scheduleTokenRefresh } from "../api/axiosInstance"; … … 40 40 e.preventDefault(); 41 41 if (profilePhotoFile && profilePhotoFile.size > 5 * 1024 * 1024) { 42 alert("Max file size is 5MB");42 toast.error("Max file size is 5MB"); 43 43 return; 44 44 } … … 55 55 } 56 56 if (profilePhotoFile && !profilePhotoFile.type.startsWith("image/")) { 57 alert("Only images allowed");57 toast.error("Only images allowed"); 58 58 return; 59 59 } … … 82 82 83 83 return ( 84 <div className="flex flex-col items-center justify-center min-h-[90vh] bg-gray-100"> 85 <h2 className="text-2xl mb-4">Register</h2> 86 <form 87 className="bg-white p-6 rounded shadow-md w-full max-w-md" 88 onSubmit={handleRegister} 89 > 90 {error && <p className="text-red-500 mb-4">{error}</p>} 91 <div className="mb-4"> 92 <label className="block text-gray-700 mb-2" htmlFor="username"> 93 Username 94 </label> 95 <input 96 type="text" 97 id="username" 98 className="w-full p-2 border border-gray-300 rounded" 99 value={username} 100 onChange={(e) => setUsername(e.target.value)} 101 /> 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> 102 91 </div> 103 <div className="mb-4"> 104 <label className="block text-gray-700 mb-2" htmlFor="password"> 105 Password 106 </label> 107 <input 108 type="password" 109 id="password" 110 className="w-full p-2 border border-gray-300 rounded" 111 value={password} 112 onChange={(e) => setPassword(e.target.value)} 113 /> 114 </div> 115 <div className="mb-4"> 116 <label className="block text-gray-700 mb-2" htmlFor="fullName"> 117 Full Name 118 </label> 119 <input 120 type="text" 121 id="fullName" 122 className="w-full p-2 border border-gray-300 rounded" 123 value={fullname} 124 onChange={(e) => setFullname(e.target.value)} 125 /> 126 </div> 127 <div className="mb-4"> 128 <label className="block text-gray-700 mb-2" htmlFor="email"> 129 Email 130 </label> 131 <input 132 type="email" 133 id="email" 134 className="w-full p-2 border border-gray-300 rounded" 135 value={email} 136 onChange={(e) => setEmail(e.target.value)} 137 /> 138 </div> 139 <div className="mb-4"> 140 <label className="block text-gray-700 mb-2" htmlFor="userType"> 141 User Type 142 </label> 143 <input 144 type="checkbox" 145 id="artist" 146 value="ARTIST" 147 onChange={(e) => { 148 if (e.target.checked) { 149 setUserType((prev) => [...prev, "ARTIST"]); 150 } else { 151 setUserType((prev) => prev.filter((type) => type !== "ARTIST")); 152 } 153 }} 154 /> 155 <label htmlFor="artist" className="ml-2 mr-4"> 156 Artist 157 </label> 158 <input 159 type="checkbox" 160 id="listener" 161 value="LISTENER" 162 onChange={(e) => { 163 if (e.target.checked) { 164 setUserType((prev) => [...prev, "LISTENER"]); 165 } else { 166 setUserType((prev) => 167 prev.filter((type) => type !== "LISTENER"), 168 ); 169 } 170 }} 171 /> 172 <label htmlFor="listener" className="ml-2"> 173 Listener 174 </label> 175 </div> 176 <div className="mb-4"> 177 <label className="block text-gray-700 mb-2">Profile Photo</label> 178 <div className="flex items-center gap-3"> 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> 179 104 <label 180 htmlFor="profilePhoto" 181 className="px-4 py-2 bg-blue-500 text-white rounded cursor-pointer hover:bg-blue-600 transition-colors text-sm font-medium text-center" 182 > 183 Choose File 184 </label> 185 <span className="text-gray-600 text-sm flex-1"> 186 {profilePhotoFile ? profilePhotoFile.name : "No file chosen"} 187 </span> 188 {profilePhotoFile && ( 189 <button 190 type="button" 191 onClick={handleRemoveFile} 192 className="px-3 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors text-sm font-medium" 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 }`} 193 182 > 194 Remove 195 </button> 196 )} 197 </div> 198 <input 199 type="file" 200 accept="image/*" 201 id="profilePhoto" 202 className="hidden" 203 onChange={(e) => { 204 if (e.target.files && e.target.files[0]) { 205 setProfilePhotoFile(e.target.files[0]); 206 setPreviewProfilePhoto(URL.createObjectURL(e.target.files[0])); 207 } 208 }} 209 /> 210 </div> 211 {previewProfilePhoto && ( 212 <div className="mb-4"> 213 <p className="block text-gray-700 mb-2">Profile Photo Preview:</p> 214 <img 215 src={previewProfilePhoto} 216 alt="Profile Preview" 217 className="w-20 h-20 object-cover rounded-full" 218 /> 219 </div> 220 )} 221 <button 222 type="submit" 223 className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 cursor-pointer" 224 onClick={handleRegister} 225 > 226 Register 227 </button> 228 </form> 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> 229 355 </div> 230 356 );
Note:
See TracChangeset
for help on using the changeset viewer.
