Skip to content

Commit 98ab0fa

Browse files
authored
Merge pull request #45 from nicolelim02/chore/refactor
Refactor
2 parents a24d33d + 446c304 commit 98ab0fa

File tree

23 files changed

+918
-1034
lines changed

23 files changed

+918
-1034
lines changed

frontend/package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"axios": "^1.7.7",
2525
"react": "^18.3.1",
2626
"react-dom": "^18.3.1",
27+
"react-hook-form": "^7.53.0",
2728
"react-router-dom": "^6.26.2",
2829
"react-toastify": "^10.0.5",
2930
"vite-plugin-svgr": "^4.2.0"

frontend/src/App.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Home from "./pages/Home";
1111
import SignUp from "./pages/SignUp";
1212
import LogIn from "./pages/LogIn";
1313
import ProtectedRoutes from "./components/ProtectedRoutes";
14+
import ProfileContextProvider from "./contexts/ProfileContext";
1415

1516
function App() {
1617
return (
@@ -26,7 +27,14 @@ function App() {
2627
<Route path=":questionId/edit" element={<QuestionEdit />} />
2728
</Route>
2829
</Route>
29-
<Route path="profile/:userId" element={<ProfilePage />} />
30+
<Route
31+
path="profile/:userId"
32+
element={
33+
<ProfileContextProvider>
34+
<ProfilePage />
35+
</ProfileContextProvider>
36+
}
37+
/>
3038
<Route path="*" element={<PageNotFound />} />
3139
</Route>
3240
<Route path="/signup" element={<SignUp />}></Route>
Lines changed: 109 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,123 @@
1-
import { forwardRef, useState } from "react";
2-
import { Box, Button, Stack, Typography } from "@mui/material";
3-
import PasswordTextField from "../PasswordTextField";
4-
import { userClient } from "../../utils/api";
5-
import axios from "axios";
61
import {
7-
FAILED_PW_UPDATE_MESSAGE,
8-
SUCCESS_PW_UPDATE_MESSAGE,
9-
} from "../../utils/constants";
2+
Button,
3+
Container,
4+
Dialog,
5+
DialogContent,
6+
DialogTitle,
7+
Stack,
8+
styled,
9+
} from "@mui/material";
10+
import { useForm } from "react-hook-form";
11+
import { useProfile } from "../../contexts/ProfileContext";
12+
import { passwordValidator } from "../../utils/validators";
13+
import PasswordTextField from "../PasswordTextField";
1014

1115
interface ChangePasswordModalProps {
12-
handleClose: () => void;
13-
userId: string;
14-
onUpdate: (
15-
isProfileEdit: boolean,
16-
message: string,
17-
isSuccess: boolean,
18-
) => void;
16+
open: boolean;
17+
onClose: () => void;
1918
}
2019

21-
const ChangePasswordModal = forwardRef<
22-
HTMLDivElement,
23-
ChangePasswordModalProps
24-
>((props, ref) => {
25-
const { handleClose, userId, onUpdate } = props;
26-
const [currPassword, setCurrPassword] = useState<string>("");
27-
const [newPassword, setNewPassword] = useState<string>("");
28-
const [confirmPassword, setConfirmPassword] = useState<string>("");
20+
const StyledForm = styled("form")(({ theme }) => ({
21+
marginTop: theme.spacing(1),
22+
}));
2923

30-
const [isCurrPasswordValid, setIsCurrPasswordValid] =
31-
useState<boolean>(false);
32-
const [isNewPasswordValid, setIsNewPasswordValid] = useState<boolean>(false);
33-
const [isConfirmPasswordValid, setIsConfirmPasswordValid] =
34-
useState<boolean>(false);
24+
const ChangePasswordModal: React.FC<ChangePasswordModalProps> = (props) => {
25+
const { open, onClose } = props;
26+
const {
27+
register,
28+
handleSubmit,
29+
formState: { errors, isDirty, isValid },
30+
watch,
31+
} = useForm<{
32+
oldPassword: string;
33+
newPassword: string;
34+
confirmPassword: string;
35+
}>({
36+
mode: "all",
37+
});
3538

36-
const isUpdateDisabled = !(
37-
isCurrPasswordValid &&
38-
isNewPasswordValid &&
39-
isConfirmPasswordValid
40-
);
39+
const profile = useProfile();
4140

42-
const handleSubmit = async () => {
43-
const accessToken = localStorage.getItem("token");
41+
if (!profile) {
42+
throw new Error("useProfile() must be used within ProfileContextProvider");
43+
}
4444

45-
try {
46-
await userClient.patch(
47-
`/users/${userId}`,
48-
{
49-
oldPassword: currPassword,
50-
newPassword: newPassword,
51-
},
52-
{
53-
headers: {
54-
Authorization: `Bearer ${accessToken}`,
55-
"Content-Type": "application/json",
56-
},
57-
},
58-
);
59-
handleClose();
60-
onUpdate(false, SUCCESS_PW_UPDATE_MESSAGE, true);
61-
} catch (error) {
62-
if (axios.isAxiosError(error)) {
63-
const message =
64-
error.response?.data.message || FAILED_PW_UPDATE_MESSAGE;
65-
onUpdate(false, message, false);
66-
} else {
67-
onUpdate(false, FAILED_PW_UPDATE_MESSAGE, false);
68-
}
69-
}
70-
};
45+
const { updatePassword } = profile;
7146

7247
return (
73-
<Box
74-
ref={ref}
75-
sx={(theme) => ({
76-
backgroundColor: theme.palette.common.white,
77-
display: "flex",
78-
width: 600,
79-
flexDirection: "column",
80-
alignItems: "center",
81-
borderRadius: "16px",
82-
padding: "40px",
83-
})}
84-
>
85-
<Typography component="h1" variant="h3">
86-
Change Password
87-
</Typography>
88-
<PasswordTextField
89-
label="Current password"
90-
passwordVal={false}
91-
password={currPassword}
92-
setPassword={setCurrPassword}
93-
isMatch={false}
94-
setValidity={setIsCurrPasswordValid}
95-
/>
96-
<PasswordTextField
97-
label="New password"
98-
passwordVal={true}
99-
password={newPassword}
100-
setPassword={setNewPassword}
101-
isMatch={true}
102-
passwordToMatch={confirmPassword}
103-
setValidity={setIsNewPasswordValid}
104-
/>
105-
<PasswordTextField
106-
label="Confirm new password"
107-
passwordVal={false}
108-
password={confirmPassword}
109-
setPassword={setConfirmPassword}
110-
isMatch={true}
111-
passwordToMatch={newPassword}
112-
setValidity={setIsConfirmPasswordValid}
113-
/>
114-
<Stack direction="row" spacing={2} sx={{ marginTop: 2, width: "100%" }}>
115-
<Button
116-
variant="contained"
117-
color="secondary"
118-
onClick={handleClose}
119-
sx={{ flexGrow: 1 }}
120-
>
121-
Cancel
122-
</Button>
123-
<Button
124-
variant="contained"
125-
disabled={isUpdateDisabled}
126-
onClick={handleSubmit}
127-
sx={{ flexGrow: 1 }}
128-
>
129-
Update
130-
</Button>
131-
</Stack>
132-
</Box>
48+
<Dialog fullWidth open={open} onClose={onClose}>
49+
<DialogTitle fontSize={24}>Change password</DialogTitle>
50+
<DialogContent>
51+
<Container maxWidth="sm">
52+
<StyledForm
53+
onSubmit={handleSubmit((data) => {
54+
updatePassword({
55+
oldPassword: data.oldPassword,
56+
newPassword: data.newPassword,
57+
});
58+
onClose();
59+
})}
60+
>
61+
<PasswordTextField
62+
label="Current password"
63+
required
64+
fullWidth
65+
margin="normal"
66+
{...register("oldPassword")}
67+
/>
68+
<PasswordTextField
69+
displayTooltip
70+
label="New password"
71+
required
72+
fullWidth
73+
margin="normal"
74+
{...register("newPassword", {
75+
validate: { passwordValidator },
76+
})}
77+
error={!!errors.newPassword}
78+
helperText={errors.newPassword?.message}
79+
/>
80+
<PasswordTextField
81+
label="Confirm password"
82+
required
83+
fullWidth
84+
margin="normal"
85+
{...register("confirmPassword", {
86+
validate: {
87+
matchPassword: (value) =>
88+
watch("newPassword") === value || "Password does not match",
89+
},
90+
})}
91+
error={!!errors.confirmPassword}
92+
helperText={errors.confirmPassword?.message}
93+
/>
94+
<Stack
95+
direction={"row"}
96+
spacing={2}
97+
sx={(theme) => ({ marginTop: theme.spacing(1) })}
98+
>
99+
<Button
100+
fullWidth
101+
variant="contained"
102+
color="secondary"
103+
onClick={onClose}
104+
>
105+
Cancel
106+
</Button>
107+
<Button
108+
fullWidth
109+
variant="contained"
110+
disabled={!isDirty || !isValid}
111+
type="submit"
112+
>
113+
Update
114+
</Button>
115+
</Stack>
116+
</StyledForm>
117+
</Container>
118+
</DialogContent>
119+
</Dialog>
133120
);
134-
});
121+
};
135122

136123
export default ChangePasswordModal;

0 commit comments

Comments
 (0)