# Sign in

Sign in without a password: your company's SSO, or a one-time code sent to your email. The code screen says where the code went and lets you go back. Errors come from the caller and are shown where they apply. Joining an account happens here too: a pending invite is accepted by signing in with the email it was sent to.

- Registry name: `sign-in` (`@candor/sign-in`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/sign-in

## Install

```bash
npx candor-ui add sign-in
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/sign-in`.
It brings in: button, input, locale, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { SignIn } from "@/components/sign-in"

export default function Example() {
  const [busy, setBusy] = React.useState(false)
  const [error, setError] = React.useState<string>()
  const wait = () => new Promise((r) => setTimeout(r, 600))
  return (
    <SignIn
      brand={<span className="text-sm font-semibold tracking-tight">Keel</span>}
      onSso={() => {}}
      busy={busy}
      error={error}
      onSendCode={async () => {
        setError(undefined)
        setBusy(true)
        await wait()
        setBusy(false)
      }}
      onVerifyCode={async (_email, code) => {
        setBusy(true)
        await wait()
        setBusy(false)
        setError(code === "123456" ? undefined : "That code doesn’t match. Codes work once, for 10 minutes.")
      }}
    />
  )
}
```

## <SignIn>

Props:

- `brand?: React.ReactNode | undefined`: Your logo or name, shown above the form.
- `onSso?: (() => void) | undefined`: Shows the SSO button; called when it's clicked.
- `onSendCode: (email: string) => void | Promise<void>`: Called with the email when a code is asked for; the code screen shows next.
- `onVerifyCode: (email: string, code: string) => void | Promise<void>`: Called with the email and the six-digit code typed.
- `busy?: boolean | undefined` (default `false`): Disables the buttons and shows a spinner while a request is in flight.
- `error?: string | undefined`: Shown under the field it concerns.
- `className?: string | undefined`

## Strings (`signIn`)

The fixed words, English by default. Override any of them under `signIn` in `<LocaleProvider strings={{ signIn: { … } }}>`; the table is exported as `signInStrings`.

- `signIn`: Sign in
- `invitedHint`: If you were invited, use the email the invite went to.
- `sso`: Continue with company SSO
- `or`: or
- `workEmail`: Work email
- `emailPlaceholder`: you@company.com
- `emailMeACode`: Email me a code
- `differentEmail`: Use a different email
- `checkEmail`: Check your email
- `codeSent`: We sent a 6-digit code to {email}. It works for 10 minutes.
- `code`: Code
- `newCode`: Send a new code

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
