Skip to content

Commit 238abf6

Browse files
authored
refactor forms (#122)
* Audit security vulnerabilities * Refactor account management pages * Yup no longer required * Add form capabilities to new * Prevent that bug before it happens * Move some dots around
1 parent d66a342 commit 238abf6

File tree

14 files changed

+1192
-610
lines changed

14 files changed

+1192
-610
lines changed

package-lock.json

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

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
"private": true,
55
"type": "module",
66
"dependencies": {
7-
"@hookform/resolvers": "^3.4.0",
8-
"@radix-ui/react-slot": "^1.1.1",
7+
"@hookform/resolvers": "^3.10.0",
8+
"@radix-ui/react-checkbox": "^1.1.4",
9+
"@radix-ui/react-label": "^2.1.2",
10+
"@radix-ui/react-slot": "^1.1.2",
911
"@tanstack/react-query": "^5.60.5",
1012
"@types/d3": "^7.4.0",
1113
"@types/lodash": "^4.17.4",
@@ -18,17 +20,18 @@
1820
"firebase": "^10.12.0",
1921
"js-base64": "^3.7.5",
2022
"lodash": "^4.17.21",
23+
"lucide-react": "^0.475.0",
2124
"md5": "^2.3.0",
2225
"moment": "^2.30.1",
2326
"papaparse": "^5.4.1",
2427
"react": "^18.3.1",
2528
"react-dom": "^18.3.1",
2629
"react-dropzone": "^14.2.2",
27-
"react-hook-form": "^7.51.4",
30+
"react-hook-form": "^7.54.2",
2831
"react-router-dom": "^6.23.1",
2932
"react-use": "^17.4.0",
3033
"tailwind-merge": "^3.0.1",
31-
"yup": "^1.0.2"
34+
"zod": "^3.24.1"
3235
},
3336
"scripts": {
3437
"shipit": "git tag v$(date +%y.%m.%d.%H.%M) && git push --tags",

src/components/layout/AppLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type AppLayoutProps = {
1111
const AppLayout: FC<AppLayoutProps> = ({ children }) => {
1212
const location = useLocation();
1313
return (
14-
<div className="grid-rows-[auto_1fr_auto] mt-4 grid min-h-dvh gap-4 md:mt-16 md:gap-16">
14+
<div className="mt-4 grid min-h-dvh grid-rows-[auto_1fr_auto] gap-4 md:mt-16 md:gap-16">
1515
<header>
1616
<div className="container">
1717
<Header />

src/components/pages/account/LogIn.tsx

Lines changed: 87 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@ import useNext from "../../../hooks/useNext";
33
import useUser from "../../../hooks/useUser";
44
import { Message } from "../../common/Message";
55
import Button from "../../ui/Button";
6-
import { yupResolver } from "@hookform/resolvers/yup";
6+
import {
7+
Form,
8+
FormControl,
9+
FormDescription,
10+
FormField,
11+
FormItem,
12+
FormLabel,
13+
FormMessage,
14+
} from "../../ui/Form";
15+
import { Input } from "../../ui/Input";
16+
import { zodResolver } from "@hookform/resolvers/zod";
717
import { useMutation } from "@tanstack/react-query";
818
import { Auth, signInWithEmailAndPassword } from "firebase/auth";
9-
import { Fragment } from "react";
1019
import { useForm } from "react-hook-form";
1120
import { Link, Navigate, useNavigate } from "react-router-dom";
12-
import { object, string } from "yup";
21+
import { z } from "zod";
1322

14-
type LogInFormFieldsType = {
15-
email: string;
16-
password: string;
17-
};
23+
const logInSchema = z.object({
24+
email: z.string().email(),
25+
password: z.string().nonempty({ message: "Password is required." }),
26+
});
1827

19-
const loginSchema = object()
20-
.shape({
21-
email: string().required().email("Invalid email address"),
22-
password: string().required().min(8, "Invalid password"),
23-
})
24-
.required();
28+
type LogInFormFieldsType = z.infer<typeof logInSchema>;
2529

2630
const logIn = async ({
2731
auth,
@@ -37,11 +41,11 @@ const logIn = async ({
3741

3842
const LogIn = () => {
3943
const form = useForm<LogInFormFieldsType>({
44+
resolver: zodResolver(logInSchema),
4045
defaultValues: {
4146
email: "",
4247
password: "",
4348
},
44-
resolver: yupResolver(loginSchema),
4549
});
4650
const navigate = useNavigate();
4751
const { next, nextSearch } = useNext();
@@ -64,69 +68,79 @@ const LogIn = () => {
6468
}
6569

6670
return (
67-
<Fragment>
68-
<form
69-
className="m-auto flex max-w-md flex-col gap-4"
70-
onSubmit={form.handleSubmit((data) =>
71-
signIn({
72-
auth: firebase.auth,
73-
email: data.email,
74-
password: data.password,
75-
}),
76-
)}
77-
>
78-
<h2 className="font-tall text-2xl uppercase md:text-4xl">Log In</h2>
79-
<div className="flex flex-col gap-2">
80-
<input
81-
{...form.register("email")}
82-
type="email"
83-
className="rounded-sm border border-stone-500 p-2"
84-
placeholder="email@example.com"
85-
/>
86-
<Message
87-
message={form.formState.errors.email?.message}
88-
type="error"
71+
<div className="flex flex-col items-center gap-8">
72+
<Form {...form}>
73+
<form
74+
className="m-auto flex w-full max-w-md flex-col gap-4"
75+
onSubmit={form.handleSubmit((data) =>
76+
signIn({
77+
auth: firebase.auth,
78+
email: data.email,
79+
password: data.password,
80+
}),
81+
)}
82+
>
83+
<h2 className="font-tall text-2xl uppercase md:text-4xl">Log In</h2>
84+
<FormField
85+
control={form.control}
86+
name="email"
87+
render={({ field }) => (
88+
<FormItem>
89+
<FormLabel>Email</FormLabel>
90+
<FormControl>
91+
<Input
92+
{...field}
93+
type="email"
94+
placeholder="email@example.com"
95+
/>
96+
</FormControl>
97+
<FormDescription>Enter your email address.</FormDescription>
98+
<FormMessage />
99+
</FormItem>
100+
)}
89101
/>
90-
</div>
91-
<div className="flex flex-col gap-2">
92-
<input
93-
{...form.register("password")}
94-
type="password"
95-
className="rounded-sm border border-stone-500 p-2"
96-
placeholder="password"
102+
<FormField
103+
control={form.control}
104+
name="password"
105+
render={({ field }) => (
106+
<FormItem>
107+
<FormLabel>Password</FormLabel>
108+
<FormControl>
109+
<Input {...field} type="password" placeholder="password" />
110+
</FormControl>
111+
<FormDescription>A very secure password.</FormDescription>
112+
<FormMessage />
113+
</FormItem>
114+
)}
97115
/>
116+
<div>
117+
<Button className="uppercase" asChild>
118+
<input type="submit" value="log in" />
119+
</Button>
120+
</div>
98121
<Message
99-
message={form.formState.errors.password?.message}
100-
type="error"
122+
message={
123+
isSuccess
124+
? "Successfully logged you in."
125+
: isError
126+
? "There was an issue logging you in."
127+
: ""
128+
}
129+
type={isError ? "error" : "info"}
101130
/>
102-
</div>
103-
<div>
104-
<Button className="uppercase" asChild>
105-
<input type="submit" value="log in" />
106-
</Button>
107-
</div>
108-
<Message
109-
message={
110-
isSuccess
111-
? "Successfully logged you in."
112-
: isError
113-
? "There was an issue logging you in."
114-
: ""
115-
}
116-
type={isError ? "error" : "info"}
117-
/>
118-
<Button variant="link" size="link" asChild>
119-
<Link
120-
to={{
121-
pathname: "/reset-password",
122-
search: nextSearch,
123-
}}
124-
>
125-
Forgot your password?
126-
</Link>
127-
</Button>
128-
</form>
129-
</Fragment>
131+
</form>
132+
</Form>
133+
<Button variant="link" size="link" asChild>
134+
<Link
135+
to={{
136+
pathname: "/reset-password",
137+
search: nextSearch,
138+
}}
139+
>
140+
Forgot your password?
141+
</Link>
142+
</Button>
143+
</div>
130144
);
131145
};
132146

src/components/pages/account/PasswordReset.tsx

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,22 @@ import useNext from "../../../hooks/useNext";
33
import useUser from "../../../hooks/useUser";
44
import { Message } from "../../common/Message";
55
import Button from "../../ui/Button";
6-
import { yupResolver } from "@hookform/resolvers/yup";
6+
import {
7+
Form,
8+
FormControl,
9+
FormDescription,
10+
FormField,
11+
FormItem,
12+
FormLabel,
13+
FormMessage,
14+
} from "../../ui/Form";
15+
import { Input } from "../../ui/Input";
16+
import { zodResolver } from "@hookform/resolvers/zod";
717
import { useMutation } from "@tanstack/react-query";
818
import { Auth, sendPasswordResetEmail } from "firebase/auth";
9-
import { Fragment } from "react";
1019
import { useForm } from "react-hook-form";
1120
import { Navigate } from "react-router-dom";
12-
import { object, string } from "yup";
21+
import { z } from "zod";
1322

1423
const sendEmail = async ({
1524
auth,
@@ -21,22 +30,18 @@ const sendEmail = async ({
2130
await sendPasswordResetEmail(auth, email);
2231
};
2332

24-
type PaswordResetFormFieldsType = {
25-
email: string;
26-
};
33+
const passwordResetSchema = z.object({
34+
email: z.string().email(),
35+
});
2736

28-
const passwordResetSchema = object()
29-
.shape({
30-
email: string().required().email("Invalid email address"),
31-
})
32-
.required();
37+
type PaswordResetFormFieldsType = z.infer<typeof passwordResetSchema>;
3338

3439
const PasswordReset = () => {
3540
const form = useForm<PaswordResetFormFieldsType>({
41+
resolver: zodResolver(passwordResetSchema),
3642
defaultValues: {
3743
email: "",
3844
},
39-
resolver: yupResolver(passwordResetSchema),
4045
});
4146
const firebase = useFirebase();
4247
const {
@@ -55,7 +60,7 @@ const PasswordReset = () => {
5560
}
5661

5762
return (
58-
<Fragment>
63+
<Form {...form}>
5964
<form
6065
className="m-auto flex max-w-md flex-col gap-4"
6166
onSubmit={form.handleSubmit((data) =>
@@ -65,18 +70,24 @@ const PasswordReset = () => {
6570
<h2 className="font-tall text-2xl uppercase md:text-4xl">
6671
Reset your passrword
6772
</h2>
68-
<div className="flex flex-col gap-2">
69-
<input
70-
{...form.register("email")}
71-
type="email"
72-
className="border border-stone-500 p-2"
73-
placeholder="email@example.com"
74-
/>
75-
<Message
76-
message={form.formState.errors.email?.message}
77-
type="error"
78-
/>
79-
</div>
73+
<FormField
74+
control={form.control}
75+
name="email"
76+
render={({ field }) => (
77+
<FormItem>
78+
<FormLabel>Email</FormLabel>
79+
<FormControl>
80+
<Input
81+
{...field}
82+
type="email"
83+
placeholder="email@example.com"
84+
/>
85+
</FormControl>
86+
<FormDescription>Enter your email address.</FormDescription>
87+
<FormMessage />
88+
</FormItem>
89+
)}
90+
/>
8091
<div>
8192
<Button className="uppercase" asChild>
8293
<input type="submit" value="send" />
@@ -93,7 +104,7 @@ const PasswordReset = () => {
93104
type={isError ? "error" : "info"}
94105
/>
95106
</form>
96-
</Fragment>
107+
</Form>
97108
);
98109
};
99110

0 commit comments

Comments
 (0)