Password validation with zod

This commit is contained in:
Ephraim Atta-Duncan
2023-06-07 12:33:33 +00:00
parent fe6561f596
commit 13a840ff78
3 changed files with 62 additions and 16 deletions

View File

@@ -4,21 +4,35 @@ import { useRouter } from "next/router";
import { Button } from "@documenso/ui"; import { Button } from "@documenso/ui";
import Logo from "./logo"; import Logo from "./logo";
import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { zodResolver } from "@hookform/resolvers/zod";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import * as z from "zod";
interface ResetPasswordForm { const schema = z
password: string; .object({
confirmPassword: string; password: z.string().min(8, { message: "Password must be at least 8 characters" }),
} confirmPassword: z.string().min(8, { message: "Password must be at least 8 characters" }),
})
.refine((data) => data.password === data.confirmPassword, {
path: ["confirmPassword"],
message: "Password don't match",
});
type ResetPasswordForm = z.infer<typeof schema>;
export default function ResetPassword() { export default function ResetPassword() {
const router = useRouter(); const router = useRouter();
const { token } = router.query; const { token } = router.query;
const methods = useForm<ResetPasswordForm>(); const methods = useForm<ResetPasswordForm>({
const { register, formState, watch } = methods; resolver: zodResolver(schema),
const password = watch("password", ""); });
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
} = methods;
const [resetSuccessful, setResetSuccessful] = useState(false); const [resetSuccessful, setResetSuccessful] = useState(false);
@@ -67,7 +81,7 @@ export default function ResetPassword() {
</div> </div>
{resetSuccessful ? null : ( {resetSuccessful ? null : (
<FormProvider {...methods}> <FormProvider {...methods}>
<form className="mt-8 space-y-6" onSubmit={methods.handleSubmit(onSubmit)}> <form className="mt-8 space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="-space-y-px rounded-md shadow-sm"> <div className="-space-y-px rounded-md shadow-sm">
<div> <div>
<label htmlFor="password" className="sr-only"> <label htmlFor="password" className="sr-only">
@@ -89,10 +103,7 @@ export default function ResetPassword() {
Password Password
</label> </label>
<input <input
{...register("confirmPassword", { {...register("confirmPassword")}
required: "Please confirm password",
validate: (value) => value === password || "The passwords do not match",
})}
id="confirmPassword" id="confirmPassword"
name="confirmPassword" name="confirmPassword"
type="password" type="password"
@@ -103,10 +114,14 @@ export default function ResetPassword() {
</div> </div>
</div> </div>
{errors && (
<span className="text-xs text-red-500">{errors.confirmPassword?.message}</span>
)}
<div> <div>
<Button <Button
type="submit" type="submit"
disabled={formState.isSubmitting} disabled={isSubmitting}
className="group relative flex w-full"> className="group relative flex w-full">
Reset password Reset password
</Button> </Button>

31
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@documenso/prisma": "*", "@documenso/prisma": "*",
"@headlessui/react": "^1.7.4", "@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13", "@heroicons/react": "^2.0.13",
"@hookform/resolvers": "^3.1.0",
"avatar-from-initials": "^1.0.3", "avatar-from-initials": "^1.0.3",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"next": "13.2.4", "next": "13.2.4",
@@ -24,7 +25,8 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.41.5", "react-hook-form": "^7.41.5",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-signature-canvas": "^1.0.6" "react-signature-canvas": "^1.0.6",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
@@ -525,6 +527,14 @@
"react": ">= 16" "react": ">= 16"
} }
}, },
"node_modules/@hookform/resolvers": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.1.0.tgz",
"integrity": "sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA==",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.8", "version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -8048,6 +8058,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"packages/features": { "packages/features": {
"name": "@documenso/features", "name": "@documenso/features",
"version": "0.0.0" "version": "0.0.0"
@@ -8507,6 +8525,12 @@
"integrity": "sha512-x89rFxH3SRdYaA+JCXwfe+RkE1SFTo9GcOkZettHer71Y3T7V+ogKmfw5CjTazgS3d0ClJ7p1NA+SP7VQLQcLw==", "integrity": "sha512-x89rFxH3SRdYaA+JCXwfe+RkE1SFTo9GcOkZettHer71Y3T7V+ogKmfw5CjTazgS3d0ClJ7p1NA+SP7VQLQcLw==",
"requires": {} "requires": {}
}, },
"@hookform/resolvers": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.1.0.tgz",
"integrity": "sha512-z0A8K+Nxq+f83Whm/ajlwE6VtQlp/yPHZnXw7XWVPIGm1Vx0QV8KThU3BpbBRfAZ7/dYqCKKBNnQh85BkmBKkA==",
"requires": {}
},
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.11.8", "version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -14097,6 +14121,11 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true "dev": true
},
"zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
} }
} }
} }

View File

@@ -26,6 +26,7 @@
"@documenso/prisma": "*", "@documenso/prisma": "*",
"@headlessui/react": "^1.7.4", "@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13", "@heroicons/react": "^2.0.13",
"@hookform/resolvers": "^3.1.0",
"avatar-from-initials": "^1.0.3", "avatar-from-initials": "^1.0.3",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"next": "13.2.4", "next": "13.2.4",
@@ -35,7 +36,8 @@
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.41.5", "react-hook-form": "^7.41.5",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-signature-canvas": "^1.0.6" "react-signature-canvas": "^1.0.6",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
@@ -54,4 +56,4 @@
"turbo": "^1.9.9", "turbo": "^1.9.9",
"typescript": "4.8.4" "typescript": "4.8.4"
} }
} }