{"content":"# Better Auth Components\n\nAdd UI components and pages for authentication flows including sign in, sign up, forgot password, reset password, and email verification.\n\n### Add Toaster to layout\n\nUpdate your layout to include the toast notification provider:\n\n```tsx\n// src/app/layout.tsx\nimport { Toaster } from \"@/components/ui/sonner\";\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"en\">\n      <body>\n        {children}\n        <Toaster richColors position=\"top-center\" />\n      </body>\n    </html>\n  );\n}\n```\n\n---\n\n## UI Components\n\n### Sign In Component\n\nThis component handles email/password sign-in with password visibility toggle and input icons. When `requireEmailVerification` is enabled (see [Better Auth Emails](/recipes/better-auth-emails)), it includes an inline resend verification option.\n\n```tsx\n// src/components/auth/sign-in.tsx\n\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n  CardDescription,\n  CardFooter,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { useState } from \"react\";\nimport {\n  Loader2,\n  Mail,\n  Lock,\n  Eye,\n  EyeOff,\n  AlertCircle,\n  Send,\n} from \"lucide-react\";\nimport { signIn, authClient } from \"@/lib/auth/client\";\nimport Link from \"next/link\";\nimport { toast } from \"sonner\";\nimport { useRouter } from \"next/navigation\";\n\nexport function SignIn() {\n  const [email, setEmail] = useState(\"\");\n  const [password, setPassword] = useState(\"\");\n  const [showPassword, setShowPassword] = useState(false);\n  const [loading, setLoading] = useState(false);\n  const [rememberMe, setRememberMe] = useState(false);\n  const [emailNotVerified, setEmailNotVerified] = useState(false);\n  const [resendLoading, setResendLoading] = useState(false);\n  const router = useRouter();\n\n  const handleResendVerification = async () => {\n    if (!email) {\n      toast.error(\"Please enter your email address\");\n      return;\n    }\n\n    setResendLoading(true);\n    try {\n      const { error } = await authClient.sendVerificationEmail({\n        email,\n        callbackURL: \"/sign-in\",\n      });\n\n      if (error) {\n        toast.error(error.message);\n        return;\n      }\n\n      toast.success(\"Verification email sent! Please check your inbox.\");\n    } finally {\n      setResendLoading(false);\n    }\n  };\n\n  const handleSubmit = async (e: React.FormEvent) => {\n    e.preventDefault();\n    setEmailNotVerified(false);\n\n    if (!email || !password) {\n      toast.error(\"Please fill in all fields\");\n      return;\n    }\n\n    await signIn.email(\n      {\n        email,\n        password,\n        rememberMe,\n        callbackURL: \"/chats\",\n      },\n      {\n        onRequest: () => setLoading(true),\n        onResponse: () => setLoading(false),\n        onError: (ctx) => {\n          const errorMessage = ctx.error.message.toLowerCase();\n          if (\n            errorMessage.includes(\"email not verified\") ||\n            errorMessage.includes(\"verify your email\")\n          ) {\n            setEmailNotVerified(true);\n          } else {\n            toast.error(ctx.error.message);\n          }\n        },\n        onSuccess: () => router.push(\"/chats\"),\n      },\n    );\n  };\n\n  return (\n    <Card className=\"w-full max-w-md shadow-lg\">\n      <CardHeader className=\"space-y-1 text-center\">\n        <CardTitle className=\"text-2xl font-bold\">Welcome back</CardTitle>\n        <CardDescription>\n          Enter your credentials to access your account\n        </CardDescription>\n      </CardHeader>\n      <CardContent className=\"space-y-4\">\n        {emailNotVerified && (\n          <div className=\"rounded-lg border border-amber-600/30 bg-amber-950/50 p-4\">\n            <div className=\"flex items-start gap-3\">\n              <AlertCircle className=\"size-5 text-amber-500 shrink-0 mt-0.5\" />\n              <div className=\"flex flex-col gap-2\">\n                <p className=\"font-medium text-amber-200\">Email not verified</p>\n                <p className=\"text-sm text-amber-200/70\">\n                  Please verify your email address before signing in. Check your\n                  inbox for a verification link.\n                </p>\n                <Button\n                  type=\"button\"\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  className=\"w-fit text-amber-400 hover:text-amber-300 hover:bg-amber-500/10 px-0\"\n                  onClick={handleResendVerification}\n                  disabled={resendLoading}\n                >\n                  {resendLoading ? (\n                    <Loader2 className=\"size-4 animate-spin\" />\n                  ) : (\n                    <Send className=\"size-4\" />\n                  )}\n                  Resend verification email\n                </Button>\n              </div>\n            </div>\n          </div>\n        )}\n        <form onSubmit={handleSubmit} className=\"space-y-4\">\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"email\">Email</Label>\n            <div className=\"relative\">\n              <Mail className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"email\"\n                type=\"email\"\n                placeholder=\"you@example.com\"\n                className=\"pl-10\"\n                value={email}\n                onChange={(e) => setEmail(e.target.value)}\n                autoComplete=\"email\"\n                required\n              />\n            </div>\n          </div>\n          <div className=\"space-y-2\">\n            <div className=\"flex items-center justify-between\">\n              <Label htmlFor=\"password\">Password</Label>\n              <Link\n                href=\"/forgot-password\"\n                className=\"text-sm text-primary hover:underline\"\n              >\n                Forgot password?\n              </Link>\n            </div>\n            <div className=\"relative\">\n              <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"password\"\n                type={showPassword ? \"text\" : \"password\"}\n                placeholder=\"Enter your password\"\n                className=\"pl-10 pr-10\"\n                value={password}\n                onChange={(e) => setPassword(e.target.value)}\n                autoComplete=\"current-password\"\n                required\n              />\n              <button\n                type=\"button\"\n                onClick={() => setShowPassword(!showPassword)}\n                className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n              >\n                {showPassword ? (\n                  <EyeOff className=\"size-4\" />\n                ) : (\n                  <Eye className=\"size-4\" />\n                )}\n              </button>\n            </div>\n          </div>\n          <div className=\"flex items-center gap-2\">\n            <Checkbox\n              id=\"remember\"\n              checked={rememberMe}\n              onCheckedChange={(checked) => setRememberMe(checked === true)}\n            />\n            <Label htmlFor=\"remember\" className=\"text-sm font-normal\">\n              Remember me for 30 days\n            </Label>\n          </div>\n          <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n            {loading ? <Loader2 className=\"size-4 animate-spin\" /> : \"Sign in\"}\n          </Button>\n        </form>\n      </CardContent>\n      <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n        <p className=\"text-center text-sm text-muted-foreground\">\n          Don&apos;t have an account?{\" \"}\n          <Link\n            href=\"/sign-up\"\n            className=\"text-primary font-medium hover:underline\"\n          >\n            Create account\n          </Link>\n        </p>\n      </CardFooter>\n    </Card>\n  );\n}\n```\n\n### Sign Up Component\n\n```tsx\n// src/components/auth/sign-up.tsx\n\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { useState } from \"react\";\nimport Image from \"next/image\";\nimport {\n  Loader2,\n  X,\n  Mail,\n  Lock,\n  User,\n  Eye,\n  EyeOff,\n  Upload,\n} from \"lucide-react\";\nimport { signUp } from \"@/lib/auth/client\";\nimport { toast } from \"sonner\";\nimport { useRouter } from \"next/navigation\";\nimport Link from \"next/link\";\n\nexport function SignUp() {\n  const [firstName, setFirstName] = useState(\"\");\n  const [lastName, setLastName] = useState(\"\");\n  const [email, setEmail] = useState(\"\");\n  const [password, setPassword] = useState(\"\");\n  const [passwordConfirmation, setPasswordConfirmation] = useState(\"\");\n  const [showPassword, setShowPassword] = useState(false);\n  const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n  const [image, setImage] = useState<File | null>(null);\n  const [imagePreview, setImagePreview] = useState<string | null>(null);\n  const [loading, setLoading] = useState(false);\n  const [success, setSuccess] = useState(false);\n  const router = useRouter();\n\n  const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const file = e.target.files?.[0];\n    if (file) {\n      setImage(file);\n      const reader = new FileReader();\n      reader.onloadend = () => {\n        setImagePreview(reader.result as string);\n      };\n      reader.readAsDataURL(file);\n    }\n  };\n\n  const handleSubmit = async (e: React.FormEvent) => {\n    e.preventDefault();\n\n    if (!firstName || !lastName || !email || !password) {\n      toast.error(\"Please fill in all required fields\");\n      return;\n    }\n\n    if (password.length < 8) {\n      toast.error(\"Password must be at least 8 characters\");\n      return;\n    }\n\n    if (password !== passwordConfirmation) {\n      toast.error(\"Passwords do not match\");\n      return;\n    }\n\n    await signUp.email(\n      {\n        email,\n        password,\n        name: `${firstName} ${lastName}`.trim(),\n        image: image ? await convertImageToBase64(image) : undefined,\n      },\n      {\n        onRequest: () => setLoading(true),\n        onResponse: () => setLoading(false),\n        onError: (ctx) => {\n          toast.error(ctx.error.message);\n        },\n        onSuccess: () => {\n          setSuccess(true);\n          toast.success(\"Account created! Please check your email to verify.\");\n        },\n      },\n    );\n  };\n\n  if (success) {\n    return (\n      <Card className=\"w-full max-w-md shadow-lg\">\n        <CardHeader className=\"space-y-1 text-center\">\n          <div className=\"mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-primary/10\">\n            <Mail className=\"size-8 text-primary\" />\n          </div>\n          <CardTitle className=\"text-2xl font-bold\">Check your email</CardTitle>\n          <CardDescription className=\"text-base\">\n            We&apos;ve sent a verification link to <strong>{email}</strong>\n          </CardDescription>\n        </CardHeader>\n        <CardContent className=\"text-center text-sm text-muted-foreground\">\n          <p>\n            Click the link in your email to verify your account and start using\n            the app.\n          </p>\n        </CardContent>\n        <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n          <Button\n            variant=\"outline\"\n            className=\"w-full\"\n            onClick={() => router.push(\"/sign-in\")}\n          >\n            Back to sign in\n          </Button>\n        </CardFooter>\n      </Card>\n    );\n  }\n\n  return (\n    <Card className=\"w-full max-w-md shadow-lg\">\n      <CardHeader className=\"space-y-1 text-center\">\n        <CardTitle className=\"text-2xl font-bold\">Create an account</CardTitle>\n        <CardDescription>Enter your details to get started</CardDescription>\n      </CardHeader>\n      <CardContent className=\"space-y-4\">\n        <form onSubmit={handleSubmit} className=\"space-y-4\">\n          <div className=\"grid grid-cols-2 gap-4\">\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"first-name\">First name</Label>\n              <div className=\"relative\">\n                <User className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n                <Input\n                  id=\"first-name\"\n                  placeholder=\"John\"\n                  className=\"pl-10\"\n                  value={firstName}\n                  onChange={(e) => setFirstName(e.target.value)}\n                  required\n                />\n              </div>\n            </div>\n            <div className=\"space-y-2\">\n              <Label htmlFor=\"last-name\">Last name</Label>\n              <Input\n                id=\"last-name\"\n                placeholder=\"Doe\"\n                value={lastName}\n                onChange={(e) => setLastName(e.target.value)}\n                required\n              />\n            </div>\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"email\">Email</Label>\n            <div className=\"relative\">\n              <Mail className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"email\"\n                type=\"email\"\n                placeholder=\"you@example.com\"\n                className=\"pl-10\"\n                value={email}\n                onChange={(e) => setEmail(e.target.value)}\n                autoComplete=\"email\"\n                required\n              />\n            </div>\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"password\">Password</Label>\n            <div className=\"relative\">\n              <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"password\"\n                type={showPassword ? \"text\" : \"password\"}\n                placeholder=\"At least 8 characters\"\n                className=\"pl-10 pr-10\"\n                value={password}\n                onChange={(e) => setPassword(e.target.value)}\n                autoComplete=\"new-password\"\n                required\n              />\n              <button\n                type=\"button\"\n                onClick={() => setShowPassword(!showPassword)}\n                className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n              >\n                {showPassword ? (\n                  <EyeOff className=\"size-4\" />\n                ) : (\n                  <Eye className=\"size-4\" />\n                )}\n              </button>\n            </div>\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"confirm-password\">Confirm password</Label>\n            <div className=\"relative\">\n              <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"confirm-password\"\n                type={showConfirmPassword ? \"text\" : \"password\"}\n                placeholder=\"Confirm your password\"\n                className=\"pl-10 pr-10\"\n                value={passwordConfirmation}\n                onChange={(e) => setPasswordConfirmation(e.target.value)}\n                autoComplete=\"new-password\"\n                required\n              />\n              <button\n                type=\"button\"\n                onClick={() => setShowConfirmPassword(!showConfirmPassword)}\n                className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n              >\n                {showConfirmPassword ? (\n                  <EyeOff className=\"size-4\" />\n                ) : (\n                  <Eye className=\"size-4\" />\n                )}\n              </button>\n            </div>\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"image\">Profile photo (optional)</Label>\n            <div className=\"flex items-center gap-4\">\n              {imagePreview ? (\n                <div className=\"relative size-16 shrink-0 rounded-full overflow-hidden ring-2 ring-border\">\n                  <Image\n                    src={imagePreview}\n                    alt=\"Profile preview\"\n                    fill\n                    className=\"object-cover\"\n                  />\n                </div>\n              ) : (\n                <div className=\"flex size-16 shrink-0 items-center justify-center rounded-full bg-muted\">\n                  <Upload className=\"size-6 text-muted-foreground\" />\n                </div>\n              )}\n              <div className=\"flex flex-1 items-center gap-2\">\n                <Input\n                  id=\"image\"\n                  type=\"file\"\n                  accept=\"image/*\"\n                  onChange={handleImageChange}\n                  className=\"flex-1\"\n                />\n                {imagePreview && (\n                  <Button\n                    type=\"button\"\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    onClick={() => {\n                      setImage(null);\n                      setImagePreview(null);\n                    }}\n                  >\n                    <X className=\"size-4\" />\n                  </Button>\n                )}\n              </div>\n            </div>\n          </div>\n          <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n            {loading ? (\n              <Loader2 className=\"size-4 animate-spin\" />\n            ) : (\n              \"Create account\"\n            )}\n          </Button>\n        </form>\n      </CardContent>\n      <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n        <p className=\"text-center text-sm text-muted-foreground\">\n          Already have an account?{\" \"}\n          <Link\n            href=\"/sign-in\"\n            className=\"text-primary font-medium hover:underline\"\n          >\n            Sign in\n          </Link>\n        </p>\n      </CardFooter>\n    </Card>\n  );\n}\n\nasync function convertImageToBase64(file: File): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onloadend = () => resolve(reader.result as string);\n    reader.onerror = reject;\n    reader.readAsDataURL(file);\n  });\n}\n```\n\n### Forgot Password Component\n\n```tsx\n// src/components/auth/forgot-password.tsx\n\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { useState } from \"react\";\nimport { Loader2, Mail, ArrowLeft, CheckCircle } from \"lucide-react\";\nimport { authClient } from \"@/lib/auth/client\";\nimport { toast } from \"sonner\";\nimport Link from \"next/link\";\n\nexport function ForgotPassword() {\n  const [email, setEmail] = useState(\"\");\n  const [loading, setLoading] = useState(false);\n  const [success, setSuccess] = useState(false);\n\n  const handleSubmit = async (e: React.FormEvent) => {\n    e.preventDefault();\n\n    if (!email) {\n      toast.error(\"Please enter your email address\");\n      return;\n    }\n\n    setLoading(true);\n    try {\n      const { error } = await authClient.requestPasswordReset({\n        email,\n        redirectTo: \"/reset-password\",\n      });\n\n      if (error) {\n        toast.error(error.message);\n        return;\n      }\n\n      setSuccess(true);\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  if (success) {\n    return (\n      <Card className=\"w-full max-w-md shadow-lg\">\n        <CardHeader className=\"space-y-1 text-center\">\n          <div className=\"mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-primary/10\">\n            <CheckCircle className=\"size-8 text-primary\" />\n          </div>\n          <CardTitle className=\"text-2xl font-bold\">Check your email</CardTitle>\n          <CardDescription className=\"text-base\">\n            If an account exists for <strong>{email}</strong>, we&apos;ve sent\n            password reset instructions.\n          </CardDescription>\n        </CardHeader>\n        <CardContent className=\"text-center text-sm text-muted-foreground\">\n          <p>\n            Didn&apos;t receive the email? Check your spam folder or try again\n            with a different email address.\n          </p>\n        </CardContent>\n        <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n          <Button\n            variant=\"outline\"\n            className=\"w-full\"\n            onClick={() => setSuccess(false)}\n          >\n            Try another email\n          </Button>\n          <Link href=\"/sign-in\" className=\"w-full\">\n            <Button variant=\"ghost\" className=\"w-full\">\n              <ArrowLeft className=\"size-4\" />\n              Back to sign in\n            </Button>\n          </Link>\n        </CardFooter>\n      </Card>\n    );\n  }\n\n  return (\n    <Card className=\"w-full max-w-md shadow-lg\">\n      <CardHeader className=\"space-y-1 text-center\">\n        <CardTitle className=\"text-2xl font-bold\">Forgot password?</CardTitle>\n        <CardDescription>\n          Enter your email and we&apos;ll send you a link to reset your password\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <form onSubmit={handleSubmit} className=\"space-y-4\">\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"email\">Email</Label>\n            <div className=\"relative\">\n              <Mail className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"email\"\n                type=\"email\"\n                placeholder=\"you@example.com\"\n                className=\"pl-10\"\n                value={email}\n                onChange={(e) => setEmail(e.target.value)}\n                autoComplete=\"email\"\n                required\n              />\n            </div>\n          </div>\n          <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n            {loading ? (\n              <Loader2 className=\"size-4 animate-spin\" />\n            ) : (\n              \"Send reset link\"\n            )}\n          </Button>\n        </form>\n      </CardContent>\n      <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n        <Link href=\"/sign-in\" className=\"w-full\">\n          <Button variant=\"ghost\" className=\"w-full\">\n            <ArrowLeft className=\"size-4\" />\n            Back to sign in\n          </Button>\n        </Link>\n      </CardFooter>\n    </Card>\n  );\n}\n```\n\n### Reset Password Component\n\n```tsx\n// src/components/auth/reset-password.tsx\n\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { useState } from \"react\";\nimport { Loader2, Lock, Eye, EyeOff, CheckCircle, XCircle } from \"lucide-react\";\nimport { authClient } from \"@/lib/auth/client\";\nimport { toast } from \"sonner\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\n\ntype ResetPasswordProps = {\n  token: string | null;\n  error: string | null;\n};\n\nexport function ResetPassword({ token, error }: ResetPasswordProps) {\n  const [password, setPassword] = useState(\"\");\n  const [confirmPassword, setConfirmPassword] = useState(\"\");\n  const [showPassword, setShowPassword] = useState(false);\n  const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n  const [loading, setLoading] = useState(false);\n  const [success, setSuccess] = useState(false);\n  const router = useRouter();\n\n  if (error || !token) {\n    return (\n      <Card className=\"w-full max-w-md shadow-lg\">\n        <CardHeader className=\"space-y-1 text-center\">\n          <div className=\"mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-destructive/10\">\n            <XCircle className=\"size-8 text-destructive\" />\n          </div>\n          <CardTitle className=\"text-2xl font-bold\">Invalid link</CardTitle>\n          <CardDescription className=\"text-base\">\n            {error === \"INVALID_TOKEN\"\n              ? \"This password reset link is invalid or has expired.\"\n              : \"Something went wrong. Please try again.\"}\n          </CardDescription>\n        </CardHeader>\n        <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n          <Link href=\"/forgot-password\" className=\"w-full\">\n            <Button className=\"w-full\">Request new link</Button>\n          </Link>\n          <Link href=\"/sign-in\" className=\"w-full\">\n            <Button variant=\"ghost\" className=\"w-full\">\n              Back to sign in\n            </Button>\n          </Link>\n        </CardFooter>\n      </Card>\n    );\n  }\n\n  const handleSubmit = async (e: React.FormEvent) => {\n    e.preventDefault();\n\n    if (password.length < 8) {\n      toast.error(\"Password must be at least 8 characters\");\n      return;\n    }\n\n    if (password !== confirmPassword) {\n      toast.error(\"Passwords do not match\");\n      return;\n    }\n\n    setLoading(true);\n    try {\n      const { error } = await authClient.resetPassword({\n        newPassword: password,\n        token,\n      });\n\n      if (error) {\n        toast.error(error.message);\n        return;\n      }\n\n      setSuccess(true);\n      toast.success(\"Password reset successfully!\");\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  if (success) {\n    return (\n      <Card className=\"w-full max-w-md shadow-lg\">\n        <CardHeader className=\"space-y-1 text-center\">\n          <div className=\"mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-primary/10\">\n            <CheckCircle className=\"size-8 text-primary\" />\n          </div>\n          <CardTitle className=\"text-2xl font-bold\">Password reset!</CardTitle>\n          <CardDescription className=\"text-base\">\n            Your password has been successfully reset. You can now sign in with\n            your new password.\n          </CardDescription>\n        </CardHeader>\n        <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n          <Button className=\"w-full\" onClick={() => router.push(\"/sign-in\")}>\n            Sign in\n          </Button>\n        </CardFooter>\n      </Card>\n    );\n  }\n\n  return (\n    <Card className=\"w-full max-w-md shadow-lg\">\n      <CardHeader className=\"space-y-1 text-center\">\n        <CardTitle className=\"text-2xl font-bold\">Set new password</CardTitle>\n        <CardDescription>Enter your new password below</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <form onSubmit={handleSubmit} className=\"space-y-4\">\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"password\">New password</Label>\n            <div className=\"relative\">\n              <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"password\"\n                type={showPassword ? \"text\" : \"password\"}\n                placeholder=\"At least 8 characters\"\n                className=\"pl-10 pr-10\"\n                value={password}\n                onChange={(e) => setPassword(e.target.value)}\n                autoComplete=\"new-password\"\n                required\n              />\n              <button\n                type=\"button\"\n                onClick={() => setShowPassword(!showPassword)}\n                className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n              >\n                {showPassword ? (\n                  <EyeOff className=\"size-4\" />\n                ) : (\n                  <Eye className=\"size-4\" />\n                )}\n              </button>\n            </div>\n          </div>\n          <div className=\"space-y-2\">\n            <Label htmlFor=\"confirm-password\">Confirm new password</Label>\n            <div className=\"relative\">\n              <Lock className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n              <Input\n                id=\"confirm-password\"\n                type={showConfirmPassword ? \"text\" : \"password\"}\n                placeholder=\"Confirm your new password\"\n                className=\"pl-10 pr-10\"\n                value={confirmPassword}\n                onChange={(e) => setConfirmPassword(e.target.value)}\n                autoComplete=\"new-password\"\n                required\n              />\n              <button\n                type=\"button\"\n                onClick={() => setShowConfirmPassword(!showConfirmPassword)}\n                className=\"absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n              >\n                {showConfirmPassword ? (\n                  <EyeOff className=\"size-4\" />\n                ) : (\n                  <Eye className=\"size-4\" />\n                )}\n              </button>\n            </div>\n          </div>\n          <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n            {loading ? (\n              <Loader2 className=\"size-4 animate-spin\" />\n            ) : (\n              \"Reset password\"\n            )}\n          </Button>\n        </form>\n      </CardContent>\n    </Card>\n  );\n}\n```\n\n### Verify Email Result Component\n\n```tsx\n// src/components/auth/verify-email-result.tsx\n\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { CheckCircle, XCircle, Mail } from \"lucide-react\";\nimport Link from \"next/link\";\n\ntype VerifyEmailResultProps = {\n  success: boolean;\n  error?: string;\n};\n\nexport function VerifyEmailResult({ success, error }: VerifyEmailResultProps) {\n  if (success) {\n    return (\n      <Card className=\"w-full max-w-md shadow-lg\">\n        <CardHeader className=\"space-y-1 text-center\">\n          <div className=\"mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-primary/10\">\n            <CheckCircle className=\"size-8 text-primary\" />\n          </div>\n          <CardTitle className=\"text-2xl font-bold\">Email verified!</CardTitle>\n          <CardDescription className=\"text-base\">\n            Your email address has been successfully verified. You can now sign\n            in to your account.\n          </CardDescription>\n        </CardHeader>\n        <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n          <Link href=\"/sign-in\" className=\"w-full\">\n            <Button className=\"w-full\">Sign in</Button>\n          </Link>\n        </CardFooter>\n      </Card>\n    );\n  }\n\n  const errorMessage = (() => {\n    switch (error) {\n      case \"INVALID_TOKEN\":\n        return \"This verification link is invalid or has expired.\";\n      case \"NO_TOKEN\":\n        return \"No verification token was provided.\";\n      case \"VERIFICATION_FAILED\":\n        return \"Email verification failed. Please try again.\";\n      default:\n        return \"Something went wrong during verification.\";\n    }\n  })();\n\n  return (\n    <Card className=\"w-full max-w-md shadow-lg\">\n      <CardHeader className=\"space-y-1 text-center\">\n        <div className=\"mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-destructive/10\">\n          <XCircle className=\"size-8 text-destructive\" />\n        </div>\n        <CardTitle className=\"text-2xl font-bold\">\n          Verification failed\n        </CardTitle>\n        <CardDescription className=\"text-base\">{errorMessage}</CardDescription>\n      </CardHeader>\n      <CardContent className=\"text-center text-sm text-muted-foreground\">\n        <p>\n          If your link has expired, you can request a new verification email\n          after signing in.\n        </p>\n      </CardContent>\n      <CardFooter className=\"flex flex-col gap-4 border-t pt-6\">\n        <Link href=\"/sign-in\" className=\"w-full\">\n          <Button className=\"w-full\">\n            <Mail className=\"size-4\" />\n            Sign in to resend\n          </Button>\n        </Link>\n        <Link href=\"/\" className=\"w-full\">\n          <Button variant=\"ghost\" className=\"w-full\">\n            Back to home\n          </Button>\n        </Link>\n      </CardFooter>\n    </Card>\n  );\n}\n```\n\n---\n\n## Auth Pages\n\nCreate pages for all auth flows with server-side session checks.\n\n### Sign In Page\n\n```tsx\n// src/app/sign-in/page.tsx\nimport type { Metadata } from \"next\";\nimport { redirect } from \"next/navigation\";\nimport { headers } from \"next/headers\";\nimport { auth } from \"@/lib/auth/server\";\nimport { SignIn } from \"@/components/auth/sign-in\";\n\nexport const metadata: Metadata = {\n  title: \"Sign In\",\n  description: \"Sign in to your account.\",\n};\n\nexport default async function SignInPage() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (session) {\n    redirect(\"/chats\");\n  }\n\n  return (\n    <main className=\"min-h-dvh flex items-center justify-center p-4\">\n      <SignIn />\n    </main>\n  );\n}\n```\n\n### Sign Up Page\n\n```tsx\n// src/app/sign-up/page.tsx\nimport type { Metadata } from \"next\";\nimport { redirect } from \"next/navigation\";\nimport { headers } from \"next/headers\";\nimport { auth } from \"@/lib/auth/server\";\nimport { SignUp } from \"@/components/auth/sign-up\";\n\nexport const metadata: Metadata = {\n  title: \"Create Account\",\n  description: \"Create a free account to get started.\",\n};\n\nexport default async function SignUpPage() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (session) {\n    redirect(\"/chats\");\n  }\n\n  return (\n    <main className=\"min-h-dvh flex items-center justify-center p-4\">\n      <SignUp />\n    </main>\n  );\n}\n```\n\n### Forgot Password Page\n\n```tsx\n// src/app/forgot-password/page.tsx\nimport type { Metadata } from \"next\";\nimport { redirect } from \"next/navigation\";\nimport { headers } from \"next/headers\";\nimport { auth } from \"@/lib/auth/server\";\nimport { ForgotPassword } from \"@/components/auth/forgot-password\";\n\nexport const metadata: Metadata = {\n  title: \"Forgot Password\",\n  description:\n    \"Reset your password by entering your email address. We'll send you a link to create a new one.\",\n};\n\nexport default async function ForgotPasswordPage() {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (session) {\n    redirect(\"/chats\");\n  }\n\n  return (\n    <main className=\"min-h-dvh flex items-center justify-center p-4\">\n      <ForgotPassword />\n    </main>\n  );\n}\n```\n\n### Reset Password Page\n\n```tsx\n// src/app/reset-password/page.tsx\nimport type { Metadata } from \"next\";\nimport { redirect } from \"next/navigation\";\nimport { headers } from \"next/headers\";\nimport { auth } from \"@/lib/auth/server\";\nimport { ResetPassword } from \"@/components/auth/reset-password\";\n\nexport const metadata: Metadata = {\n  title: \"Reset Password\",\n  description: \"Create a new password for your account.\",\n};\n\ntype SearchParams = Promise<{ token?: string; error?: string }>;\n\nexport default async function ResetPasswordPage({\n  searchParams,\n}: {\n  searchParams: SearchParams;\n}) {\n  const session = await auth.api.getSession({\n    headers: await headers(),\n  });\n\n  if (session) {\n    redirect(\"/chats\");\n  }\n\n  const { token, error } = await searchParams;\n\n  return (\n    <main className=\"min-h-dvh flex items-center justify-center p-4\">\n      <ResetPassword token={token ?? null} error={error ?? null} />\n    </main>\n  );\n}\n```\n\n### Verify Email Page\n\n```tsx\n// src/app/verify-email/page.tsx\nimport type { Metadata } from \"next\";\nimport { headers } from \"next/headers\";\nimport { auth } from \"@/lib/auth/server\";\nimport { VerifyEmailResult } from \"@/components/auth/verify-email-result\";\n\nexport const metadata: Metadata = {\n  title: \"Verify Email\",\n  description: \"Confirm your email address to complete your account setup.\",\n};\n\ntype SearchParams = Promise<{ token?: string; error?: string }>;\n\nexport default async function VerifyEmailPage({\n  searchParams,\n}: {\n  searchParams: SearchParams;\n}) {\n  const { token, error } = await searchParams;\n\n  let verificationResult: { success: boolean; error?: string } = {\n    success: false,\n  };\n\n  if (error) {\n    verificationResult = { success: false, error };\n  } else if (token) {\n    try {\n      const result = await auth.api.verifyEmail({\n        query: { token },\n        headers: await headers(),\n      });\n      verificationResult = { success: result?.status === true };\n    } catch {\n      verificationResult = { success: false, error: \"VERIFICATION_FAILED\" };\n    }\n  } else {\n    verificationResult = { success: false, error: \"NO_TOKEN\" };\n  }\n\n  return (\n    <main className=\"min-h-dvh flex items-center justify-center p-4\">\n      <VerifyEmailResult\n        success={verificationResult.success}\n        error={verificationResult.error}\n      />\n    </main>\n  );\n}\n```\n\n---\n\n## Extending with OAuth Providers\n\nTo add social sign-in buttons (like GitHub, Google, or Vercel), you can extend the SignIn and SignUp components with a `showSocialSignIn` prop:\n\n```tsx\n// Extended SignIn with optional OAuth\nexport function SignIn({\n  showVercelSignIn = false,\n}: {\n  showVercelSignIn?: boolean;\n} = {}) {\n  // ... existing state\n\n  const handleVercelSignIn = async () => {\n    await signIn.social(\n      { provider: \"vercel\", callbackURL: \"/chats\" },\n      {\n        onRequest: () => setSocialLoading(true),\n        onResponse: () => setSocialLoading(false),\n        onError: (ctx) => toast.error(ctx.error.message),\n      },\n    );\n  };\n\n  return (\n    <Card>\n      {/* Add OAuth button before the form */}\n      {showVercelSignIn && (\n        <>\n          <Button variant=\"outline\" onClick={handleVercelSignIn}>\n            Sign in with Vercel\n          </Button>\n          <div className=\"relative\">\n            <div className=\"absolute inset-0 flex items-center\">\n              <span className=\"w-full border-t\" />\n            </div>\n            <div className=\"relative flex justify-center text-xs uppercase\">\n              <span className=\"bg-card px-2 text-muted-foreground\">\n                Or continue with email\n              </span>\n            </div>\n          </div>\n        </>\n      )}\n      {/* Rest of the form */}\n    </Card>\n  );\n}\n```\n\nUse with a feature flag to conditionally show OAuth based on environment configuration:\n\n```tsx\n// src/app/sign-in/page.tsx\nimport { vercelSignInFlag } from \"@/lib/auth/flags\";\n\nexport default async function SignInPage() {\n  const showVercelSignIn = await vercelSignInFlag();\n\n  return <SignIn showVercelSignIn={showVercelSignIn} />;\n}\n```\n\nSee the [Feature Flags](/recipes/feature-flags-setup) recipe for setting up the Flags SDK.\n\n---\n\n## File Structure\n\n```\nsrc/components/auth/\n  sign-in.tsx\n  sign-up.tsx\n  forgot-password.tsx\n  reset-password.tsx\n  verify-email-result.tsx\n\nsrc/app/\n  sign-in/page.tsx\n  sign-up/page.tsx\n  forgot-password/page.tsx\n  reset-password/page.tsx\n  verify-email/page.tsx\n```"}