Account state

Pro

A whole-page stop for a signed-in person the app can't show anything to yet: no account, removed from one, email not verified, an invite to accept. It says who is signed in and offers sign-out, because the usual cause is the wrong email. The wording is the product's, passed in.

As a setup gate (before the app can work at all: connect a model, verify a domain), steps lists what's left as a checklist inside the card, children holds your own controls for the current step, and an action can stay locked with its reason until the steps are done.

Markdown

Preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/account-state

Brings in: button, locale, locked, setup-checklist, team, utils

Usage

TSX · The example above
"use client"

import * as React from "react"
import { AccountState } from "@/components/account-state"
import type { SetupStep } from "@/lib/team"

export default function Example() {
  const [gate, setGate] = React.useState(false)
  const [tested, setTested] = React.useState(false)
  // As a setup gate: what's left as a checklist, your own control for the step, Continue locked until it's done.
  const steps: SetupStep[] = [
    { id: "workspace", title: "Name your workspace", status: "done" },
    { id: "model", title: "Connect a model", detail: "Paste an API key in Settings, then test it here.", status: tested ? "done" : "current", action: tested ? undefined : { label: "Test the connection", onClick: () => setTested(true) } },
  ]
  return (
    <div className="flex flex-col gap-2">
      <label className="flex items-center gap-1.5 text-xs text-muted-foreground">
        <input type="checkbox" checked={gate} onChange={(e) => setGate(e.target.checked)} />
        As a setup gate
      </label>
      {!gate ? (
        <div className="flex h-[440px] overflow-hidden rounded-lg border">
          <AccountState
            brand={<span className="text-lg font-semibold tracking-tight">Keel</span>}
            title="You don’t have access to Acme yet"
            detail="Ask an owner of the Acme workspace to invite this email, or sign in with the email you were invited with."
            actions={[
              { label: "Request access", onClick: () => {} },
              { label: "Use a different email", onClick: () => {} },
            ]}
            email="sam@acme.example"
            onSignOut={() => {}}
          />
        </div>
      ) : (
        <div className="flex h-[520px] overflow-hidden rounded-lg border">
          <AccountState
            brand={<span className="text-lg font-semibold tracking-tight">Keel</span>}
            title="One thing before we start"
            detail="Keel needs a model it can reach before it can work on anything."
            steps={steps}
            actions={[{ label: "Continue", onClick: () => {}, locked: tested ? undefined : "Connect a model first." }]}
          />
        </div>
      )}
    </div>
  )
}

<AccountState>

PropTypeDefaultDescription
brandrequiredReact.ReactNode—The product's logo or name, shown above the card.
titlerequiredstring—What's wrong, as the heading, e.g. "You don't have an account yet".
detailReact.ReactNode | undefined—More on the situation and what to do, under the title.
actionsrequired{ label: string; onClick: () => void; locked?: string | undefined }[]—The next steps, the first one primary; `locked` keeps one listed but unusable, with the reason on it.
emailstring | undefined—Who is signed in; with `onSignOut`, printed under the card with a Sign out link.
onSignOut(() => void) | undefined—Called when the person chooses Sign out.
stepsSetupStep[] | undefined—What's left before the app can work, as a checklist in the card (a setup gate).
childrenReact.ReactNode | undefined—Your own controls in the card, under the detail and above the steps, such as a choice for the current step.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under accountState in <LocaleProvider strings={{ … }}>; the table is exported as accountStateStrings.

KeyEnglish
signedInAsSigned in as {email}
signOutSign out