{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reset-password",
  "title": "Reset Password",
  "description": "A form component for resetting password with a token from the URL.",
  "dependencies": [
    "@better-auth-ui/react@latest",
    "@better-auth-ui/core@latest",
    "better-auth",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "field",
    "input-group",
    "sonner",
    "spinner"
  ],
  "files": [
    {
      "path": "src/components/auth/reset-password.tsx",
      "content": "\"use client\"\n\nimport { getAuthLinkURL } from \"@better-auth-ui/core\"\nimport { useAuth, useResetPassword } from \"@better-auth-ui/react\"\nimport { Eye, EyeOff } from \"lucide-react\"\nimport { type SyntheticEvent, useEffect, useState } from \"react\"\nimport { toast } from \"sonner\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel\n} from \"@/components/ui/field\"\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput\n} from \"@/components/ui/input-group\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"@/lib/utils\"\n\nexport type ResetPasswordProps = {\n  className?: string\n}\n\n/**\n * Render a password reset form that validates the reset token from the URL, accepts a new password (and optional confirmation), and submits it to the auth client.\n *\n * The component checks for a `token` query parameter on mount and, if missing, shows an error toast and navigates to the sign-in page. It exposes per-field validation messages, toggles for password visibility, and disables inputs while the reset request is pending.\n *\n * @returns The password reset form UI ready to be mounted in the app layout.\n */\nexport function ResetPassword({ className }: ResetPasswordProps) {\n  const {\n    authClient,\n    basePaths,\n    emailAndPassword,\n    localization,\n    navigate,\n    redirectTo,\n    viewPaths,\n    Link\n  } = useAuth()\n  const signInURL = getAuthLinkURL(\n    `${basePaths.auth}/${viewPaths.auth.signIn}`,\n    redirectTo\n  )\n\n  const { mutate: resetPassword, isPending } = useResetPassword(authClient, {\n    onSuccess: () => {\n      toast.success(localization.auth.passwordResetSuccess)\n      navigate({ to: signInURL })\n    }\n  })\n\n  const [isPasswordVisible, setIsPasswordVisible] = useState(false)\n  const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =\n    useState(false)\n\n  const [fieldErrors, setFieldErrors] = useState<{\n    password?: string\n    confirmPassword?: string\n  }>({})\n\n  useEffect(() => {\n    const searchParams = new URLSearchParams(window.location.search)\n    const token = searchParams.get(\"token\") as string\n\n    if (!token) {\n      toast.error(localization.auth.invalidResetPasswordToken)\n      navigate({ to: signInURL })\n    }\n  }, [localization.auth.invalidResetPasswordToken, navigate, signInURL])\n\n  function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {\n    e.preventDefault()\n\n    const searchParams = new URLSearchParams(window.location.search)\n    const token = searchParams.get(\"token\") as string\n\n    if (!token) {\n      toast.error(localization.auth.invalidResetPasswordToken)\n      navigate({ to: signInURL })\n      return\n    }\n\n    const formData = new FormData(e.currentTarget)\n    const password = formData.get(\"password\") as string\n    const confirmPassword = formData.get(\"confirmPassword\") as string\n\n    if (emailAndPassword?.confirmPassword && password !== confirmPassword) {\n      toast.error(localization.auth.passwordsDoNotMatch)\n      return\n    }\n\n    resetPassword({ token, newPassword: password })\n  }\n\n  return (\n    <Card className={cn(\"w-full max-w-sm\", className)}>\n      <CardHeader>\n        <CardTitle className=\"text-xl font-semibold\">\n          {localization.auth.resetPassword}\n        </CardTitle>\n      </CardHeader>\n\n      <CardContent>\n        <form onSubmit={handleSubmit}>\n          <FieldGroup>\n            <Field data-invalid={!!fieldErrors.password}>\n              <FieldLabel htmlFor=\"password\">\n                {localization.auth.password}\n              </FieldLabel>\n\n              <InputGroup>\n                <InputGroupInput\n                  id=\"password\"\n                  name=\"password\"\n                  type={isPasswordVisible ? \"text\" : \"password\"}\n                  autoComplete=\"new-password\"\n                  placeholder={localization.auth.newPasswordPlaceholder}\n                  required\n                  minLength={emailAndPassword?.minPasswordLength}\n                  maxLength={emailAndPassword?.maxPasswordLength}\n                  disabled={isPending}\n                  onChange={() => {\n                    setFieldErrors((prev) => ({\n                      ...prev,\n                      password: undefined\n                    }))\n                  }}\n                  onInvalid={(e) => {\n                    e.preventDefault()\n                    const el = e.target as HTMLInputElement\n                    const min = emailAndPassword?.minPasswordLength\n                    const max = emailAndPassword?.maxPasswordLength\n                    const msg = el.validity.valueMissing\n                      ? localization.auth.fieldRequired\n                      : el.validity.tooShort\n                        ? localization.auth.tooShort.replace(\n                            \"{{min}}\",\n                            String(min)\n                          )\n                        : localization.auth.tooLong.replace(\n                            \"{{max}}\",\n                            String(max)\n                          )\n\n                    setFieldErrors((prev) => ({\n                      ...prev,\n                      password: msg\n                    }))\n                  }}\n                  aria-invalid={!!fieldErrors.password}\n                />\n\n                <InputGroupAddon align=\"inline-end\">\n                  <InputGroupButton\n                    size=\"icon-xs\"\n                    aria-label={\n                      isPasswordVisible\n                        ? localization.auth.hidePassword\n                        : localization.auth.showPassword\n                    }\n                    title={\n                      isPasswordVisible\n                        ? localization.auth.hidePassword\n                        : localization.auth.showPassword\n                    }\n                    onClick={() => {\n                      setIsPasswordVisible((visible) => !visible)\n                    }}\n                  >\n                    {isPasswordVisible ? <EyeOff /> : <Eye />}\n                  </InputGroupButton>\n                </InputGroupAddon>\n              </InputGroup>\n\n              <FieldError>{fieldErrors.password}</FieldError>\n            </Field>\n\n            {emailAndPassword?.confirmPassword && (\n              <Field data-invalid={!!fieldErrors.confirmPassword}>\n                <FieldLabel htmlFor=\"confirmPassword\">\n                  {localization.auth.confirmPassword}\n                </FieldLabel>\n\n                <InputGroup>\n                  <InputGroupInput\n                    id=\"confirmPassword\"\n                    name=\"confirmPassword\"\n                    type={isConfirmPasswordVisible ? \"text\" : \"password\"}\n                    autoComplete=\"new-password\"\n                    placeholder={localization.auth.confirmPasswordPlaceholder}\n                    required\n                    minLength={emailAndPassword?.minPasswordLength}\n                    maxLength={emailAndPassword?.maxPasswordLength}\n                    disabled={isPending}\n                    onChange={() => {\n                      setFieldErrors((prev) => ({\n                        ...prev,\n                        confirmPassword: undefined\n                      }))\n                    }}\n                    onInvalid={(e) => {\n                      e.preventDefault()\n                      const el = e.target as HTMLInputElement\n                      const min = emailAndPassword?.minPasswordLength\n                      const max = emailAndPassword?.maxPasswordLength\n                      const msg = el.validity.valueMissing\n                        ? localization.auth.fieldRequired\n                        : el.validity.tooShort\n                          ? localization.auth.tooShort.replace(\n                              \"{{min}}\",\n                              String(min)\n                            )\n                          : localization.auth.tooLong.replace(\n                              \"{{max}}\",\n                              String(max)\n                            )\n\n                      setFieldErrors((prev) => ({\n                        ...prev,\n                        confirmPassword: msg\n                      }))\n                    }}\n                    aria-invalid={!!fieldErrors.confirmPassword}\n                  />\n\n                  <InputGroupAddon align=\"inline-end\">\n                    <InputGroupButton\n                      size=\"icon-xs\"\n                      aria-label={\n                        isConfirmPasswordVisible\n                          ? localization.auth.hidePassword\n                          : localization.auth.showPassword\n                      }\n                      title={\n                        isConfirmPasswordVisible\n                          ? localization.auth.hidePassword\n                          : localization.auth.showPassword\n                      }\n                      onClick={() => {\n                        setIsConfirmPasswordVisible((visible) => !visible)\n                      }}\n                    >\n                      {isConfirmPasswordVisible ? <EyeOff /> : <Eye />}\n                    </InputGroupButton>\n                  </InputGroupAddon>\n                </InputGroup>\n\n                <FieldError>{fieldErrors.confirmPassword}</FieldError>\n              </Field>\n            )}\n\n            <div className=\"flex flex-col gap-3\">\n              <Button type=\"submit\" disabled={isPending}>\n                {isPending && <Spinner />}\n\n                {localization.auth.resetPassword}\n              </Button>\n            </div>\n          </FieldGroup>\n        </form>\n\n        <div className=\"flex flex-col gap-3 items-center w-full mt-4\">\n          <FieldDescription className=\"text-center\">\n            {localization.auth.rememberYourPassword}{\" \"}\n            <Link\n              href={getAuthLinkURL(\n                `${basePaths.auth}/${viewPaths.auth.signIn}`,\n                redirectTo\n              )}\n              className=\"underline underline-offset-4\"\n            >\n              {localization.auth.signIn}\n            </Link>\n          </FieldDescription>\n        </div>\n      </CardContent>\n    </Card>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/reset-password.tsx"
    }
  ],
  "type": "registry:component"
}