# Benefits list

The benefits list shows a holder the benefits your distributor offers, one card per benefit: the ones they can claim now, the locked ones with the one sentence that says why, and the ones already claimed or redeemed. It is drawn inside your page from the rows you pass it, in the shape of the distributor API's benefits list.

## The script

Yutai serves the SDK's script at the address below: the script loads once, then the element is written with its facts as attributes.

The script is served today.

Embed code

```
<script src="https://app.yutai.com/sdk/v1/yutai-sdk-web.min.js"></script>

<yutai-benefits-list
  benefits='[
    {
      "benefitId": "bft_01H8Z2N9P4QX7K3M5A0V8D9T2W",
      "name": "Season voucher",
      "type": "VOUCHER",
      "delivery": "CODE",
      "state": "eligible",
      "sourceMode": "SIMULATED"
    },
    {
      "benefitId": "bft_01H8Z2N9P4QX7K3M5A0V8D9T2X",
      "name": "Lounge access",
      "type": "ACCESS",
      "delivery": "VENUE_REDEMPTION",
      "state": "locked",
      "lockedReason": "This benefit opens once the wallet keeps 15 SGH for 30 days.",
      "sourceMode": "SIMULATED"
    },
    {
      "benefitId": "bft_01H8Z2N9P4QX7K3M5A0V8D9T2Y",
      "name": "Welcome points",
      "type": "POINTS",
      "delivery": "POINTS_CREDIT",
      "state": "claimed",
      "sourceMode": "SIMULATED"
    }
  ]'
></yutai-benefits-list>
```

## The sample

The sample renders the same list from the same element and the same three rows the embed above writes: one eligible, one locked, one claimed.

Sample rows: a simulated holder's benefits, not a real holder's.

## The attribute

The list's facts arrive as one attribute: the rows, in the distributor API's own shape, as JSON text. Set it again with new rows and the list draws them at once.

| Attribute | What to pass |
| --- | --- |
| `benefits` | Required. The holder's benefit rows as a JSON array, at most 500 rows, each with the fields below. The list draws one card per row, in the order given. A row the list cannot draw is refused, naming the row by its position, and the list keeps its last good paint. |
| `owner-customer` | Optional. true when your server's Connect outcome says the holder owns the company and shops with it, otherwise false, the default. A row that also carries needsConnect: true draws locked, with the Connect button beneath it, until this is true. |
| `connect-session-url` | Optional. The Connect session's hosted address your server's create answered. A row behind Connect carries the button with the seal and the words Link a card to unlock more, which opens that address. |

## The row fields

Each row carries the fields of one benefit in the distributor API's benefits list, under the names its schema gives them, plus needsConnect, which only the list reads. The list draws the name, the state and the locked sentence, and passes over the rest, so an API row can be handed over unchanged.

| Field | What it carries |
| --- | --- |
| `benefitId` | Required. The benefit's id, like "bft_01H8Z2N9P4QX7K3M5A0V8D9T2W". The claim event carries it back. |
| `name` | Required. The words the benefit's card names it by. |
| `type` | The benefit's type, one of DISCOUNT_CODE, EXPERIENCE, POINTS, VOUCHER, ACCESS, DIGITAL_COLLECTIBLE or STOCK_REWARD. The distributor API's rows carry it; the list does not draw it. |
| `delivery` | How the benefit is delivered, one of `CODE`, `POINTS_CREDIT`, `VENUE_REDEMPTION`, `CHAIN_MINT`, `TOKEN_TRANSFER` or `REWARD_FULFILMENT`. The distributor API's rows carry it; the list does not draw it. |
| `state` | Required. Where the holder stands with the benefit, one of eligible, locked, claimed, redeemed or unavailable. Only an eligible card carries the claim press. |
| `lockedReason` | Required on a locked row, and only there: the one sentence the holder reads about why the benefit is locked. A row in any other state that carries it is refused. |
| `needsConnect` | Optional, true or false. true on a benefit that opens through Yutai Connect: the list draws the row locked with the Connect button beneath it until owner-customer is true. Any other value is refused. The distributor API's rows carry none. |
| `sourceMode` | The data mode the row was read in, one of SIMULATED, SANDBOX or LIVE. The distributor API's rows carry it; the list does not draw it. |

## The parts

Each piece of the list is a named part inside its shadow root, so your page can restyle one without touching the others. The list carries three parts, and a name it does not carry matches nothing.

| Part | What it restyles |
| --- | --- |
| `benefits-row` | Each benefit card, whatever its state: eligible with its claim press, locked with the sentence that says why, claimed or redeemed. |
| `benefits-claim` | The claim press on an eligible card. The list writes nothing when it is pressed: the claim is your own action. |
| `benefits-connect` | The Connect press beneath a row that needs the holder to link a card first. |

Restyle the parts

