{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "verify-email",
  "title": "Verify Email",
  "description": "A page prompting the user to verify their email, with a button to open their email provider and a resend button rate-limited by a cooldown timer.",
  "dependencies": [
    "@better-auth-ui/react@latest",
    "@better-auth-ui/core@latest",
    "better-auth",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "field",
    "sonner",
    "spinner",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/components/auth/verify-email.tsx",
      "content": "\"use client\"\n\nimport { useAuth, useSendVerificationEmail } from \"@better-auth-ui/react\"\nimport { 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 { FieldDescription } from \"@/components/ui/field\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { cn } from \"@/lib/utils\"\nimport { OpenEmailButton } from \"./open-email-button\"\nimport { useIsHydrated } from \"./use-is-hydrated\"\n\nexport type VerifyEmailProps = {\n  className?: string\n}\n\n/** Seconds the resend button stays disabled to prevent spamming the endpoint. */\nconst RESEND_COOLDOWN_SECONDS = 60\n\n/**\n * Render a card prompting the user to verify their email, with a resend button\n * that is rate-limited by a cooldown timer.\n *\n * The target email is read from `sessionStorage` (set when sign-up or sign-in\n * redirects here); the OpenEmail/Resend controls are only shown when an email\n * is stored. The resend button is disabled while a cooldown is active and shows\n * the remaining seconds.\n *\n * @param className - Additional CSS classes applied to the card\n * @returns The verify-email card React element\n */\nexport function VerifyEmail({ className }: VerifyEmailProps) {\n  const {\n    authClient,\n    basePaths,\n    baseURL,\n    localization,\n    redirectTo,\n    viewPaths,\n    Link\n  } = useAuth()\n\n  const isHydrated = useIsHydrated()\n  const [email, setEmail] = useState(\n    (isHydrated && sessionStorage.getItem(\"better-auth-ui.verify-email\")) || \"\"\n  )\n  const [cooldown, setCooldown] = useState(RESEND_COOLDOWN_SECONDS)\n\n  useEffect(() => {\n    setEmail(sessionStorage.getItem(\"better-auth-ui.verify-email\") ?? \"\")\n  }, [])\n\n  useEffect(() => {\n    if (cooldown <= 0 || !email) return\n\n    const interval = setInterval(() => {\n      setCooldown((current) => (current > 0 ? current - 1 : 0))\n    }, 1000)\n\n    return () => clearInterval(interval)\n  }, [cooldown, email])\n\n  const { mutate: sendVerificationEmail, isPending } = useSendVerificationEmail(\n    authClient,\n    {\n      onSuccess: () => {\n        toast.success(localization.auth.verificationEmailSent)\n        setCooldown(RESEND_COOLDOWN_SECONDS)\n      }\n    }\n  )\n\n  const isCoolingDown = cooldown > 0\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.verifyEmail}\n        </CardTitle>\n      </CardHeader>\n\n      <CardContent>\n        <div className=\"flex flex-col gap-4\">\n          <FieldDescription>\n            {localization.auth.checkYourEmail}\n          </FieldDescription>\n\n          {email && (\n            <div className=\"flex flex-col gap-3\">\n              <OpenEmailButton email={email} />\n\n              <Button\n                type=\"button\"\n                variant=\"outline\"\n                disabled={!email || isCoolingDown || isPending}\n                onClick={() =>\n                  sendVerificationEmail({\n                    email,\n                    callbackURL: `${baseURL}${redirectTo}`\n                  })\n                }\n              >\n                {isPending && <Spinner />}\n\n                {isCoolingDown\n                  ? localization.auth.resendIn.replace(\n                      \"{{seconds}}\",\n                      String(cooldown)\n                    )\n                  : localization.auth.resend}\n              </Button>\n            </div>\n          )}\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.alreadyVerifiedYourEmail}{\" \"}\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/verify-email.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"
}