Files
DetnalCare/app/(auth)/reset-password/reset-password-form.tsx
Iliyan Angelov 39077550ef Dental Care
2025-11-16 14:29:51 +02:00

255 lines
9.3 KiB
TypeScript

"use client";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { toast } from "sonner";
import { useState } from "react";
import { Loader2 } from "lucide-react";
import Link from "next/link";
import { authClient } from "@/lib/auth-session/auth-client";
import { useRouter, useSearchParams } from "next/navigation";
export function ResetPasswordForm({
className,
...props
}: React.ComponentProps<"div">) {
const searchParams = useSearchParams();
const router = useRouter();
const token = searchParams.get("token");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [isLoading, setIsLoading] = useState(false);
function togglePassword(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
setShowPassword((s) => !s);
}
function toggleConfirm(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
setShowConfirm((s) => !s);
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsLoading(true);
if (!token) {
toast.error("Invalid or missing reset token");
setIsLoading(false);
return;
}
if (password.length < 8) {
toast.error("Password must be at least 8 characters");
setIsLoading(false);
return;
}
if (password !== confirmPassword) {
toast.error("Passwords do not match");
setIsLoading(false);
return;
}
const { error } = await authClient.resetPassword({
newPassword: password,
token,
});
if (error) {
toast.error(error.message || "Failed to reset password");
} else {
toast.success("Password reset successfully");
router.push("/sign-in");
}
setIsLoading(false);
}
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<CardHeader className="text-center">
<CardTitle className="text-xl">Reset Password</CardTitle>
<CardDescription>Enter your new password</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit}>
<FieldGroup>
<Field>
<FieldLabel htmlFor="password">Password</FieldLabel>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your new password"
disabled={isLoading}
minLength={8}
required
/>
<button
aria-label={
showPassword ? "Hide password" : "Show password"
}
onClick={togglePassword}
type="button"
className="absolute right-2 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 text-sm opacity-70 hover:opacity-100"
>
{showPassword ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-5.523 0-10-4.477-10-10a9.97 9.97 0 012.175-5.875M6.343 6.343A9.97 9.97 0 0112 5c5.523 0 10 4.477 10 10 0 1.042-.161 2.045-.463 2.998M3 3l18 18"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
)}
</button>
</div>
<p className="text-xs text-muted-foreground mt-1">
Must be at least 8 characters
</p>
</Field>
<Field>
<FieldLabel htmlFor="confirmPassword">
Confirm Password
</FieldLabel>
<div className="relative">
<Input
id="confirmPassword"
type={showConfirm ? "text" : "password"}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Confirm your new password"
disabled={isLoading}
minLength={8}
required
/>
<button
aria-label={
showConfirm
? "Hide confirm password"
: "Show confirm password"
}
onClick={toggleConfirm}
type="button"
className="absolute right-2 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 text-sm opacity-70 hover:opacity-100"
>
{showConfirm ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-5.523 0-10-4.477-10-10a9.97 9.97 0 012.175-5.875M6.343 6.343A9.97 9.97 0 0112 5c5.523 0 10 4.477 10 10 0 1.042-.161 2.045-.463 2.998M3 3l18 18"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
)}
</button>
</div>
</Field>
<Field>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="size-4 animate-spin mr-2" />
Resetting...
</>
) : (
"Reset Password"
)}
</Button>
</Field>
<div className="text-center text-sm">
Don&apos;t have an account?{" "}
<Link href="/sign-up" className="underline underline-offset-4">
Sign up
</Link>
</div>
</FieldGroup>
</form>
</CardContent>
</Card>
<div className="text-muted-foreground *:[a]:hover:text-primary text-center text-xs text-balance *:[a]:underline *:[a]:underline-offset-4">
By clicking continue, you agree to our{" "}
<Link href="/docs/terms-of-service">Terms of Service</Link> and{" "}
<Link href="/docs/privacy-policy">Privacy Policy</Link>.
</div>
</div>
);
}