Skip to content

Commit 6d32c6d

Browse files
committed
feat: implement change password functionality in settings
- Added changePasswordHandler function in Settings page - Setup state for password form inputs - Added Zod schema validation for new password - Implemented changePassword async thunk in authSlice
1 parent 04941aa commit 6d32c6d

File tree

3 files changed

+129
-13
lines changed

3 files changed

+129
-13
lines changed

src/pages/settings/SettingPage.tsx

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,34 @@ import { Input } from "@/components/ui/input";
2323
import { Label } from "@/components/ui/label";
2424
import { useToast } from "@/hooks/use-toast";
2525
import { AppDispatch, RootState } from "@/store";
26-
import { loadUser, updateProfile } from "@/store/slices/authSlice";
26+
import {
27+
changePassword,
28+
loadUser,
29+
updateProfile,
30+
} from "@/store/slices/authSlice";
2731
import { ProfileForm } from "@/types";
2832
import { useEffect, useState } from "react";
2933
import { FaRegTrashCan } from "react-icons/fa6";
3034
import { useDispatch, useSelector } from "react-redux";
3135
import { useNavigate } from "react-router";
36+
import { passwordFormValues, passwordSchema } from "@/schemas";
37+
import { FaEye, FaEyeSlash } from "react-icons/fa";
3238

