Skip to content

Commit d9aee83

Browse files
committed
add validation for email and password
1 parent 69a3f7c commit d9aee83

File tree

5 files changed

+215
-33
lines changed

5 files changed

+215
-33
lines changed

peerprep-fe/src/app/profile/page.tsx

Lines changed: 105 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,15 @@ import { useAuthStore } from '@/state/useAuthStore';
1717
import { axiosClient } from '@/network/axiosClient';
1818
import { logout } from '@/lib/auth';
1919
import { useRouter } from 'next/navigation';
20+
import { validateEmail, validatePassword, validateUsername } from '@/lib/utils';
21+
import { ValidationError } from '@/types/validation';
2022

23+
/**
24+
* ValidationError: For the form validation errors
25+
* ProfileMessage: For the profile related API response message
26+
* PasswordMessage: For the password related API response message
27+
* @returns profile component
28+
*/
2129
const ProfilePage = () => {
2230
const { user, setUser, clearAuth } = useAuthStore();
2331
const router = useRouter();
@@ -61,19 +69,62 @@ const ProfilePage = () => {
6169
confirmPassword: false,
6270
});
6371

72+
const [validationErrors, setValidationErrors] = useState<ValidationError>({});
73+
6474
// Add function to check if form data has changed
6575
const hasProfileChanges = () => {
6676
return (
6777
formData.username !== user?.username || formData.email !== user?.email
6878
);
6979
};
7080

