Authentication
Login Page
The <LoginForm /> offers a simple and intuitive interface for users to securely log in to their accounts.
Installation
Copy and paste the following code into your project.
"use client";
import { cn } from "@/lib/utils"
import * as React from "react"
import { useState } from "react";
import * as z from "zod"
import { FaGoogle, FaGithub } from "react-icons/fa";
import { useRouter } from "next/navigation";
import { toast } from "sonner"
import { useForm } from "@tanstack/react-form"
import { authClient } from "@/lib/auth/client";
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Field,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
const formSchema = z.object({
email: z
.email()
.min(1, "Email cannot be empty."),
password: z
.string()
.min(1, "Password cannot be empty.")
})
export function LoginForm({
className,
...props
}: React.ComponentProps<"div">) {
const callbackURL = "/success";
const router = useRouter();
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
const [isGithubLoading, setIsGithubLoading] = useState(false);
const [isEmailLoading, setIsEmailLoading] = useState(false);
const form = useForm({
defaultValues: {
email: "",
password: "",
},
validators: {
onSubmit: formSchema,
},
onSubmit: async ({ value }) => {
setIsEmailLoading(true);
try {
await authClient.signIn.email(
{
email: value.email.toLowerCase(),
password: value.password,
},
{
onSuccess: (_ctx) => {
toast.success("Welcome!");
router.push(callbackURL);
},
onError: (ctx) => {
if (ctx.error.status === 403) {
toast.error("Please verify your email address");
}
},
}
);
} catch (_error) {
return toast("Login failed. Please try again.");
} finally {
setIsEmailLoading(false);
}
},
})
const handleSocialSignIn = async (provider: "google" | "github") => {
if (provider === "google") {
setIsGoogleLoading(true);
} else {
setIsGithubLoading(true);
}
try {
await authClient.signIn.social({
provider,
callbackURL
});
} catch (_error) {
return toast("Sign in failed. Please try again.");
} finally {
if (provider === "google") {
setIsGoogleLoading(false);
} else {
setIsGithubLoading(false);
}
}
};
return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<CardHeader>
<CardTitle>Login to your account</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<form
id="login-form"
onSubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<FieldGroup>
<form.Field
name="email"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
type="email"
placeholder="Enter your email..."
required
/>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<form.Field
name="password"
children={(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid
return (
<Field data-invalid={isInvalid}>
<div className="flex items-center">
<FieldLabel htmlFor="password">Password</FieldLabel>
<a
href="/forgot-password"
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</a>
</div>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={isInvalid}
type="password"
placeholder="Enter your password..."
required
/>
{isInvalid && (
<FieldError errors={field.state.meta.errors} />
)}
</Field>
)
}}
/>
<Field>
<Button
type="submit"
disabled={isEmailLoading || isGoogleLoading || isGithubLoading}
>
Login
{isEmailLoading ? "Loading..." : "Login"}
</Button>
<Button
variant="outline"
type="button"
disabled={isEmailLoading || isGoogleLoading || isGithubLoading}
onClick={async () => handleSocialSignIn("google")}
>
{isGoogleLoading ? (
"Loading..."
) : (
<>
<FaGoogle />
<span className="sr-only">Login with Google</span>
</>
)}
</Button>
<Button
variant="outline"
type="button"
disabled={isEmailLoading || isGoogleLoading || isGithubLoading}
onClick={async () => handleSocialSignIn("github")}
>
{isGithubLoading ? (
"Loading..."
) : (
<>
<FaGithub />
<span className="sr-only">Login with Github</span>
</>
)}
</Button>
<FieldDescription className="text-center">
Don't have an account? <a href="/sign-up">Sign up</a>
</FieldDescription>
</Field>
</FieldGroup>
</form>
</CardContent>
</Card>
</div>
)
}Usage
Here's how you can include the <LoginForm /> in your project
import { Suspense } from "react"
import { LoginForm } from "@/components/login-form"
import { Skeleton } from "@/components/ui/skeleton"
export const metadata: Metadata = {
title: "Log In",
};
export default function Page() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<Suspense>
<LoginForm />
</Suspense>
</div>
</div>
);
}Inspired by the shadcn login block!