# Invite members

Invite people by pasting any list of emails. Each address becomes a chip; ones that aren't valid, or already belong to members, are marked and left out. The invite grants one role (the app's choice, e.g. its lowest) on the environments ticked; `rule` says anything else the app enforces.

- Registry name: `invite-members` (`@candor/invite-members`)
- 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/invite-members

## Install

```bash
npx candor-ui add invite-members
```

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

## Example

```tsx
"use client"

import { InviteMembers } from "@/components/invite-members"
import type { Environment } from "@/lib/team"

const environments: Environment[] = [
  { id: "env-staging", name: "Acme API", detail: "Staging", production: false, hue: 2, connection: "connected" },
  { id: "env-qa", name: "Acme API", detail: "QA", production: false, hue: 5, connection: "connected" },
]

export default function Example() {
  return (
    <InviteMembers
      className="w-full max-w-2xl"
      environments={environments}
      grants="Contributor"
      rule="An Admin can raise roles once they’ve joined."
      footnote="Invites expire in 7 days. People join by signing in with the invited email."
      existing={["priya@acme.example", "dana@acme.example"]}
      onInvite={() => {}}
    />
  )
}
```

## <InviteMembers>

Props:

- `environments: Environment[]`: Environments the inviter may invite to.
- `grants: string`: The role label the invite grants, e.g. "Editor".
- `rule?: string | undefined`: The app's rule, e.g. "An Admin can raise roles once they’ve joined."
- `footnote?: string | undefined`: How invites work, from the app, e.g. "Invites expire in 7 days."
- `existing?: string[] | undefined` (default `[]`): Emails already in the account.
- `onInvite: (emails: string[], environmentIds: string[]) => void`: Called on send with the valid new emails and the ticked environment ids.
- `busy?: boolean | undefined` (default `false`): Disables Send while the invites are going out.
- `className?: string | undefined`

## Strings (`inviteMembers`)

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

- `label`: Invite members
- `emails`: Emails
- `placeholder`: Paste emails, separated by commas, spaces or lines
- `notValid`: Not a valid email
- `alreadyMember`: Already a member
- `notValidNote`: (not valid)
- `alreadyMemberNote`: (already a member)
- `remove`: Remove {email}
- `wontBeSent`: { one: "{count} won’t be sent: it’s invalid or already members.", other: "{count} won’t be sent: they’re invalid or already members.", }
- `grantsOn`: {role} on
- `production`: Production
- `addEmail`: Add at least one new, valid email.
- `chooseWhere`: Choose where they get access.
- `send`: { zero: "Send invite", one: "Send invite", other: "Invite {count} people" }

## 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`.
