@get-asset/sdk
Global Receipts Config
The receipt-request setting as a whole: a title, the copy explaining what the rule does and who it applies to, one ReceiptsThreshold row per country the tenant has a threshold in, and a Save that commits the lot. Reads the configs from GET /v0/tenant/rules/receipts and turns each country in the payload into a row. Where a standalone row reports every edit immediately, GlobalReceiptsConfig batches — the rows edit a draft held in the Root, and nothing leaves the component until Saveis pressed. That's what makes one Save button for several rows honest. Save then writes the whole set back through PATCH /v0/tenant/rules/receipts, telling you through onSaveSuccess / onSaveError how it went — or hands the request to your own onSave.
Preview
Loading preview…
Anatomy
Import the primitive and compose the parts you need.
<GlobalReceiptsConfig.Roottitle="Auto-request Receipts"subtitle="Businesses are asked for a receipt when a transaction is over the amount you set."note="This is the default for your businesses."onSaveSuccess={() => toast("Receipt settings saved")}><GlobalReceiptsConfig.Title /><GlobalReceiptsConfig.Subtitle /><GlobalReceiptsConfig.Note />{/* One ReceiptsThreshold.Root per entry — compose its parts inside. */}<GlobalReceiptsConfig.Thresholds><GlobalReceiptsConfig.Threshold><ReceiptsThreshold.Title /><ReceiptsThreshold.Subtitle /><ReceiptsThreshold.Field><ReceiptsThreshold.Currency /><ReceiptsThreshold.Amount /></ReceiptsThreshold.Field><ReceiptsThreshold.Toggle /></GlobalReceiptsConfig.Threshold></GlobalReceiptsConfig.Thresholds><GlobalReceiptsConfig.SaveError><GlobalReceiptsConfig.SaveErrorMessage /></GlobalReceiptsConfig.SaveError><GlobalReceiptsConfig.Save /></GlobalReceiptsConfig.Root>
Required scopes
The access token used by AssetProvider must include these scopes for this primitive to fetch data successfully:
tenant:readtenant:update
API reference
Root props
| Prop | Type | Default | Description |
|---|---|---|---|
titlerequired | string | — | The card's label, e.g. "Auto-request Receipts". |
subtitlerequired | string | — | The line explaining what the rule does. |
note | string | — | A second line for who the setting applies to, e.g. "This is the default for your businesses." Optional — Note renders nothing without it. |
thresholds | GlobalReceiptsConfigThreshold[] | — | Override the fetched configs, skipping the request entirely — for storybook, tests, or a host that already holds the setting. One entry per row: { id, title, subtitle, currency, amount, enabled }. Seeds the draft and adopts every later change. |
state | "loading" | "error" | "empty" | — | Force a state, skipping the request — storybook/tests. |
saveError | Error | — | Force the save-error state — storybook/tests. A real one only appears after a failed write, which needs an edit and a press to reach, so the state isn't otherwise renderable on its own. |
onSave | (thresholds: GlobalReceiptsConfigThreshold[]) => void | Promise<void> | — | Own the write. Called with the whole edited set when Save is pressed, instead of the built-in PATCH — return a promise and the card shows its saving state until it settles, and reports a rejection through SaveError. Without it the card writes for itself and reseeds the rows from the response; with it, the draft stays dirty until saved values come back in as thresholds. Named for what it does rather than as an on… handler: it performs the save, it isn't told about one. |
onSaveSuccess | (thresholds: GlobalReceiptsConfigThreshold[]) => void | — | Called after a save succeeds — whichever path wrote it — with the rows as saved: the server-confirmed set when the card wrote, the submitted set when onSave did. For a toast or a page-level “saved” marker; the card needs nothing back. |
onSaveError | (error: Error) => void | — | Called when a save fails, with the same error SaveError renders. The generated client rejects with the parsed error body rather than an Error, so this is normalised into one — the body rides along as `cause`. |
saving | boolean | — | Force the saving state — storybook/tests. Otherwise it follows the write in flight. While set, the rows go inert and Save stops accepting presses. |
disabled | boolean | — | Make the whole card inert (e.g. the viewer can't change the setting). |
editable | boolean | — | Whether the setting can be changed here. Defaults to true; pass false and the controls go inert and Save isn't rendered at all. Different from disabled, which keeps the button on screen but unpressable — that says “not now”, this says “not here”. |
saveLabel | string | — | Override the copy on the save button. Defaults to "Save". |
Parts
State wrappers
The four fetch states are mutually exclusive — compose your state UI by nesting children inside them. Dirty and Saving are about the draft and cut across all four.
| Part | Description |
|---|---|
GlobalReceiptsConfig.Loading | Rendered while the tenant's configs are being read. |
GlobalReceiptsConfig.Error | Rendered when the read failed. Compose ErrorMessage and Retry inside. |
GlobalReceiptsConfig.Success | Rendered once at least one country's config has landed. |
GlobalReceiptsConfig.Empty | Rendered when the read succeeded but no country has a threshold configured. |
GlobalReceiptsConfig.Dirty | Rendered only while the draft differs from the configs it was seeded from — e.g. an “unsaved changes” note beside Save. |
GlobalReceiptsConfig.Saving | Rendered only while a save is in flight. |
GlobalReceiptsConfig.SaveError | Rendered when the last save failed. Distinct from Error, which is the read failing: a failed write still has rows on screen and a draft worth keeping, so it belongs beside Save rather than in place of the card. |
Parts
| Part | Description |
|---|---|
GlobalReceiptsConfig.TitleasChild? | The card's label, from the Root's title. |
GlobalReceiptsConfig.SubtitleasChild? | The line explaining what the rule does, from the Root's subtitle. |
GlobalReceiptsConfig.NoteasChild? | The second line — who the setting applies to. Renders nothing when the Root got no note, so the styled layer doesn't have to guard it. |
GlobalReceiptsConfig.Thresholds | Iterates the thresholds, wrapping its children in a ReceiptsThreshold.Root per entry — so the rows are the ReceiptsThreshold compound, composed with its own parts, and every edit lands in this card's draft instead of going straight to you. Each row is disabled while the card is disabled or saving. |
GlobalReceiptsConfig.ThresholdasChild? | Wrapper for one row, inside Thresholds. Carries data-threshold (the entry's id) and data-enabled, so a row can be styled or found by which threshold it is without reading the hook. |
GlobalReceiptsConfig.ErrorMessageasChild?, format? | The error copy. Defaults to the standard “We couldn't load your receipt settings…” line; pass format to override it. |
GlobalReceiptsConfig.RetryasChild? | Refetches the tenant's configs. Renders a button unless slotted. |
GlobalReceiptsConfig.SaveErrorMessageasChild?, format? | The failed-write copy. Defaults to “We couldn't save your receipt settings. Try again.”; pass format to override it. Renders nothing when the last save didn't fail, so it can stand on its own without SaveError around it — the same shape as TransactionMatch.ActionError. |
GlobalReceiptsConfig.SaveasChild? | Writes the draft — the built-in PATCH, or your onSave. Disabled until there is something to save: an untouched card, a save in flight, a failed read, or a disabled one all leave it inert. A card that is not editable renders no button at all. Prints "Save" unless you pass a saveLabel or your own children. Carries data-saving. |
Hooks
| Part | Description |
|---|---|
useGlobalReceiptsConfig() | Hook returning { title, subtitle, note, thresholds, loading, error, hasData, retry, dirty, saving, saveError, disabled, canSave, saveLabel, setAmount, setEnabled, save }. `thresholds` is the draft as currently edited — the same set Save reports. Throws when used outside <GlobalReceiptsConfig.Root>. |
The request and the rows
One request on mount: GET /v0/tenant/rules/receipts (scope tenant:read). It answers with the tenant's settings keyed by country — { CA: { is_enabled, threshold_amount }, US: {…} } — and, unlike most of this API, with no data envelope around them.
That payload carries the settingand nothing else, so the row's label, its explanation, and the currency the threshold is denominated in come from a country map inside the component: CA → “Canada” / CAD, US→ “United States” / USD. The map is keyed by the API's own country enum, so a country added upstream fails the build here rather than rendering a row labelled with its bare code, and its declaration order is the row order — a refetch can't reshuffle the card.
Per entry: threshold_amount is optional and reads as 0 when absent, which is the API's own “ask for every spend”; is_enabled is optional and reads as off. A country missing from the payload gets no row at all — an absent entry is “not configured”, not “configured to nothing” — and a payload with no countries at all lands in Empty.
Save sends PATCH /v0/tenant/rules/receipts (scope tenant:update) with { configs: { CA: { is_enabled, threshold_amount }, … } } — every country at once, because that endpoint upserts the whole set rather than one row, which is exactly what one Save button for several rows produces. Its response is the updated map in the same shape the read returns, so it seeds the read cache directly: the rows reseed from what the server now holds, Save goes inert, and no second GET is fired.
Pass thresholds to skip the read and render your own set, or state to force a state. Either one takes the read offline, which is how the stories and tests run; onSave does the same for the write.
The draft and Save
The rows don't report to you directly. GlobalReceiptsConfig.Thresholds hands each ReceiptsThreshold.Root an onAmountChange / onEnabledChange of its own that writes into a draft held in the config Root, so a row commit (blur, Enter, a switch flip) moves the draft and nothing else. Save is the only thing that writes, and it sends the whole set.
dirty compares values, not events: a threshold typed away from 30 and back to 30 leaves nothing to save, and Save goes inert again. The draft is reseeded the same way — by value — so a background refetch that returns what the card already has cannot throw away an edit in progress, while server values that actually moved do reseed it (someone else changed the setting, and their write is the one on record).
Saveis also inert while the configs are loading or the read failed — there's nothing to save on top of values that never arrived.
A failed write keeps the draft: the rows stay as the user left them and Save stays pressable, so a retry costs nothing. SaveError is where that surfaces — separate from Error, which replaces the card when the read fails.
States
saving and disabledboth make the card inert, and both pass straight through to every row's disabled — so the threshold fields and switches stop responding, and Save with them. They differ only in what they mean: saving is a write in flight, disabledis a viewer who can't change the setting at all.
A row's own on/off state is separate again, and belongs to the row: see ReceiptsThreshold's states.