# Access request

Ask for a role you don't have on an environment. Says who decides, when the app knows. Once sent it shows as pending, with withdraw. A role at or below the one you hold there is locked, with the reason.

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

## Install

```bash
npx candor-ui add access-request
```

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

## Example

```tsx
"use client"

import * as React from "react"
import { AccessRequest, type SentAccessRequest } from "@/components/access-request"
import type { Environment, Role } from "@/lib/team"

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

const roles: Role[] = [
  { id: "contributor", label: "Contributor", rank: 1 },
  { id: "approver", label: "Approver", rank: 2 },
  { id: "admin", label: "Admin", rank: 3 },
]

const deciders = { "env-prod": ["Priya", "Dana"], "env-staging": ["Priya"] }

export default function Example() {
  const [sent, setSent] = React.useState<SentAccessRequest | null>(null)
  return (
    <AccessRequest
      className="w-full max-w-md"
      environments={environments}
      roles={roles}
      requestable={["approver", "admin"]}
      current={{ "env-staging": "contributor" }}
      deciders={deciders}
      note="Raising a role always needs approval."
      sent={sent}
      onSend={(environmentId, role) => setSent({ environment: environmentId, role, at: new Date().toISOString(), deciders: deciders[environmentId as keyof typeof deciders] })}
      onWithdraw={() => setSent(null)}
    />
  )
}
```

## <AccessRequest>

Props:

- `environments: Environment[]`: The environments a role can be asked for on.
- `roles: Role[]`: Every role, so the one you hold can be named.
- `requestable: string[]`: Role ids that can be asked for.
- `current: Record<string, string | undefined>`: The viewer's role id per environment id.
- `deciders: Record<string, string[]>`: Who decides, per environment id.
- `note?: string | undefined`: The app's rule, e.g. "Raising a role always needs approval."
- `sent?: SentAccessRequest | null | undefined`: The request already sent; set, the form gives way to a pending note. null/undefined means none sent.
- `onSend: (environmentId: string, roleId: string, reason: string) => void`: Called on send with the environment id, role id and the reason given.
- `onWithdraw?: (() => void) | undefined`: Shows Withdraw on a pending request; called when it's clicked.
- `className?: string | undefined`

## Strings (`accessRequest`)

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

- `sentLabel`: Access request
- `formLabel`: Ask for access
- `heading`: Ask for more access
- `withA`: a {role}
- `withAn`: an {role}
- `chooseEnvironment`: Choose the {environment}.
- `chooseAccess`: Choose the access you need.
- `alreadyThere`: You’re already {role} there.
- `sayWhy`: Say why you need it; the deciders see this.
- `askedFor`: You asked to be {role} on {place}.
- `waitingFor`: Waiting for {deciders}.
- `withdraw`: Withdraw
- `production`: Production
- `youAre`: You’re {role} on this {environment}.
- `noRole`: You have no role on this {environment}.
- `role`: Role
- `why`: Why
- `decidedBy`: Decided by {deciders}.
- `send`: Send request

## Types

### SentAccessRequest

```ts
{ /** The environment's id, as onSend gave it; shown by its name. (A name that matches no id is shown as it is.) */ environment: string /** The role's id, shown by its label. */ role: string at: string deciders: string[] }
```

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