81+
const validateProfileForm = (): boolean => {
82+
const errors: ValidationError = {};
83+
let isValid = true;
84+
85+
// Validate email
86+
const emailErrors = validateEmail(formData.email);
87+
if (emailErrors.length > 0) {
88+
errors.email = emailErrors;
89+
isValid = false;
90+
}
91+
92+
// Validate username
93+
const usernameErrors = validateUsername(formData.username);
94+
if (usernameErrors.length > 0) {
95+
errors.username = ['Username is required'];
96+
isValid = false;
97+
}
98+
99+
setValidationErrors(errors);
100+
return isValid;
101+
};
102+
103+
const validatePasswordForm = (): boolean => {
104+
const errors: ValidationError = {};
105+
let isValid = true;
106+
107+
// Validate new password
108+
const passwordErrors = validatePassword(
109+
passwordData.newPassword,
110+
passwordData.confirmPassword,
111+
);
112+
if (passwordErrors.length > 0) {
113+
errors.newPassword = passwordErrors;
114+
isValid = false;
115+
}
116+
117+
setValidationErrors(errors);
118+
return isValid;
119+
};
120+
71121
// Add form handlers
72122
const handleProfileSubmit = async (e: FormEvent<HTMLFormElement>) => {
73123
e.preventDefault();
74124
setProfileMessage(null);
125+
setValidationErrors({});
75126

76-
if (!user) return;
127+
if (!user || !validateProfileForm()) return;
77128

78129
try {
79130
const result = await axiosClient.patch(`/users/${user.id}`, {
@@ -108,16 +159,9 @@ const ProfilePage = () => {
108159
const handlePasswordSubmit = async (e: FormEvent) => {
109160
e.preventDefault();
110161
setPasswordMessage(null);
162+
setValidationErrors({});
111163

112-
if (!user) return;
113-
114-
if (passwordData.newPassword !== passwordData.confirmPassword) {
115-
setPasswordMessage({
116-
type: 'error',
117-
text: 'New password and confirm password do not match',
118-
});
119-
return;
120-
}
164+
if (!user || !validatePasswordForm()) return;
121165

122166
try {
123167
const verifyResult = await axiosClient.post(
@@ -135,25 +179,21 @@ const ProfilePage = () => {
135179
password: passwordData.newPassword,
136180
});
137181

138-
// throw error if result is not 200
139182
if (result.status !== 200) {
140183
throw new Error(result.data.message);
141184
}
142185

143-
// Sucess state
144186
setPasswordMessage({
145187
type: 'success',
146188
text: 'Password updated successfully',
147189
});
148190

149-
// reset the fields
150191
setPasswordData({
151192
currentPassword: '',
152193
newPassword: '',
153194
confirmPassword: '',
154195
});
155196

156-
// Clear message after 5 seconds
157197
setTimeout(() => {
158198
setPasswordMessage(null);
159199
}, 5000);
@@ -168,13 +208,15 @@ const ProfilePage = () => {
168208
const handleDeleteAccount = async () => {
169209
const res = await axiosClient.delete(`/users/${user?.id}`);
170210
if (res.status === 200) {
171-
const res = await logout();
211+
const resLogout = await logout();
172212

173-
if (res) {
174-
clearAuth();
175-
router.push('/');
213+
if (!resLogout) {
176214
return;
177215
}
216+
217+
clearAuth();
218+
router.push('/');
219+
return;
178220
}
179221
};
180222

@@ -198,12 +240,23 @@ const ProfilePage = () => {
198240
<Input
199241
id="username"
200242
placeholder="Enter username"
201-
className="border-slate-700 bg-slate-900 text-slate-200"
243+
className={`border-slate-700 bg-slate-900 text-slate-200 ${
244+
validationErrors.username ? 'border-red-500' : ''
245+
}`}
202246
value={formData.username}
203247
onChange={(e) =>
204248
setFormData({ ...formData, username: e.target.value })
205249
}
206250
/>
251+
{validationErrors.username && (
252+
<div className="mt-1 space-y-1">
253+
{validationErrors.username.map((error, index) => (
254+
<p key={index} className="text-xs text-red-500">
255+
{error}
256+
</p>
257+
))}
258+
</div>
259+
)}
207260
</div>
208261
<div className="space-y-2">
209262
<Label htmlFor="email" className="text-slate-200">
@@ -213,12 +266,23 @@ const ProfilePage = () => {
213266
id="email"
214267
type="email"
215268
placeholder="Enter email"
216-
className="border-slate-700 bg-slate-900 text-slate-200"
269+
className={`border-slate-700 bg-slate-900 text-slate-200 ${
270+
validationErrors.email ? 'border-red-500' : ''
271+
}`}
217272
value={formData.email}
218273
onChange={(e) =>
219274
setFormData({ ...formData, email: e.target.value })
220275
}
221276
/>
277+
{validationErrors.email && (
278+
<div className="mt-1 space-y-1">
279+
{validationErrors.email.map((error, index) => (
280+
<p key={index} className="text-xs text-red-500">
281+
{error}
282+
</p>
283+
))}
284+
</div>
285+
)}
222286
</div>
223287
<Button
224288
type="submit"
@@ -279,6 +343,7 @@ const ProfilePage = () => {
279343
</button>
280344
</div>
281345
</div>
346+
282347
<div className="space-y-2">
283348
<Label htmlFor="new-password" className="text-slate-200">
284349
New Password
@@ -287,7 +352,9 @@ const ProfilePage = () => {
287352
<Input
288353
id="new-password"
289354
type={showPasswords.newPassword ? 'text' : 'password'}
290-
className="border-slate-700 bg-slate-900 text-slate-200"
355+
className={`border-slate-700 bg-slate-900 text-slate-200 ${
356+
validationErrors.newPassword ? 'border-red-500' : ''
357+
}`}
291358
value={passwordData.newPassword}
292359
onChange={(e) =>
293360
setPasswordData({
@@ -313,7 +380,17 @@ const ProfilePage = () => {
313380
)}
314381
</button>
315382
</div>
383+
{validationErrors.newPassword && (
384+
<div className="mt-1 space-y-1">
385+
{validationErrors.newPassword.map((error, index) => (
386+
<p key={index} className="text-xs text-red-500">
387+
{error}
388+
</p>
389+
))}
390+
</div>
391+
)}
316392
</div>
393+
317394
<div className="space-y-2">
318395
<Label htmlFor="confirm-password" className="text-slate-200">
319396
Confirm New Password
@@ -322,7 +399,7 @@ const ProfilePage = () => {
322399
<Input
323400
id="confirm-password"
324401
type={showPasswords.confirmPassword ? 'text' : 'password'}
325-
className="border-slate-700 bg-slate-900 text-slate-200"
402+
className={`border-slate-700 bg-slate-900 text-slate-200`}
326403
value={passwordData.confirmPassword}
327404
onChange={(e) =>
328405
setPasswordData({
@@ -349,6 +426,7 @@ const ProfilePage = () => {
349426
</button>
350427
</div>
351428
</div>
429+
352430
<Button
353431
type="submit"
354432
className="bg-[#4ADE80] text-slate-900 hover:bg-[#4ADE80]/90"
@@ -358,7 +436,11 @@ const ProfilePage = () => {
358436
</form>
359437
{passwordMessage && (
360438
<div
361-
className={`mt-2 text-sm ${passwordMessage.type === 'error' ? 'text-red-500' : 'text-green-500'}`}
439+
className={`mt-2 text-sm ${
440+
passwordMessage.type === 'error'
441+
? 'text-red-500'
442+
: 'text-green-500'
443+
}`}
362444
>
363445
{passwordMessage.text}
364446
</div>

peerprep-fe/src/app/signup/page.tsx

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { useState } from 'react';
33
import { Button } from '@/components/ui/button';
44
import { Checkbox } from '@/components/ui/checkbox';
55
import { Input } from '@/components/ui/input';
6-
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
6+
import { Alert, AlertDescription } from '@/components/ui/alert';
77
import { axiosClient } from '@/network/axiosClient';
88
import { useRouter } from 'next/navigation';
99
import { useAuthStore } from '@/state/useAuthStore';
1010
import Link from 'next/link';
1111
import { login } from '@/lib/auth';
12+
import { validateEmail, validatePassword } from '@/lib/utils';
13+
import { ValidationError } from '@/types/validation';
1214

1315
export default function SignUpPage() {
1416
const [name, setName] = useState('');
@@ -17,16 +19,38 @@ export default function SignUpPage() {
1719
const [confirmPassword, setConfirmPassword] = useState('');
1820
const [agreeTerms, setAgreeTerms] = useState(false);
1921
const [error, setError] = useState('');
22+
const [validationErrors, setValidationErrors] = useState<ValidationError>({});
2023
const router = useRouter();
2124
const setAuth = useAuthStore((state) => state.setAuth);
2225

26+
const validateForm = (): boolean => {
27+
const errors: ValidationError = {};
28+
let isValid = true;
29+
30+
// Validate email
31+
const emailErrors = validateEmail(email);
32+
if (emailErrors.length > 0) {
33+
errors.email = emailErrors;
34+
isValid = false;
35+
}
36+
37+
// Validate password
38+
const passwordErrors = validatePassword(password, confirmPassword);
39+
if (passwordErrors.length > 0) {
40+
errors.newPassword = passwordErrors;
41+
isValid = false;
42+
}
43+
44+
setValidationErrors(errors);
45+
return isValid;
46+
};
47+
2348
const handleSignUp = async (e: React.FormEvent) => {
2449
e.preventDefault();
2550
setError('');
51+
setValidationErrors({});
2652

27-
// const type = 'user';
28-
if (password !== confirmPassword) {
29-
setError('Passwords do not match');
53+
if (!validateForm()) {
3054
return;
3155
}
3256

@@ -52,15 +76,16 @@ export default function SignUpPage() {
5276
email: email,
5377
password: password,
5478
});
79+
5580
if (loginResult.request.status !== 200) {
5681
setError('Unable to login');
5782
return;
5883
}
5984

6085
const data = loginResult.data.data;
61-
6286
const token = data.accessToken;
6387
const res = await login(token);
88+
6489
if (res) {
6590
setAuth(true, token, data);
6691
router.push('/');
@@ -79,8 +104,6 @@ export default function SignUpPage() {
79104
<div className="w-full max-w-md space-y-6 rounded-lg bg-gray-800 p-8 shadow-xl">
80105
{error && (
81106
<Alert variant="destructive" className="mb-4">
82-
{/* <AlertCircle className="h-4 w-4" /> */}
83-
<AlertTitle>Error</AlertTitle>
84107
<AlertDescription>{error}</AlertDescription>
85108
</Alert>
86109
)}
@@ -110,25 +133,49 @@ export default function SignUpPage() {
110133
placeholder="Email address"
111134
value={email}
112135
onChange={(e) => setEmail(e.target.value)}
113-
className="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 text-sm text-white placeholder-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
136+
className={`w-full rounded-md border ${
137+
validationErrors.email ? 'border-red-500' : 'border-gray-600'
138+
} bg-gray-700 px-3 py-2 text-sm text-white placeholder-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500`}
114139
/>
140+
{validationErrors.email && (
141+
<div className="mt-1 space-y-1">
142+
{validationErrors.email.map((error, index) => (
143+
<p key={index} className="text-xs text-red-500">
144+
{error}
145+
</p>
146+
))}
147+
</div>
148+
)}
115149
</div>
116150
<div>
117151
<Input
118152
type="password"
119153
placeholder="Password"
120154
value={password}
121155
onChange={(e) => setPassword(e.target.value)}
122-
className="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 text-sm text-white placeholder-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
156+
className={`w-full rounded-md border ${
157+
validationErrors.newPassword
158+
? 'border-red-500'
159+
: 'border-gray-600'
160+
} bg-gray-700 px-3 py-2 text-sm text-white placeholder-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500`}
123161
/>
162+
{validationErrors.newPassword && (
163+
<div className="mt-1 space-y-1">
164+
{validationErrors.newPassword.map((error, index) => (
165+
<p key={index} className="text-xs text-red-500">
166+
{error}
167+
</p>
168+
))}
169+
</div>
170+
)}
124171
</div>
125172
<div>
126173
<Input
127174
type="password"
128175
placeholder="Confirm password"
129176
value={confirmPassword}
130177
onChange={(e) => setConfirmPassword(e.target.value)}
131-
className="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 text-sm text-white placeholder-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
178+
className={`w-full rounded-md border bg-gray-700 px-3 py-2 text-sm text-white placeholder-gray-400 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500`}
132179
/>
133180
</div>
134181
<div className="flex items-center">

0 commit comments

Comments
 (0)