```
yutai-benefits-list::part(benefits-row) { background: teal; }
yutai-benefits-list::part(benefits-claim) { background: navy; color: white; }
```

## The tokens

Set the tokens you want on the element itself, the way the member template does: a value the element carries beats its own binding.

| Token | What it changes |
| --- | --- |
| `--accent` | The primary action colour. |
| `--accent-foreground` | The text colour that sits on the primary action colour. |
| `--focus` | The colour focus rings wear. |
| `--link` | The colour a link wears. |
| `--radius` | The corners the element rounds with, 0 to 0.75rem. |
| `--yutai-font-sans` | The font stack the element's text renders in. |
| `--background` | The page background around the element, from the approved neutral list. |

## The claim event

A press on an eligible card is your distributor's own action: the list tells your page which benefit was pressed, and your page claims it.

| Name | What it carries |
| --- | --- |
| `yutai-claim` | Fired once each time the holder presses the claim on an eligible card. It bubbles and crosses the element's shadow root, so a listener on your document hears it. The list writes nothing itself: the claim is your action. |
| `detail.benefitId` | The pressed row's benefitId, exactly as your benefits attribute gave it. |

Claim listener

```
document.addEventListener("yutai-claim", (event) => {
  const { benefitId } = event.detail;
  // Claim the benefit through your own server, then give the list its new rows.
});
```

The claim itself is your own call: the [API reference](https://docs.yutai.com/api) carries the claim calls, the [benefits module](https://docs.yutai.com/benefits-module)'s claim sheet runs one for the holder, and [Errors and limits](https://docs.yutai.com/errors) explains a refusal.

## The refusals

The list never paints a row it cannot read. A refusal happens before anything renders, so the list keeps its last good paint, and it reaches your page as one yutai-error event for each refusal, its detail carrying the list's tag and one plain sentence. A React page hears the same sentence through the wrapper's onError prop: [the React page's onError note](https://docs.yutai.com/react#props-heading) says how.

For a row that is not an object, the sentence is:

> Row 1 of the benefits attribute is not a benefit row. Each row is an object carrying benefitId, name and state.

For a row with no benefitId, the sentence is:

> Row 1 of the benefits attribute has no benefitId. Set benefitId to the benefit's id, like "bft_01H8Z2N9P4QX7K3M5A0V8D9T2W".

For a benefitId that is not text, the sentence is:

> Row 1 of the benefits attribute names a benefitId that is not text. Set benefitId to the benefit's id, like "bft_01H8Z2N9P4QX7K3M5A0V8D9T2W".

For a benefitId that is not a benefit id, the sentence is:

> Row 1 of the benefits attribute names benefitId "bft_not_a_benefit_id" and that is not a benefit id. Set benefitId to the benefit's id, like "bft_01H8Z2N9P4QX7K3M5A0V8D9T2W".

For a row with no name, the sentence is:

> Row 1 of the benefits attribute has no name. Set name to the words the benefit is named by, like "Season voucher".

For a name that is not text, the sentence is:

> Row 1 of the benefits attribute names a name that is not text. Set name to the words the benefit is named by, like "Season voucher".

For a name past the length the list draws, the sentence is:

> Row 1 of the benefits attribute carries a name of 129 characters and the list draws at most 128. Shorten name to 128 characters or fewer.

For a row with no state, the sentence is:

> Row 1 of the benefits attribute has no state. Set state to eligible, locked, claimed, redeemed and unavailable.

For a state that is not text, the sentence is:

> Row 1 of the benefits attribute names a state that is not text. Set state to eligible, locked, claimed, redeemed and unavailable.

For a state the list does not draw, the sentence is:

> Row 1 of the benefits attribute names state "renewed" and that is not a state the list draws. Set state to eligible, locked, claimed, redeemed and unavailable.

For a locked row with no lockedReason, the sentence is:

> Row 1 of the benefits attribute is locked and carries no lockedReason. Set lockedReason to the one sentence the holder reads, like "This benefit opens once the wallet keeps 15 SGH for 30 days.".

For a locked row whose lockedReason is not a sentence, the sentence is:

> Row 1 of the benefits attribute is locked and its lockedReason is not a sentence. Set lockedReason to the one sentence the holder reads, like "This benefit opens once the wallet keeps 15 SGH for 30 days.".

For a lockedReason past the length the list draws, the sentence is:

> Row 1 of the benefits attribute carries a lockedReason of 513 characters and the list draws at most 512. Shorten lockedReason to 512 characters or fewer.

For a lockedReason on a row that is not locked, the sentence is:

> Row 1 of the benefits attribute carries lockedReason, and only a locked row carries one. Remove it, or set the row's state to locked.

For a needsConnect that is not true or false, the sentence is:

> Row 1 of the benefits attribute names a needsConnect that is not true or false. Set needsConnect to true on a benefit that opens through Yutai Connect, or leave it out.
