Skip to content

Commit

Permalink
Merge pull request #45 from nicolelim02/chore/refactor
Browse files Browse the repository at this point in the history
Refactor
  • Loading branch information
ruiqi7 authored Oct 3, 2024
2 parents a24d33d + 446c304 commit 98ab0fa
Show file tree
Hide file tree
Showing 23 changed files with 918 additions and 1,034 deletions.
17 changes: 17 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-router-dom": "^6.26.2",
"react-toastify": "^10.0.5",
"vite-plugin-svgr": "^4.2.0"
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Home from "./pages/Home";
import SignUp from "./pages/SignUp";
import LogIn from "./pages/LogIn";
import ProtectedRoutes from "./components/ProtectedRoutes";
import ProfileContextProvider from "./contexts/ProfileContext";

function App() {
return (
Expand All @@ -26,7 +27,14 @@ function App() {
<Route path=":questionId/edit" element={<QuestionEdit />} />
</Route>
</Route>
<Route path="profile/:userId" element={<ProfilePage />} />
<Route
path="profile/:userId"
element={
<ProfileContextProvider>
<ProfilePage />
</ProfileContextProvider>
}
/>
<Route path="*" element={<PageNotFound />} />
</Route>
<Route path="/signup" element={<SignUp />}></Route>
Expand Down
231 changes: 109 additions & 122 deletions frontend/src/components/ChangePasswordModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,123 @@
import { forwardRef, useState } from "react";
import { Box, Button, Stack, Typography } from "@mui/material";
import PasswordTextField from "../PasswordTextField";
import { userClient } from "../../utils/api";
import axios from "axios";
import {
FAILED_PW_UPDATE_MESSAGE,
SUCCESS_PW_UPDATE_MESSAGE,
} from "../../utils/constants";
Button,
Container,
Dialog,
DialogContent,
DialogTitle,
Stack,
styled,
} from "@mui/material";
import { useForm } from "react-hook-form";
import { useProfile } from "../../contexts/ProfileContext";
import { passwordValidator } from "../../utils/validators";
import PasswordTextField from "../PasswordTextField";

interface ChangePasswordModalProps {
handleClose: () => void;
userId: string;
onUpdate: (
isProfileEdit: boolean,
message: string,
isSuccess: boolean,
) => void;
open: boolean;
onClose: () => void;
}

const ChangePasswordModal = forwardRef<
HTMLDivElement,
ChangePasswordModalProps
>((props, ref) => {
const { handleClose, userId, onUpdate } = props;
const [currPassword, setCurrPassword] = useState<string>("");
const [newPassword, setNewPassword] = useState<string>("");
const [confirmPassword, setConfirmPassword] = useState<string>("");
const StyledForm = styled("form")(({ theme }) => ({
marginTop: theme.spacing(1),
}));

const [isCurrPasswordValid, setIsCurrPasswordValid] =
useState<boolean>(false);
const [isNewPasswordValid, setIsNewPasswordValid] = useState<boolean>(false);
const [isConfirmPasswordValid, setIsConfirmPasswordValid] =
useState<boolean>(false);
const ChangePasswordModal: React.FC<ChangePasswordModalProps> = (props) => {
const { open, onClose } = props;
const {
register,
handleSubmit,
formState: { errors, isDirty, isValid },
watch,
} = useForm<{
oldPassword: string;
newPassword: string;
confirmPassword: string;
}>({
mode: "all",
});

const isUpdateDisabled = !(
isCurrPasswordValid &&
isNewPasswordValid &&
isConfirmPasswordValid
);
const profile = useProfile();

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

try {
await userClient.patch(
`/users/${userId}`,
{
oldPassword: currPassword,
newPassword: newPassword,
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
},
);
handleClose();
onUpdate(false, SUCCESS_PW_UPDATE_MESSAGE, true);
} catch (error) {
if (axios.isAxiosError(error)) {
const message =
error.response?.data.message || FAILED_PW_UPDATE_MESSAGE;
onUpdate(false, message, false);
} else {
onUpdate(false, FAILED_PW_UPDATE_MESSAGE, false);
}
}
};
const { updatePassword } = profile;

return (
<Box
ref={ref}
sx={(theme) => ({
backgroundColor: theme.palette.common.white,
display: "flex",
width: 600,
flexDirection: "column",
alignItems: "center",
borderRadius: "16px",
padding: "40px",
})}
>
<Typography component="h1" variant="h3">
Change Password
</Typography>
<PasswordTextField
label="Current password"
passwordVal={false}
password={currPassword}
setPassword={setCurrPassword}
isMatch={false}
setValidity={setIsCurrPasswordValid}
/>
<PasswordTextField
label="New password"
passwordVal={true}
password={newPassword}
setPassword={setNewPassword}
isMatch={true}
passwordToMatch={confirmPassword}
setValidity={setIsNewPasswordValid}
/>
<PasswordTextField
label="Confirm new password"
passwordVal={false}
password={confirmPassword}
setPassword={setConfirmPassword}
isMatch={true}
passwordToMatch={newPassword}
setValidity={setIsConfirmPasswordValid}
/>
<Stack direction="row" spacing={2} sx={{ marginTop: 2, width: "100%" }}>
<Button
variant="contained"
color="secondary"
onClick={handleClose}
sx={{ flexGrow: 1 }}
>
Cancel
</Button>
<Button
variant="contained"
disabled={isUpdateDisabled}
onClick={handleSubmit}
sx={{ flexGrow: 1 }}
>
Update
</Button>
</Stack>
</Box>
<Dialog fullWidth open={open} onClose={onClose}>
<DialogTitle fontSize={24}>Change password</DialogTitle>
<DialogContent>
<Container maxWidth="sm">
<StyledForm
onSubmit={handleSubmit((data) => {
updatePassword({
oldPassword: data.oldPassword,
newPassword: data.newPassword,
});
onClose();
})}
>
<PasswordTextField
label="Current password"
required
fullWidth
margin="normal"
{...register("oldPassword")}
/>
<PasswordTextField
displayTooltip
label="New password"
required
fullWidth
margin="normal"
{...register("newPassword", {
validate: { passwordValidator },
})}
error={!!errors.newPassword}
helperText={errors.newPassword?.message}
/>
<PasswordTextField
label="Confirm password"
required
fullWidth
margin="normal"
{...register("confirmPassword", {
validate: {
matchPassword: (value) =>
watch("newPassword") === value || "Password does not match",
},
})}
error={!!errors.confirmPassword}
helperText={errors.confirmPassword?.message}
/>
<Stack
direction={"row"}
spacing={2}
sx={(theme) => ({ marginTop: theme.spacing(1) })}
>
<Button
fullWidth
variant="contained"
color="secondary"
onClick={onClose}
>
Cancel
</Button>
<Button
fullWidth
variant="contained"
disabled={!isDirty || !isValid}
type="submit"
>
Update
</Button>
</Stack>
</StyledForm>
</Container>
</DialogContent>
</Dialog>
);
});
};

export default ChangePasswordModal;
Loading

0 comments on commit 98ab0fa

Please sign in to comment.