3339
const SettingPage = () => {
3440
const { user, isAuthenticated, isLoading } = useSelector(
3541
(state: RootState) => state.auth
3642
);
43+
const [isShowCurrentPassword, setIsShowCurrentPassword] =
44+
useState<boolean>(false);
45+
const [isShowNewPassword, setIsShowNewPassword] = useState<boolean>(false);
46+
const [isShowConfirmPassword, setIsShowConfirmPassword] =
47+
useState<boolean>(false);
3748
const [profileForm, setProfileForm] = useState<ProfileForm>({
3849
name: user?.name,
3950
email: user?.email,
4051
imageFile: null,
4152
});
42-
const [passwordForm, setPasswordForm] = useState({
53+
const [passwordForm, setPasswordForm] = useState<passwordFormValues>({
4354
currentPassword: "",
4455
newPassword: "",
4556
confirmPassword: "",
@@ -84,7 +95,10 @@ const SettingPage = () => {
8495
const profileFormChangeHandler = (field: string, value: any) => {
8596
setProfileForm((prev) => ({ ...prev, [field]: value }));
8697
};
87-
const passwordFormChangeHandler = (field: string, value: string) => {
98+
const passwordFormChangeHandler = (
99+
field: keyof passwordFormValues,
100+
value: string
101+
) => {
88102
setPasswordForm((prev) => ({ ...prev, [field]: value }));
89103
};
90104

@@ -114,7 +128,30 @@ const SettingPage = () => {
114128
}
115129
};
116130

117-
const updatePasswordHandler = () => {};
131+
const updatePasswordHandler = async () => {
132+
try {
133+
const validateData = passwordSchema.parse(passwordForm);
134+
135+
const formData = new FormData();
136+
formData.append("currentPassword", validateData.currentPassword);
137+
formData.append("newPassword", validateData.newPassword);
138+
139+
const result = await dispatch(changePassword(formData));
140+
if (changePassword.fulfilled.match(result)) {
141+
showToast("Password update successfully", "success");
142+
} else {
143+
throw new Error(result.payload as string);
144+
}
145+
} catch (error: any) {
146+
let errorMessage;
147+
if (error.errors) {
148+
errorMessage = error.errors[0].message;
149+
} else {
150+
errorMessage = error.message;
151+
}
152+
showToast(`${errorMessage} ` || "Failed to updated Password", "error");
153+
}
154+
};
118155

119156
if (isLoading) return <LoadingSnipper>Loading Settings...</LoadingSnipper>;
120157

@@ -232,35 +269,53 @@ const SettingPage = () => {
232269
</CardHeader>
233270
<CardContent className="space-y-6">
234271
<div className="grid sm:grid-cols-2 gap-4">
235-
<div className="space-y-2">
272+
<div className="relative space-y-2">
236273
<Label>Current Password</Label>
237274
<Input
238-
type="password"
275+
type={isShowCurrentPassword ? "text" : "password"}
239276
className=" border border-border"
240277
onChange={(e) =>
241278
passwordFormChangeHandler("currentPassword", e.target.value)
242279
}
243280
/>
281+
<div
282+
className={`absolute right-4 top-1/2 cursor-pointer`}
283+
onClick={() => setIsShowCurrentPassword((prev) => !prev)}
284+
>
285+
{isShowCurrentPassword ? <FaEyeSlash /> : <FaEye />}
286+
</div>
244287
</div>
245-
<div className="space-y-2">
288+
<div className="space-y-2 relative">
246289
<Label>New Password</Label>
247290
<Input
248-
type="password"
291+
type={isShowNewPassword ? "text" : "password"}
249292
className=" border border-border"
250293
onChange={(e) =>
251294
passwordFormChangeHandler("newPassword", e.target.value)
252295
}
253296
/>
297+
<div
298+
className={`absolute right-4 top-1/2 cursor-pointer`}
299+
onClick={() => setIsShowNewPassword((prev) => !prev)}
300+
>
301+
{isShowNewPassword ? <FaEyeSlash /> : <FaEye />}
302+
</div>
254303
</div>
255-
<div className="space-y-2">
304+
<div className="space-y-2 relative">
256305
<Label>Confirm New Password</Label>
257306
<Input
258-
type="password"
307+
type={isShowConfirmPassword ? "text" : "password"}
259308
className=" border border-border"
260309
onChange={(e) =>
261310
passwordFormChangeHandler("confirmPassword", e.target.value)
262311
}
263312
/>
313+
<div
314+
className={`absolute right-4 top-1/2 cursor-pointer`}
315+
onClick={() => setIsShowConfirmPassword((prev) => !prev)}
316+
>
317+
{isShowConfirmPassword ? <FaEyeSlash /> : <FaEye />}
318+
</div>
264319
</div>
265320
</div>
266321
<Button onClick={updatePasswordHandler}>Update Password</Button>

src/schemas/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,30 @@ export const snippetFormSchema = z.object({
1818
});
1919

2020
export type SnippetFormInput = z.infer<typeof snippetFormSchema>;
21+
22+
export const passwordSchema = z
23+
.object({
24+
currentPassword: z.string(),
25+
newPassword: z
26+
.string()
27+
.min(8, { message: "Password must be atleast 8 characters" })
28+
.regex(/[A-Z]/, {
29+
message: "Password must include atleast one uppercase letter",
30+
})
31+
.regex(/[a-z]/, {
32+
message: "Password must include atleast one lowercase letter",
33+
})
34+
.regex(/[0-9]/, {
35+
message: "Password must include atleast one number",
36+
})
37+
.regex(/[^a-zA-z0-9]/, {
38+
message: "Password must include atleast one special character",
39+
}),
40+
confirmPassword: z.string(),
41+
})
42+
.refine((data) => data.newPassword === data.confirmPassword, {
43+
message: "Password do not match",
44+
path: ["confirmPassword"],
45+
});
46+
47+
export type passwordFormValues = z.infer<typeof passwordSchema>;

src/store/slices/authSlice.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,31 @@ export const updateProfile = createAsyncThunk(
184184
);
185185

186186
export const changePassword = createAsyncThunk(
187-
"auth/updatePassword",
188-
async (_, { rejectWithValue }) => {}
187+
"auth/changePassword",
188+
async (passwordFormData: FormData, { rejectWithValue }) => {
189+
try {
190+
const token = localStorage.getItem("token");
191+
if (!token) {
192+
return rejectWithValue("No authentication token");
193+
}
194+
195+
const response = await axios.put(
196+
`${API}/auth/change-password`,
197+
passwordFormData,
198+
{
199+
headers: {
200+
Authorization: `Bearer ${token}`,
201+
},
202+
}
203+
);
204+
205+
return response.data.data;
206+
} catch (error: any) {
207+
return rejectWithValue(
208+
error.response?.data?.message || "Failed to update profile data"
209+
);
210+
}
211+
}
189212
);
190213

191214
export const deleteAccount = createAsyncThunk(
@@ -312,11 +335,22 @@ const authSlice = createSlice({
312335
.addCase(updateProfile.fulfilled, (state, action) => {
313336
state.isLoading = false;
314337
state.user = action.payload;
315-
console.log(state.user);
316338
})
317339
.addCase(updateProfile.rejected, (state, action) => {
318340
state.isLoading = false;
319341
state.error = action.payload as string;
342+
})
343+
// Change Password
344+
.addCase(changePassword.pending, (state) => {
345+
state.isLoading = true;
346+
state.error = null;
347+
})
348+
.addCase(changePassword.fulfilled, (state, action) => {
349+
state.isLoading = false;
350+
})
351+
.addCase(changePassword.rejected, (state, action) => {
352+
state.isLoading = false;
353+
state.error = action.payload as string;
320354
});
321355
},
322356
});

0 commit comments

Comments
 (0)