asafefe.dev

Auth Card

New

A sign in card with email and password, social buttons, and an email me a code path that slides to a one-time code step. Errors shake the form and a verified sign in ends on a success state.

motionlucide-reactutilsbuttoncardfieldinputinput-groupspinner@soultel/otp-input@soultel/use-reduced-motion-safe
Sign in to Ledgerly
Welcome back. Enter your details to continue.
New to Ledgerly? Create an account

Password hunter22 signs in. The emailed code is 246810.

Installation

$ pnpm dlx shadcn@latest add @soultel/auth-card

No namespace set up? npx shadcn@latest add https://asafefe.dev/r/auth-card.json works too.

Manual installation
  1. 1. Install the dependencies.
    terminal1 lines
    npm install motion lucide-react
  2. 2. Copy the source from the Code tab into components/auth-card.tsx.

Usage

example.tsx25 lines
import { AuthCard } from "@/components/auth-card"

export function SignIn() {
  const router = useRouter()
  return (
    <AuthCard
      title="Sign in to Ledgerly"
      onSignIn={async ({ email, password }) => {
        const res = await fetch("/api/auth/sign-in", { method: "POST", body: JSON.stringify({ email, password }) })
        if (!res.ok) throw new Error("That email and password do not match.")
        router.push("/dashboard")
      }}
      onSendCode={async (email) => {
        await fetch("/api/auth/otp", { method: "POST", body: JSON.stringify({ email }) })
      }}
      onVerify={async (email, code) => {
        const res = await fetch("/api/auth/otp/verify", { method: "POST", body: JSON.stringify({ email, code }) })
        if (!res.ok) throw new Error("That code did not work.")
      }}
      onSocial={(provider) => signIn(provider)}
      onForgotPassword={(email) => router.push(`/reset?email=${encodeURIComponent(email)}`)}
      footer={<a href="/sign-up">New here? Create an account</a>}
    />
  )
}

Props

PropTypeDefaultDescription
titlestring"Sign in"Heading on the first step.
descriptionReactNode-Line under the heading.
logoReactNode-Mark shown above the heading.
providersAuthProvider[]Google, GitHub, MicrosoftSocial buttons: { id, label, icon }. Labels show when the card is wide enough. An empty array hides the row.
step / defaultStep"password" | "code" | "success""password"Controlled or initial step.
onStepChange(step: AuthStep) => void-Called when the card moves between steps.
defaultEmailstring-Prefills the email field.
onSignIn({ email, password }) => void | Promise<void>-A promise shows a pending state. A rejection shakes the form and shows the error's message.
onSendCode(email: string) => void | Promise<void>-Called before the code step opens and on resend. A rejection keeps the visitor where they are with an error.
onVerify(email: string, code: string) => void | Promise<void>-Called once every box is filled. A rejection shakes and clears the boxes; success moves to the success step.
onSocial(providerId: string) => void | Promise<void>-Called by the social buttons. A promise spins that button.
onForgotPassword(email: string) => void-Shows a Forgot password link when set.
footerReactNode-Slot under the form, e.g. a sign up link.
codeLengthnumber6Number of code boxes.
resendAfternumber30Seconds before Resend code is offered again.
successTitlestring"You're signed in"Heading on the success step.