# ApprovalCard

A review card for approving or rejecting a proposed action.

- **Use it for**: Approving or rejecting one proposed agent action, bound to its version.
- **Not for**: Generic confirmations unrelated to an agent. Try [AlertDialog](https://unseenui.com/components/alert-dialog/), [ApprovalBatch](https://unseenui.com/components/approval-batch/).

## Installation

```bash
npm install @stackunseen/ui
```

## Parts

A card for one proposed action. It shows what your server sent, asks for a decision and never claims the action ran.

1. **Header**: A risk icon, what kind of decision it is ("Review required"), the action and its status. Built from StatusBadge. Props: `proposal`.
2. **Summary**: What your server supplied about the action, such as its scopes, then its impact in plain words. Props: `proposal`.
3. **Details**: Hidden until someone presses Details: risk and version, expiry, scopes when none were given, and a note that permission is checked again before anything runs. Props: `proposal`.
4. **Warnings**: Shown only when needed: the proposal changed, it expired, or your server reported an error. Props: `stale`, `expired`, `error`.
5. **Decision**: Details, Reject and Approve action, then a status line that says "Decision received" only once your server confirms. If the outcome is unknown, Check decision status replaces the buttons. Built from Button. Props: `onDecision`, `submission`, `onReconcile`.

## Usage

```tsx
import { ApprovalCard } from '@stackunseen/ui';
import '@stackunseen/ui/styles.css';

<ApprovalCard variant="card" proposal={proposal} submission={submission} onDecision={decide} />
```

## Props

- `proposal` (`ApprovalProposal`): The action details, scope, version, digest, status and expiry from your server.
- `submission` (`SubmissionState`): The decision request state. Keep it separate from proposal.status.
- `onDecision` (`(decision: 'approve' | 'reject', proposal: ApprovalProposal) => void`): Send the chosen decision for this proposal and update submission from the result.
- `stale` (`boolean (optional)`): Disables decisions when the displayed proposal may be out of date.
- `error` (`string (optional)`): The decision failure to show. The card does not infer a result from a click.
- `onReconcile` (`() => void (optional)`): Look up the original decision after an unknown outcome before trying again.

## All props

| Prop | Type | Description |
| --- | --- | --- |
| `error` | `string` |  |
| `expired` | `boolean` |  |
| `onDecision` (required) | `(decision: "approve" \| "reject", proposal: ApprovalProposal) => void` |  |
| `onReconcile` | `(() => void)` |  |
| `proposal` (required) | `ApprovalProposal` |  |
| `stale` | `boolean` |  |
| `submission` (required) | `SubmissionState` |  |
| `variant` | `"card" \| "banner" \| "split"` |  |

## Keyboard

Tab reaches Reject, then Approve action. When the outcome is unknown, it reaches Check decision status instead. Enter or Space activates an enabled action.

## Works with

[PermissionRequest](https://unseenui.com/components/permission-request/), [ReviewChanges](https://unseenui.com/components/review-changes/), [DryRunPreview](https://unseenui.com/components/dry-run-preview/), [ApprovalBatch](https://unseenui.com/components/approval-batch/), [ScopeGrant](https://unseenui.com/components/scope-grant/), [AccessModeSelector](https://unseenui.com/components/access-mode-selector/), [ClarificationRequest](https://unseenui.com/components/clarification-request/)

Page: https://unseenui.com/components/approval-card/
