I'm working on a password reset request form in my Next.js project. Here's the code I have:
"use client";
import * as React from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { cn } from "@/lib/util";
import { forgotPasswordSchema } from "@/lib/validations/auth";
import { buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Icons } from "@/components/icons";
import { useRouter } from "next/navigation";
import { requestPasswordReset } from "@/lib/request-password-reset";
export type FormData = z.infer<typeof forgotPasswordSchema>;
export function RequestPasswordResetForm() {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<FormData>({ resolver: zodResolver(forgotPasswordSchema) });
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | null>(null);
const router = useRouter();
async function onSubmit(submittedData: FormData) {
await requestPasswordReset({
submittedData,
setError,
setIsLoading,
reset,
router,
});
}
return (
<div>
{error && (
<p className="mb-5 text-sm text-red-500 animate-in fade-in-0 slide-in-from-left-1">
{error}
</p>
)}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid gap-6">
<div className="grid gap-1">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="224c434f4762475a434f524e470c414d4f">[email protected]</a>"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading}
{...register("email")}
/>
{errors.email && (
<p className="px-1 text-xs text-red-600 animate-in fade-in-0 slide-in-from-left-1">
{errors.email.message}
</p>
)}
</div>
<button className={cn(buttonVariants())} disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Request password reset
</button>
</div>
</form>
</div>
);
}
In the onSubmit
handler, I call the requestPasswordReset()
function, which includes router
as an argument.
Here's the code for the requestPasswordReset()
function:
import type { FormData } from "@/components/request-password-reset-form";
const RESPONSE_MESSAGES = {
USER_NOT_FOUND: "User not found",
EMAIL_NOT_VERIFIED: "User not registered",
PASSWORD_RESET_LINK_SENT: "Password reset link sent",
REQUEST_PASSWORD_RESET_FAILURE:
"Password reset request failed. Please try again.",
};
interface RequestPasswordResetProps {
submittedData: FormData;
setError: React.Dispatch<React.SetStateAction<string | null>>;
reset: () => void;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
router: any;
}
export async function requestPasswordReset({
submittedData,
setError,
reset,
setIsLoading,
router,
}: RequestPasswordResetProps) {
setIsLoading(true);
setError(null);
try {
// Attempt to reset the user password
const resetResponse = await fetch("/api/request-password-reset", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(submittedData),
});
// Reset failed
if (!resetResponse.ok) {
throw new Error(RESPONSE_MESSAGES.PASSWORD_RESET_FAILURE);
}
const resetResponseData = await resetResponse.json();
// Handle different server responses
if (resetResponseData.message === RESPONSE_MESSAGES.USER_NOT_FOUND) {
setError(RESPONSE_MESSAGES.USER_NOT_FOUND);
reset();
return;
}
if (resetResponseData.message === RESPONSE_MESSAGES.EMAIL_NOT_VERIFIED) {
setError(RESPONSE_MESSAGES.EMAIL_NOT_VERIFIED);
reset();
return;
}
} catch (error) {
setError((error as Error).message);
} finally {
setIsLoading(false);
}
}
Inside the RequestPasswordResetProps
, I used router
typed as any
. What would be the proper way to annotate the router
instance?
Thank you!