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

main
Last change on this file since 3238007 was 3238007, checked in by Filip Gavrilovski <filipgavrilovski28@…>, 6 months ago

add custom exceptions and proper server side error handling

  • Property mode set to 100644
File size: 4.8 KB
RevLine 
[484dc3f]1import { useEffect, useState } from "react";
[0c86e77]2import { useNavigate } from "react-router";
[b70fbeb]3import { toast } from "react-toastify";
[4e5cf92]4import axiosInstance, { scheduleTokenRefresh } from "../api/axiosInstance";
5import { useAuth } from "../context/authContext";
6import type { User } from "../utils/types";
[0c86e77]7
8const Register = () => {
[4022db1]9 const { setUser } = useAuth();
[0c86e77]10 const [username, setUsername] = useState("");
11 const [password, setPassword] = useState("");
12 const [fullname, setFullname] = useState("");
13 const [email, setEmail] = useState("");
[484dc3f]14 const [profilePhotoFile, setProfilePhotoFile] = useState<File | null>(null);
15 const [previewProfilePhoto, setPreviewProfilePhoto] = useState<string | null>(
16 null,
17 );
[b70fbeb]18 const [error, setError] = useState<string | null>(null);
[0c86e77]19
20 const navigate = useNavigate();
21
[484dc3f]22 useEffect(() => {
23 return () => {
24 if (previewProfilePhoto) {
25 URL.revokeObjectURL(previewProfilePhoto);
26 }
27 };
28 }, [previewProfilePhoto]);
29
[24a2a2a]30 const handleRegister = async (e: React.FormEvent) => {
31 e.preventDefault();
[484dc3f]32 if (profilePhotoFile && profilePhotoFile.size > 5 * 1024 * 1024) {
33 alert("Max file size is 5MB");
34 return;
35 }
36
37 if (profilePhotoFile && !profilePhotoFile.type.startsWith("image/")) {
38 alert("Only images allowed");
39 return;
40 }
41 const formData = new FormData();
42 if (profilePhotoFile) formData.append("profilePhoto", profilePhotoFile);
43 formData.append("username", username);
44 formData.append("fullname", fullname);
45 formData.append("email", email);
46 formData.append("password", password);
47
[0c86e77]48 try {
49 const response = await axiosInstance.post<{
[3727852]50 user: User;
[0c86e77]51 tokenExpiresIn: number;
[484dc3f]52 }>("/auth/register", formData);
[0c86e77]53 scheduleTokenRefresh(response.data.tokenExpiresIn);
[3727852]54 setUser(response.data.user);
[0c86e77]55 navigate("/");
[b70fbeb]56 toast.success("Registration successful!");
57 } catch (error: any) {
58 const errorMessage =
59 error.response?.data?.error || "Registration failed. Please try again.";
[3238007]60 setError(`Registration failed: ${errorMessage}`);
[0c86e77]61 }
62 };
63
64 return (
[4022db1]65 <div className="flex flex-col items-center justify-center min-h-[90vh] bg-gray-100">
[0c86e77]66 <h2 className="text-2xl mb-4">Register</h2>
67 <form className="bg-white p-6 rounded shadow-md w-80">
[b70fbeb]68 {error && <p className="text-red-500 mb-4">{error}</p>}
[0c86e77]69 <div className="mb-4">
70 <label className="block text-gray-700 mb-2" htmlFor="username">
71 Username
72 </label>
73 <input
74 type="text"
75 id="username"
76 className="w-full p-2 border border-gray-300 rounded"
77 value={username}
78 onChange={(e) => setUsername(e.target.value)}
79 />
80 </div>
81 <div className="mb-4">
82 <label className="block text-gray-700 mb-2" htmlFor="password">
83 Password
84 </label>
85 <input
86 type="password"
87 id="password"
88 className="w-full p-2 border border-gray-300 rounded"
89 value={password}
90 onChange={(e) => setPassword(e.target.value)}
91 />
92 </div>
93 <div className="mb-4">
94 <label className="block text-gray-700 mb-2" htmlFor="fullName">
95 Full Name
96 </label>
97 <input
98 type="text"
99 id="fullName"
100 className="w-full p-2 border border-gray-300 rounded"
101 value={fullname}
102 onChange={(e) => setFullname(e.target.value)}
103 />
104 </div>
105 <div className="mb-4">
106 <label className="block text-gray-700 mb-2" htmlFor="email">
107 Email
108 </label>
109 <input
110 type="email"
111 id="email"
112 className="w-full p-2 border border-gray-300 rounded"
113 value={email}
114 onChange={(e) => setEmail(e.target.value)}
115 />
116 </div>
117 <div className="mb-4">
[484dc3f]118 <label className="block text-gray-700 mb-2">Profile Photo</label>
119 <div className="flex items-center gap-3">
120 <label
121 htmlFor="profilePhoto"
122 className="px-4 py-2 bg-blue-500 text-white rounded cursor-pointer hover:bg-blue-600 transition-colors text-sm font-medium"
123 >
124 Choose File
125 </label>
126 <span className="text-gray-600 text-sm">
127 {profilePhotoFile ? profilePhotoFile.name : "No file chosen"}
128 </span>
129 </div>
[0c86e77]130 <input
[484dc3f]131 type="file"
132 accept="image/*"
[0c86e77]133 id="profilePhoto"
[484dc3f]134 className="hidden"
135 onChange={(e) => {
136 if (e.target.files && e.target.files[0]) {
137 setProfilePhotoFile(e.target.files[0]);
138 setPreviewProfilePhoto(URL.createObjectURL(e.target.files[0]));
139 }
140 }}
[0c86e77]141 />
142 </div>
[484dc3f]143 {previewProfilePhoto && (
144 <div className="mb-4">
145 <p className="block text-gray-700 mb-2">Profile Photo Preview:</p>
146 <img
147 src={previewProfilePhoto}
148 alt="Profile Preview"
149 className="w-20 h-20 object-cover rounded-full"
150 />
151 </div>
152 )}
[0c86e77]153 <button
154 type="submit"
[3727852]155 className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 cursor-pointer"
[484dc3f]156 onClick={handleRegister}
[0c86e77]157 >
158 Register
159 </button>
160 </form>
161 </div>
162 );
163};
164
165export default Register;
Note: See TracBrowser for help on using the repository browser.