# Approval Card

> Source: https://design.nanatec.co.ke/components/approval-card/
> One thing waiting on the owner — a repair quote, a fee waiver, a refund, a new tenancy — with what it costs, who asked, and the decision to make.
> Status: beta
> Since: v0.0.36

Approving is one press. Rejecting asks why, because the person who asked
will read the answer; `require-reason="all"` asks on approval too, for a
portfolio that wants every decision explained. Once decided, the card
becomes the record of the decision: who made it, when, and what they said.

The card does not decide anything itself. It fires the decision; the app
saves it and sets `status`. Set `busy` while the save is in flight.

## Preview

```html
<nana-approval-card
  kind="Repair quote"
  heading="Replace the geyser in Unit 4B"
  amount="18500"
  currency="KES"
  requester="Otieno, caretaker"
  requested-at="2026-10-07T08:30:00Z"
>
  The element has failed twice this month. Quote from Jua Plumbers, fitted Thursday.
</nana-approval-card>
```

## Rejecting

Reject opens a field for the reason and will not send without one: the
caretaker who asked will read it. Escape or Cancel puts the buttons back.
`allow-changes` adds a third answer, for a request that is nearly right.

```html
<nana-approval-card
  kind="Fee waiver"
  heading="Waive the October late fee for Unit 1B"
  amount="1500"
  requester="Otieno S., tenant"
  requested-at="2026-10-06T12:10:00Z"
  due="2026-10-09"
  allow-changes
  style="max-width:32rem"
>
  Paid on the 6th; the M-Pesa reversal on the 5th was the bank's error. Statement attached.
</nana-approval-card>
```

## Once decided

The app saves the decision and sets `status`, `decided-by`, `decided-at` and
`decision-note`. The buttons give way to the record.

```html
<nana-approval-card
  kind="Repair quote"
  heading="Replace the geyser in Unit 4B"
  amount="18500"
  requester="Otieno, caretaker"
  requested-at="2026-10-07T08:30:00Z"
  status="rejected"
  decided-by="Mary N."
  decided-at="2026-10-07T18:02:00Z"
  decision-note="Get a second quote. Last year's was 12,000 for the same unit."
  style="max-width:32rem"
></nana-approval-card>
```

## Wiring it up

```js
const card = document.querySelector("nana-approval-card");

card.addEventListener("nana-approve", async (e) => {
  card.busy = true;
  await api.decide(id, "approved", e.detail.reason);
  Object.assign(card, { busy: false, status: "approved", decidedBy: me.name, decidedAt: new Date().toISOString() });
});
card.addEventListener("nana-reject", (e) => api.decide(id, "rejected", e.detail.reason));
```

`require-reason` decides which answers need words: `reject` (the default),
`all`, or `none`. Asking for changes always does.

## Installation

```bash
npm install @nana-tec/ui-components
```

Or copy the source in and own it:

```bash
npx @nana-tec/ui add approval-card
```

## Usage

```js
import "@nana-tec/ui-components/approval-card";
```

```html
<nana-approval-card
  kind="Repair quote"
  heading="Replace the geyser in Unit 4B"
  amount="18500"
  currency="KES"
  requester="Otieno, caretaker"
  requested-at="2026-10-07T08:30:00Z"
>
  The element has failed twice this month. Quote from Jua Plumbers, fitted Thursday.
</nana-approval-card>
```

```js
const el = document.querySelector("nana-approval-card");
el.addEventListener("nana-approve", (e) => console.log(e.detail));
el.addEventListener("nana-reject", (e) => console.log(e.detail));
el.addEventListener("nana-request-changes", (e) => console.log(e.detail));
```

## API

## Storybook

See every state and prop interactively in Storybook: https://storybook.nanatec.co.ke/?path=/docs/components-approval-card--docs
