{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "forgot-password",
  "title": "Forgot Password",
  "description": "A form component for requesting a password reset link via email.",
  "dependencies": [
    "@better-auth-ui/react@latest",
    "@better-auth-ui/core@latest",
    "better-auth",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "input",
    "field",
    "sonner",
    "spinner",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/components/auth/forgot-password.tsx",
      "content": "\"use client\"\n\nimport { getViewURL } from \"@better-auth-ui/core\"\nimport {\n  useAuth,\n  useFetchOptions,\n  useRequestPasswordReset\n} from \"@better-auth-ui/react\"\nimport { type SyntheticEvent, useState } from \"react\"\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 { Input } from \"@/components/ui/input\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"@/lib/utils\"\nimport { RESET_LINK_SENT_STORAGE_KEY } from \"./reset-link-sent\"\n\nexport type ForgotPasswordProps = {\n  className?: string\n}\n\n/**\n * Render a card-based \"Forgot Password\" form that sends a password-reset email.\n *\n * The form displays an email input, submit button, and a link back to sign-in.\n * After a successful request the submitted email is stored in `sessionStorage`\n * and the user is redirected to the reset-link-sent view, which offers to open\n * their email provider.\n *\n * @param className - Optional additional CSS class names applied to the card\n * @returns The forgot-password form UI as a JSX element\n */\nexport function ForgotPassword({ className }: ForgotPasswordProps) {\n  const {\n    authClient,\n    baseURL,\n    basePaths,\n    localization,\n    navigate,\n    plugins,\n    viewPaths,\n    Link\n  } = useAuth()\n\n  const { fetchOptions, resetFetchOptions } = useFetchOptions()\n\n  const { mutate: requestPasswordReset, isPending } = useRequestPasswordReset(\n    authClient,\n    {\n      onError: () => {\n        resetFetchOptions()\n      },\n      onSuccess: (_data, { email }) => {\n        sessionStorage.setItem(RESET_LINK_SENT_STORAGE_KEY, email)\n        navigate({ to: `${basePaths.auth}/${viewPaths.auth.resetLinkSent}` })\n      }\n    }\n  )\n\n  function handleSubmit(e: SyntheticEvent<HTMLFormElement>) {\n    e.preventDefault()\n    const formData = new FormData(e.currentTarget)\n    requestPasswordReset({\n      email: formData.get(\"email\") as string,\n      redirectTo: getViewURL(\n        baseURL,\n        basePaths.auth,\n        viewPaths.auth.resetPassword\n      ),\n      fetchOptions\n    })\n  }\n\n  const Captcha = plugins.find(\n    (plugin) => plugin.captchaComponent\n  )?.captchaComponent\n\n  const [fieldErrors, setFieldErrors] = useState<{\n    email?: string\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.forgotPassword}\n        </CardTitle>\n      </CardHeader>\n\n      <CardContent>\n        <form onSubmit={handleSubmit}>\n          <FieldGroup>\n            <Field data-invalid={!!fieldErrors.email}>\n              <FieldLabel htmlFor=\"email\">{localization.auth.email}</FieldLabel>\n\n              <Input\n                id=\"email\"\n                name=\"email\"\n                type=\"email\"\n                autoComplete=\"email\"\n                placeholder={localization.auth.emailPlaceholder}\n                required\n                disabled={isPending}\n                onChange={() => {\n                  setFieldErrors((prev) => ({\n                    ...prev,\n                    email: undefined\n                  }))\n                }}\n                onInvalid={(e) => {\n                  e.preventDefault()\n                  const el = e.target as HTMLInputElement\n                  const msg = el.validity.valueMissing\n                    ? localization.auth.fieldRequired\n                    : localization.auth.invalidEmail\n\n                  setFieldErrors((prev) => ({\n                    ...prev,\n                    email: msg\n                  }))\n                }}\n                aria-invalid={!!fieldErrors.email}\n              />\n\n              <FieldError>{fieldErrors.email}</FieldError>\n            </Field>\n\n            {Captcha && <div className=\"flex justify-center\">{Captcha}</div>}\n\n            <div className=\"flex flex-col gap-3\">\n              <Button type=\"submit\" disabled={isPending}>\n                {isPending && <Spinner />}\n\n                {localization.auth.sendResetLink}\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={`${basePaths.auth}/${viewPaths.auth.signIn}`}\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/forgot-password.tsx"
    },
    {
      "path": "src/components/auth/reset-link-sent.tsx",
      "content": "\"use client\"\n\nimport { getAuthLinkURL } from \"@better-auth-ui/core\"\nimport { useAuth } from \"@better-auth-ui/react\"\nimport { useEffect, useState } from \"react\"\n\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\"\nimport { FieldDescription } from \"@/components/ui/field\"\nimport { cn } from \"@/lib/utils\"\nimport { OpenEmailButton } from \"./open-email-button\"\nimport { useIsHydrated } from \"./use-is-hydrated\"\n\n/** `sessionStorage` key the forgot-password form stores the submitted email under. */\nexport const RESET_LINK_SENT_STORAGE_KEY = \"better-auth-ui.reset-link-sent\"\n\nexport type ResetLinkSentProps = {\n  className?: string\n}\n\n/**\n * Render a card confirming that a password-reset email was sent, with a\n * button to open the user's email provider.\n *\n * The target email is read from `sessionStorage` (set when the forgot-password\n * form redirects here); the OpenEmail button is only shown when an email is\n * stored and resolves to a known provider.\n *\n * @param className - Additional CSS classes applied to the card\n * @returns The reset-link-sent card React element\n */\nexport function ResetLinkSent({ className }: ResetLinkSentProps) {\n  const { basePaths, localization, redirectTo, viewPaths, Link } = useAuth()\n\n  const isHydrated = useIsHydrated()\n  const [email, setEmail] = useState(\n    (isHydrated && sessionStorage.getItem(RESET_LINK_SENT_STORAGE_KEY)) || \"\"\n  )\n\n  useEffect(() => {\n    setEmail(sessionStorage.getItem(RESET_LINK_SENT_STORAGE_KEY) ?? \"\")\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.checkYourEmailTitle}\n        </CardTitle>\n      </CardHeader>\n\n      <CardContent>\n        <div className=\"flex flex-col gap-4\">\n          <FieldDescription>\n            {email\n              ? localization.auth.resetLinkSentTo.replace(\"{{email}}\", email)\n              : localization.auth.passwordResetEmailSent}\n          </FieldDescription>\n\n          {email && <OpenEmailButton email={email} />}\n        </div>\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-link-sent.tsx"
    },
    {
      "path": "src/components/auth/open-email-button.tsx",
      "content": "\"use client\"\n\nimport { createQrCodeSvgData, getEmailProviderLink } from \"@better-auth-ui/core\"\nimport { useAuth } from \"@better-auth-ui/react\"\nimport type { VariantProps } from \"class-variance-authority\"\nimport { QrCode } from \"lucide-react\"\nimport { useMemo } from \"react\"\n\nimport { buttonVariants } from \"@/components/ui/button\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger\n} from \"@/components/ui/tooltip\"\nimport { cn } from \"@/lib/utils\"\n\nexport type OpenEmailButtonProps = {\n  /** Email address used to detect the provider, e.g. from the verify-email flow. */\n  email: string\n  className?: string\n  /**\n   * Button variant. Defaults to the primary style for dead-end views where\n   * opening the inbox is the only action; pass `\"secondary\"` where it sits\n   * beside a submit button that should stay the primary call to action.\n   */\n  variant?: VariantProps<typeof buttonVariants>[\"variant\"]\n}\n\n/**\n * Render a button that opens the user's email provider login page in a new\n * tab. Hovering or focusing the button reveals a QR code for opening the same\n * provider on another device.\n *\n * The provider is resolved from the email domain via the curated\n * `@mikkelscheike/email-provider-links` dataset (Gmail, Outlook, GMX, etc.).\n * Renders nothing when the domain is empty or not a known provider.\n *\n * @param email - Email address to resolve the provider from.\n * @param className - Additional CSS classes applied to the button.\n * @param variant - Button variant. Defaults to the primary style.\n * @returns The open-email button, or `null` when no provider matches.\n */\nexport function OpenEmailButton({\n  email,\n  className,\n  variant\n}: OpenEmailButtonProps) {\n  const { localization } = useAuth()\n\n  const provider = getEmailProviderLink(email)\n  const loginUrl = provider?.loginUrl\n  const qrCode = useMemo(\n    () => (loginUrl ? createQrCodeSvgData(loginUrl) : null),\n    [loginUrl]\n  )\n\n  if (!provider || !qrCode) return null\n\n  const scanLabel = localization.auth.scanToOpenEmailProvider.replace(\n    \"{{provider}}\",\n    provider.companyProvider\n  )\n\n  return (\n    <TooltipProvider>\n      <Tooltip>\n        <TooltipTrigger\n          type=\"button\"\n          className={cn(buttonVariants({ variant }), \"w-full\", className)}\n          onClick={() =>\n            window.open(provider.loginUrl, \"_blank\", \"noopener,noreferrer\")\n          }\n        >\n          {localization.auth.openEmailProvider.replace(\n            \"{{provider}}\",\n            provider.companyProvider\n          )}\n          <QrCode data-icon=\"inline-end\" />\n        </TooltipTrigger>\n        <TooltipContent\n          sideOffset={8}\n          className=\"flex-col items-center gap-2 p-3\"\n        >\n          <svg\n            viewBox={`0 0 ${qrCode.size} ${qrCode.size}`}\n            aria-hidden=\"true\"\n            focusable=\"false\"\n            className=\"size-40\"\n          >\n            <path fill=\"white\" d={`M0 0h${qrCode.size}v${qrCode.size}H0z`} />\n            <path fill=\"black\" d={qrCode.path} shapeRendering=\"crispEdges\" />\n          </svg>\n          <p className=\"max-w-40 text-center leading-snug\">{scanLabel}</p>\n        </TooltipContent>\n      </Tooltip>\n    </TooltipProvider>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/open-email-button.tsx"
    },
    {
      "path": "src/components/auth/use-is-hydrated.ts",
      "content": "\"use client\"\n\nimport { useSyncExternalStore } from \"react\"\n\n/**\n * Returns `true` once the component is mounted on the client (hydrated) and\n * `false` while rendering on the server, so client-only reads (e.g.\n * `sessionStorage`) stay safe during SSR.\n *\n * @returns Whether the component has hydrated on the client.\n */\nexport function useIsHydrated() {\n  const subscribe = () => () => {}\n  return useSyncExternalStore(\n    subscribe,\n    () => true,\n    () => false\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/auth/use-is-hydrated.ts"
    }
  ],
  "type": "registry:component"
}