Skip to content

Commit 28affa1

Browse files
committed
submit
1 parent f5372b4 commit 28affa1

File tree

5 files changed

+125
-15
lines changed

5 files changed

+125
-15
lines changed

client/src/Components/v2/design-elements/BasePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export const MonitorBasePageWithStates = ({
177177
error={error}
178178
{...props}
179179
>
180-
{priorityFallback}
180+
<Stack height={"100%"}>{priorityFallback}</Stack>
181181
</BasePage>
182182
);
183183
}

client/src/Components/v2/design-elements/BulletPointCheck.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const BulletPointCheck = ({
2727
<Check
2828
size={16}
2929
strokeWidth={1.5}
30+
color={variant === "info" ? theme.palette.text.secondary : colors[variant]}
3031
style={{
3132
flexShrink: 0,
3233
}}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {
2+
setNewPasswordSchema,
3+
type SetNewPasswordFormData,
4+
} from "@/Validation/setNewPassword";
5+
6+
export const useSetNewPasswordForm = () => {
7+
const defaults: SetNewPasswordFormData = {
8+
password: "",
9+
confirm: "",
10+
};
11+
12+
return {
13+
schema: setNewPasswordSchema,
14+
defaults,
15+
};
16+
};

client/src/Pages/Auth/SetNewPassword/index.tsx

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,125 @@ import { Button, TextField } from "@/Components/v2/inputs";
77
import Stack from "@mui/material/Stack";
88
import { useTheme } from "@mui/material/styles";
99
import { useTranslation } from "react-i18next";
10+
import { useForm, Controller } from "react-hook-form";
11+
import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js";
12+
import { useSetNewPasswordForm } from "@/Hooks/useSetNewPasswordForm";
13+
import type { SetNewPasswordFormData } from "@/Validation/setNewPassword";
14+
import { specialCharPattern } from "@/Validation/patterns";
15+
import { useParams, useNavigate } from "react-router-dom";
16+
import { usePost } from "@/Hooks/UseApi";
17+
import { useDispatch } from "react-redux";
18+
import { setAuthState } from "@/Features/Auth/authSlice";
1019

1120
const SetNewPasswordPage = () => {
1221
const theme = useTheme();
1322
const { t } = useTranslation();
23+
const { token } = useParams<{ token: string }>();
24+
const navigate = useNavigate();
25+
const dispatch = useDispatch();
26+
const { post, loading } = usePost();
27+
28+
const { schema, defaults } = useSetNewPasswordForm();
29+
30+
const { control, handleSubmit, watch } = useForm<SetNewPasswordFormData>({
31+
resolver: zodResolver(schema),
32+
defaultValues: defaults,
33+
});
34+
35+
const password = watch("password");
36+
const confirm = watch("confirm");
37+
38+
const getVariant = (condition: boolean): "success" | "info" => {
39+
return condition ? "success" : "info";
40+
};
41+
42+
const hasLength = password.length >= 8;
43+
const hasSpecial = specialCharPattern.test(password);
44+
const hasNumber = /\d/.test(password);
45+
const hasUppercase = /[A-Z]/.test(password);
46+
const hasLowercase = /[a-z]/.test(password);
47+
const passwordsMatch = password.length > 0 && password === confirm;
48+
49+
const onSubmit = async (data: SetNewPasswordFormData) => {
50+
if (loading) return;
51+
52+
const result = await post("/auth/recovery/reset", {
53+
password: data.password,
54+
recoveryToken: token,
55+
});
56+
57+
if (result?.success) {
58+
dispatch(setAuthState(result));
59+
navigate("/uptime");
60+
}
61+
};
1462

1563
return (
1664
<BaseAuthPage
65+
component="form"
66+
onSubmit={handleSubmit(onSubmit)}
1767
title={t("pages.auth.setNewPassword.title")}
1868
subtitle={t("pages.auth.setNewPassword.subtitle")}
1969
>
20-
<TextField
21-
type="password"
22-
fieldLabel={t("pages.auth.common.form.option.password.label")}
23-
placeholder={t("pages.auth.common.form.option.password.placeholder")}
70+
<Controller
71+
name="password"
72+
control={control}
73+
render={({ field, fieldState }) => (
74+
<TextField
75+
{...field}
76+
type="password"
77+
fieldLabel={t("pages.auth.common.form.option.password.label")}
78+
placeholder={t("pages.auth.common.form.option.password.placeholder")}
79+
error={!!fieldState.error}
80+
helperText={fieldState.error?.message ?? ""}
81+
/>
82+
)}
2483
/>
25-
<TextField
26-
type="password"
27-
fieldLabel={t("pages.auth.common.form.option.confirmPassword.label")}
28-
placeholder={t("pages.auth.common.form.option.password.placeholder")}
84+
<Controller
85+
name="confirm"
86+
control={control}
87+
render={({ field, fieldState }) => (
88+
<TextField
89+
{...field}
90+
type="password"
91+
fieldLabel={t("pages.auth.common.form.option.confirmPassword.label")}
92+
placeholder={t("pages.auth.common.form.option.password.placeholder")}
93+
error={!!fieldState.error}
94+
helperText={fieldState.error?.message ?? ""}
95+
/>
96+
)}
2997
/>
3098
<Stack gap={theme.spacing(4)}>
3199
<BulletPointCheck
32100
text={t("auth.common.passwordRules.length")}
33-
variant="info"
101+
variant={getVariant(hasLength)}
34102
/>
35103
<BulletPointCheck
36104
text={t("auth.common.passwordRules.special")}
37-
variant="info"
105+
variant={getVariant(hasSpecial)}
38106
/>
39107
<BulletPointCheck
40108
text={t("auth.common.passwordRules.number")}
41-
variant="info"
109+
variant={getVariant(hasNumber)}
42110
/>
43111
<BulletPointCheck
44112
text={t("auth.common.passwordRules.uppercase")}
45-
variant="info"
113+
variant={getVariant(hasUppercase)}
46114
/>
47115
<BulletPointCheck
48116
text={t("auth.common.passwordRules.lowercase")}
49-
variant="info"
117+
variant={getVariant(hasLowercase)}
50118
/>
51119
<BulletPointCheck
52120
text={t("auth.common.passwordRules.match")}
53-
variant="info"
121+
variant={getVariant(passwordsMatch)}
54122
/>
55123
</Stack>
56124
<Button
57125
variant="contained"
58126
type="submit"
59127
fullWidth
128+
loading={loading}
60129
>
61130
{t("auth.forgotPassword.buttons.resetPassword")}
62131
</Button>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { z } from "zod";
2+
import { specialCharPattern } from "@/Validation/patterns";
3+
4+
export const setNewPasswordSchema = z
5+
.object({
6+
password: z
7+
.string()
8+
.min(1, "Password is required")
9+
.min(8, "Password must be at least 8 characters")
10+
.refine((val) => /[A-Z]/.test(val), "Password must contain an uppercase letter")
11+
.refine((val) => /[a-z]/.test(val), "Password must contain a lowercase letter")
12+
.refine((val) => /\d/.test(val), "Password must contain a number")
13+
.refine(
14+
(val) => specialCharPattern.test(val),
15+
"Password must contain a special character"
16+
),
17+
confirm: z.string().min(1, "Please confirm your password"),
18+
})
19+
.refine((data) => data.password === data.confirm, {
20+
message: "Passwords do not match",
21+
path: ["confirm"],
22+
});
23+
24+
export type SetNewPasswordFormData = z.infer<typeof setNewPasswordSchema>;

0 commit comments

Comments
 (0)