# Getting started (/components/getting-started)

This package is the public npm surface for Tesouro embedded React widgets.

`@tesouro/embedded-components-react` is an ESM package for bundler-based React
apps (Vite, webpack, esbuild, Next.js). `react` and `react-dom` (v19) are peer
dependencies.

These steps put one widget on screen: a token route on your server, a provider
in the browser, and the widget itself. The examples use the Next.js App Router.

Using a coding assistant? Point it at
[`llms-full.txt`](https://components.tesouro.com/llms-full.txt), which has every
page of the docs site as Markdown in one file.
[`llms.txt`](https://components.tesouro.com/llms.txt) is the index.

## Before you start

You need three credentials from Tesouro: a client ID, a client secret and a
widget secret. Authentication › User token in the
[Embedded Banking](https://docs.tesouro.com/embedded-banking/guides/authentication/user-token)
or [FinOps](https://docs.tesouro.com/finops/guides/authentication/user-token)
docs explains how to request them. All three stay on your server.

## 1. Install

```bash
npm install @tesouro/embedded-components-react
npm install @tesouro/embedded-components-widget-token
```

The second package mints widget tokens and handles your secrets, so only your
server imports it.

## 2. Add a token route

A widget acts as one of your users, authenticated by a short-lived widget token
that your server mints. This route handler, at `app/api/widget-token/route.ts`,
returns the `{ widgetToken, exp }` shape the provider in step 3 expects:

```ts
import { NextRequest, NextResponse } from 'next/server';
import { configureCreateWidgetToken } from '@tesouro/embedded-components-widget-token';

const mintWidgetToken = configureCreateWidgetToken({
  clientId: process.env.TESOURO_CLIENT_ID!,
  clientSecret: process.env.TESOURO_CLIENT_SECRET!,
  widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
});

export async function POST(req: NextRequest) {
  const session = await getSession(req);
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const { widgetToken, exp } = await mintWidgetToken({
    userId: session.userId,
    userEmail: session.userEmail,
    organizationReference: session.organizationReference,
  });

  return NextResponse.json(
    { widgetToken, exp },
    { headers: { 'Cache-Control': 'no-store' } },
  );
}
```

`getSession` stands in for your own server-side session check. Take the user
and their `organizationReference` (your stable ID for the business they belong
to) from that session, never from the request: the route mints a valid token
for whatever identity it's given. Never expose the three credentials to the
browser, for example through a `NEXT_PUBLIC_` variable.

[`@tesouro/embedded-components-widget-token`](/providers/widget-tokens)
documents the other options, including the token lifetime.

Backend not on Node? Build the token yourself: Authentication › User token in
the [Embedded Banking](https://docs.tesouro.com/embedded-banking/guides/authentication/user-token)
or [FinOps](https://docs.tesouro.com/finops/guides/authentication/user-token)
docs specifies its claims and encryption. Your route returns the same
`{ widgetToken, exp }`.

## 3. Add the provider

`RefreshingRootWidgetProvider` calls your route on mount, fetches a new token
shortly before `exp`, and shares the signed-in scope with every widget inside
it. It runs in the browser, so in the App Router it goes in a Client Component
that your root layout renders, such as `app/providers.tsx`. Import the
stylesheet there too: widgets render unstyled without it.

```tsx
'use client';

import '@tesouro/embedded-components-react/styles.css';
import type { ReactNode } from 'react';
import {
  RefreshingRootWidgetProvider,
  type WidgetTokenFetcher,
} from '@tesouro/embedded-components-react';

const fetcher: WidgetTokenFetcher = async () => {
  const res = await fetch('/api/widget-token', { method: 'POST' });
  if (!res.ok) throw new Error('Widget token request failed');
  return res.json();
};

export function Providers({ children }: { children: ReactNode }) {
  return (
    <RefreshingRootWidgetProvider
      baseUrl="https://api.sandbox.tesouro.com"
      fetcher={fetcher}
    >
      {children}
    </RefreshingRootWidgetProvider>
  );
}
```

Use `https://api.sandbox.tesouro.com` while you build and
`https://api.tesouro.com` in production.

## 4. Render a widget

Any widget inside the provider is ready to use:

```tsx
import { BankAccountsWidget } from '@tesouro/embedded-components-react';

export default function AccountsPage() {
  return <BankAccountsWidget />;
}
```

Browse the widgets for
[Embedded Banking](/components?product=embedded-banking)
or [FinOps](/components?product=finops). Every provider
and widget prop is typed in the shipped declarations, so your editor documents
them inline.

## Entry points

| Import path                                       | What you get                                                                             |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `@tesouro/embedded-components-react`              | All widgets, `WidgetSuite`, and the providers, including `RefreshingRootWidgetProvider`. |
| `@tesouro/embedded-components-react/styles.css`   | The widget stylesheet. See About the stylesheet.                |
| `@tesouro/embedded-components-react/core`         | Framework-agnostic token utilities (`createWidgetTokenManager`).                         |
| `@tesouro/embedded-components-react/experimental` | Work in progress, with no compatibility promise. See below. |

## Experimental entry point

`@tesouro/embedded-components-react/experimental` (and `.../experimental/<Module>`)
holds work in progress. It is **outside semver**: exports
can change or disappear in any release, including a patch, and aren't documented
here. Use it only if we point you at a specific module, and pin an exact version.

## About the stylesheet

The stylesheet won't restyle your host page. It ships no global CSS reset (the
reset is scoped under the `.tesouro-embedded` wrapper every widget renders), and
all utilities are `ttw`-prefixed, so it won't collide with your own Tailwind.
Design tokens (`--ttw-*`) are declared on `:root` / `.dark`. Dark mode follows an
ancestor `.dark` class, and you can set the widget font with `--ttw-font-family`.

## Theming

Widgets read their look from `--ttw-*` CSS variables. Set them on `:root`, or
on any element around the widgets, in plain CSS or with `createTheme`.

**Basic theming** is all most integrations need: your brand color, the corner
radius and the font.

```tsx
import type { CSSProperties } from 'react';
import { createTheme, themeToCss } from '@tesouro/embedded-components-react';

const theme = createTheme({
  primary: '#0f766e',
  primaryForeground: '#ffffff',
  radius: '0.5rem',
  fontFamily: 'Inter, sans-serif',
});

// On a wrapper around the widgets…
<div style={theme as CSSProperties}></div>;
// …or once for the page: themeToCss(theme) prints a `:root { … }` rule.
```

A wrapper theme reaches the widgets' dialogs, sheets and menus too, although
they render at the end of `<body>`: the widgets copy the wrapper's values onto
their overlay root each time they render.

Nested widgets share that overlay root. When a themed widget sits inside
another widget (or inside another `WidgetProvider` / `RootWidgetProvider`
scope), its dialogs, sheets and menus render into the outer scope's overlay
root and so take the outer theme. If two widgets need different themes, theme
the outermost wrapper, or render them side by side rather than nested.

**Advanced theming** reaches every public variable:

| Group      | Keys (`createTheme`) and variables                                                                                                                                               |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Surfaces   | `background`, `foreground`, `card`, `cardForeground`, `popover`, `popoverForeground`, `muted`, `mutedForeground` (`--ttw-background` …)                                          |
| Actions    | `primary`, `primaryForeground`, `secondary`, `secondaryForeground`, `accent`, `accentForeground`, `destructive`, `destructiveForeground`                                         |
| Lines      | `border`, `input`, `ring`                                                                                                                                                        |
| Tones      | `info`, `success`, `warning`, and `destructive`: one color each for a status badge                                                                                               |
| Accents    | `violet`, `cyan`: for a status no tone carries                                                                                                                                   |
| Overrides  | `<tone>Subtle` and `<tone>Foreground` (`--ttw-success-subtle`, `--ttw-success-foreground`; for destructive, `destructiveSubtle` and `destructiveSubtleForeground`), all optional |
| Overlay    | `overlay`: the scrim behind dialogs and sheets, black at 50% by default                                                                                                          |
| Shape/type | `radius` (0–1.25rem), `fontFamily`                                                                                                                                               |

A tone or accent is one color. The badge's tinted fill and its text are
derived from it where the badge is painted, so they follow a dark card or a
tinted page without extra variables; set the `Subtle` / `Foreground` override
only to pick either half yourself.

**Compatibility variables.** `surfaceSubtle` (`--ttw-surface-subtle`) and the
eight sidebar keys (`sidebar`, `sidebarForeground`, `sidebarPrimary`,
`sidebarPrimaryForeground`, `sidebarAccent`, `sidebarAccentForeground`,
`sidebarBorder`, `sidebarRing`) keep working for existing themes and will be
removed in a future major version. None is declared: until you set one,
`surfaceSubtle` is `muted` and each sidebar color is its base color (`card`,
`foreground`, `primary`, `accent`, `border`, `ring`). A new theme can leave
them out.

In development, `createTheme` warns once in the console about unknown keys,
text under 4.5:1 contrast (3:1 for `mutedForeground` on `muted`) and a radius
outside 0–1.25rem, and the widgets run the same contrast check once against
the theme they actually render with.
Nothing warns in production, and a warning never stops a widget rendering.
`themeIssues(tokens)` returns the same findings as data, for a test in your
own CI; a variable you leave out is judged at its default, so
`themeIssues(createTheme({ primary }))` checks `primary` against the default
`primaryForeground`.

A status colour you set (`info`, `success`, `warning`, `destructive`,
`violet`, `cyan`) gets its pale fill and its text derived from it. Browsers
without CSS relative colours derive them a different way, so `themeIssues`
checks each one both ways: a `contrast` issue means it fails in current
browsers, and a `fallback-contrast` issue means it only fails in those older
ones. Setting its text colour explicitly (`warningForeground`, or
`destructiveSubtleForeground` for `destructive`) fixes both. If your CI
switches on `issue.kind`, handle `fallback-contrast` too.

Only the variables above are supported. Tailwind's own variables share the
`--ttw-` prefix (`--ttw-spacing`, `--ttw-text-sm`, `--ttw-color-*` …) because
the utilities are prefixed `ttw`; they are pinned inside the widgets, so
setting one has no effect. `--ttw-chart-*` were removed.

## UI frameworks (shadcn default, Tecton optional)

Widgets render with the default shadcn surface and need no configuration. Tecton
(the Q2 / Stencil design system) ships as a separate, opt-in package:

```bash
npm i @tesouro/embedded-components-react-tecton-ui
```

```ts
import { registerTectonUI } from '@tesouro/embedded-components-react-tecton-ui';
registerTectonUI();
```

Call `registerTectonUI()` once at app startup, then set `uiFramework="tecton"` on a provider. Without the extension installed it
falls back to shadcn. See the extension's README for setup.

### TypeScript module resolution

Set `"moduleResolution"&#x60; to &#x2A;*`"bundler"`** (or `"node16"` / `"nodenext"`); every
entry point then resolves, and the declarations type-check with
`skipLibCheck: false`. Legacy `"node"` / `node10` resolution is **not supported**
(it ignores the `exports` map and reports `TS2307`). The package is ESM-only and
can't be `require()`d from CommonJS.

# Component and root providers (/providers/widget-provider)

Cascading React context providers that give embedded widgets an authenticated scope. They create the API client, load widget init, and pass config down the tree so each widget needs no setup of its own.

***

## Overview

Config resolves from three layers. Each level inherits every field it doesn't set from the level above:

| Layer                  | How to set                                       | Priority |
| ---------------------- | ------------------------------------------------ | -------- |
| **WidgetProvider**     | `<WidgetProvider widgetToken="...">` in the tree | Highest  |
| **RootWidgetProvider** | `<RootWidgetProvider baseUrl="..." ...>` at root | Mid      |
| **Global store**       | `setGlobalWidgetConfig({ ... })` outside React   | Lowest   |

For `widgetToken` and `organizationId`, `undefined` means "inherit", `null` means "explicitly cleared" (descendants see no value even if an ancestor set one), and a string overrides any ancestor.

Once `baseUrl` and `widgetToken` both resolve, the provider loads widget init and exposes the result as `initResponse` through `useWidgetConfig()`. `initResponse` carries the user's `status`, `scopes`, `disclosuresRequired` and `organizationId`. When no level sets `organizationId`, it defaults to `initResponse.organizationId`, so org-scoped widgets wait for init. Changing `organizationId` alone doesn't reload init.

Every `WidgetProvider`, including the one each widget mounts, wraps its subtree in an error boundary (see Error boundary). `RootWidgetProvider` doesn't. For automatic token rotation, use [`WidgetTokenRefreshProvider`](/providers/widget-token-refresh-provider).

***

## Quick Start

### Single provider for the whole app

```tsx
import {
  RootWidgetProvider,
  BalancesWidget,
} from '@tesouro/embedded-components-react';

export function App() {
  return (
    <RootWidgetProvider
      baseUrl="https://api.tesouro.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
    >
      <BalancesWidget />
    </RootWidgetProvider>
  );
}
```

### Widget as a self-contained web component

With no provider ancestor, a `WidgetProvider` acts as its own root.

```tsx
import { WidgetProvider } from '@tesouro/embedded-components-react';

function PaymentWidget({ baseUrl, token }: { baseUrl: string; token: string }) {
  return (
    <WidgetProvider baseUrl={baseUrl} widgetToken={token}>
      <PaymentWidgetContent />
    </WidgetProvider>
  );
}
```

***

## Providers

### `RootWidgetProvider`

Place this **once** near the root of your React tree. It resolves `baseUrl`, `widgetToken` and `organizationId` from its props (falling back to the global store), loads widget init, and provides the HTTP client and query cache to every descendant. Rotating `widgetToken` doesn't recreate the client. Requests to known Tesouro API hosts are routed through the widget gateway automatically; set `gatewayRouting` only for a custom domain (`true`) or a host that routes its own requests (`false`). Pass `configClient` as a stable reference (hoisted or `useCallback`); a new function recreates the client.

#### Props

| Prop                            | Type                                         | Description                                                                                                                                                    |
| ------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`                       | `string`                                     | Base URL of the Tesouro embedded API, such as `"https://api.tesouro.com"`. Falls back to `global.baseUrl`.                                                     |
| `widgetToken`                   | `string \| null`                             | Bearer token for widget auth. Falls back to `global.widgetToken`; `null` suppresses auth.                                                                      |
| `organizationId`                | `string \| null`                             | Org ID sent with data requests. Falls back to `global.organizationId`, then to `initResponse.organizationId`; `null` clears org scoping.                       |
| `configClient`                  | `(client: EmbeddedClient) => EmbeddedClient` | Post-creation hook for the HTTP client, applied after the built-in auth and routing interceptors. Falls back to `global.configClient`.                         |
| `gatewayRouting`                | `boolean`                                    | Forces widget-gateway routing on (`true`) or off (`false`); unset, it applies automatically to known Tesouro API hosts. Falls back to `global.gatewayRouting`. |
| `linkComponent`                 | `LinkComponent`                              | Host link component widgets use for navigational links. Falls back to `global.linkComponent`.                                                                  |
| `uiFramework`                   | `'shadcn' \| 'tecton' \| null`               | UI framework for this tree; falls back to `global.uiFramework`, then `'shadcn'`.                                                                               |
| `analytics`                     | `boolean`                                    | Enables anonymous analytics (default `true`); `false` disables capture and skips loading PostHog.                                                              |
| `unstable_initResponseOverride` | `WidgetInitResponse`                         | First-party hosts only: skips the init fetch and uses this object as `initResponse`. Embed integrations minting widget JWEs must leave this unset.             |
| `disclosuresAcceptance`         | `ReactNode`                                  | Accept surface shown when the user owes disclosures; pass `<AcceptDisclosuresWidget />`. Cascades to nested providers.                                         |
| `providerLabels`                | `Partial<WidgetProviderLabels>`              | Overrides the copy a provider renders in place of a widget (error fallback, disclosures gate). Cascades to nested providers.                                   |
| `children`                      | `ReactNode`                                  | The React subtree that consumes the widget context.                                                                                                            |

> `RootWidgetProvider` doesn't take `errorFallback` / `onError`. Set them on a `WidgetProvider` inside it.

***

### `WidgetProvider`

Mid-tree provider for overriding config for one subtree. It has three modes:

* **Pass-through** (no props): inherits everything; no fetch, no new client. Loading, error and refetch come from the nearest ancestor that owns a fetch.
* **Override** (`baseUrl` or `widgetToken` set): loads its own init and creates its own client for this subtree only. While its fetch is in flight it shows the parent's `initResponse`.
* **Standalone** (no provider ancestor): behaves like `RootWidgetProvider`. Use this for widgets mounted as isolated web components with their own React root.

#### Props

| Prop                    | Type                                               | Description                                                                                                                             |
| ----------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`               | `string`                                           | Overrides the base URL for this subtree and creates a new client. Inherits when omitted.                                                |
| `widgetToken`           | `string \| null`                                   | Overrides the token for this subtree and reloads init; `null` suppresses auth here. Inherits when omitted.                              |
| `organizationId`        | `string \| null`                                   | Overrides the org ID for this subtree without reloading init; `null` clears org scoping. Inherits when omitted.                         |
| `configClient`          | `(client: EmbeddedClient) => EmbeddedClient`       | Post-creation hook for this provider's client; not called in pass-through mode. Inherits when omitted.                                  |
| `gatewayRouting`        | `boolean`                                          | Forces widget-gateway routing on or off for this subtree's client; unset, it applies to known Tesouro API hosts. Inherits when omitted. |
| `linkComponent`         | `LinkComponent`                                    | Overrides the host link component for this subtree. Inherits when omitted.                                                              |
| `uiFramework`           | `'shadcn' \| 'tecton' \| null`                     | Overrides the UI framework for this subtree; omitted or `null` inherits, defaulting to `'shadcn'`.                                      |
| `errorFallback`         | `ReactNode \| (props: FallbackProps) => ReactNode` | Custom error-boundary fallback: a node, or a function receiving `{ error, resetErrorBoundary }`.                                        |
| `onError`               | `(error: unknown, info: ErrorInfo) => void`        | Telemetry hook, called once per caught error before the fallback renders.                                                               |
| `analytics`             | `boolean`                                          | Enables anonymous analytics (default `true`); honored only in standalone mode, ignored on a nested provider.                            |
| `disclosuresAcceptance` | `ReactNode`                                        | Accept surface shown when the user owes disclosures; pass `<AcceptDisclosuresWidget />`. Inherits when omitted.                         |
| `providerLabels`        | `Partial<WidgetProviderLabels>`                    | Overrides the copy this provider renders in place of the widget. Inherits when omitted.                                                 |
| `children`              | `ReactNode`                                        | The React subtree that consumes the overridden context.                                                                                 |

***

## UI framework

`uiFramework` picks the look (`'shadcn'` default, or `'tecton'`) and keeps the widget API identical. It cascades like other config, so a subtree can opt in on its own:

```tsx
import {
  RootWidgetProvider,
  WidgetProvider,
  UIFramework,
} from '@tesouro/embedded-components-react';

<RootWidgetProvider baseUrl={API_URL} widgetToken={token}>
  <Dashboard />
  <WidgetProvider uiFramework={UIFramework.Tecton}>
    <PilotWidget />
  </WidgetProvider>
</RootWidgetProvider>;
```

***

## Hooks

### `useWidgetConfig()`

Returns the resolved config at the nearest provider, including `initResponse`. Outside any provider it reads the global store.

```ts
useWidgetConfig(): WidgetConfig
```

### `useWidgetLoading()`

`true` while the nearest fetch-owning provider has a request in flight; `false` outside any provider. If `initResponse` is still defined while loading, it is a background refetch and the previous data is safe to show.

```ts
useWidgetLoading(): boolean
```

### `useWidgetError()`

The latest init error from the nearest fetch-owning provider, or `null`. It clears when the token or base URL changes or a fetch succeeds. A first-load failure leaves `initResponse` undefined; a failed refetch keeps the previous one. API errors carry the response body as `error.cause`, so don't log the whole error object.

```ts
useWidgetError(): Error | null
```

### `useRefetchWidget()`

A stable function that reloads init at the nearest fetch-owning provider, keeping the current `initResponse` visible meanwhile. A no-op outside any provider.

```ts
useRefetchWidget(): () => void
```

### `useOwnWidgetConfig()`

Only the fields set explicitly at the current provider level, never `initResponse`; `null` outside any provider.

```ts
useOwnWidgetConfig(): WidgetConfigInput | null
```

***

## Error boundary

Every `WidgetProvider` catches render-time exceptions in its subtree, so one failing widget doesn't crash the page. `RootWidgetProvider` has no boundary of its own: widgets are covered by the `WidgetProvider` they mount, but your own JSX placed directly under the root isn't, so wrap it in a `WidgetProvider` if it needs one. Resetting the boundary also retries failed queries, and a token change resets it automatically. Don't add your own boundary to catch widget errors; pass `errorFallback` / `onError` instead.

```tsx
<RootWidgetProvider baseUrl="https://api.tesouro.com" widgetToken={token}>
  <WidgetProvider
    errorFallback={({ error, resetErrorBoundary }) => (
      <ErrorPanel error={error} onRetry={resetErrorBoundary} />
    )}
    onError={(error, info) => errorTracker.capture(error, { extra: info })}
  >
    <Dashboard />
  </WidgetProvider>
</RootWidgetProvider>
```

Custom JSX placed directly inside `RootWidgetProvider`, outside any `WidgetProvider`, isn't covered by crash telemetry.

***

## Provider copy

A provider renders its own UI in place of a widget in two cases: the error-boundary fallback, and the disclosures gate (see [`AcceptDisclosuresWidget`](/components/accept-disclosures)). Override that copy with `providerLabels`; unlisted keys keep their defaults, and a descendant's `providerLabels` replaces an ancestor's rather than merging.

```tsx
<RootWidgetProvider
  baseUrl="https://api.tesouro.com"
  widgetToken={token}
  disclosuresAcceptance={<AcceptDisclosuresWidget />}
  providerLabels={{
    disclosuresRequiredTitle: 'Review your account agreements to continue',
  }}
>
  <Dashboard />
</RootWidgetProvider>
```

See the exported `WidgetProviderLabels` type for every key. Without `disclosuresAcceptance`, a gated widget can only tell the user that acceptance is owed, with no way to act on it.

***

## Non-React hosts

### Global config store

Push config into the tree from outside React, before or after mount; mounted providers update automatically.

```ts
import {
  setGlobalWidgetConfig,
  updateGlobalWidgetConfig,
  getGlobalWidgetConfig,
} from '@tesouro/embedded-components-react';

setGlobalWidgetConfig({ baseUrl: 'https://api.tesouro.com', widgetToken });
updateGlobalWidgetConfig({ widgetToken: 'wt_rotated' });
const config = getGlobalWidgetConfig();
```

* `setGlobalWidgetConfig` **replaces** the whole config; omitted fields become `undefined`.
* `updateGlobalWidgetConfig` **merges** a patch; use it for single-field updates such as a new token.
* `getGlobalWidgetConfig` is a non-reactive read. `initResponse` can't be set here.

### Observing widget init

`getGlobalWidgetInitState()` and `subscribeToGlobalWidgetInitState(listener)` report the init lifecycle to hosts without hooks. The state is a `WidgetInitState` union: `'idle'`, `'loading'`, `'ready'` (with `initResponse`) or `'error'` (with `error`, and possibly `initResponse`).

```ts
import {
  getGlobalWidgetInitState,
  subscribeToGlobalWidgetInitState,
} from '@tesouro/embedded-components-react';

const initState = getGlobalWidgetInitState();
const unsubscribe = subscribeToGlobalWidgetInitState((state) => {
  host.toggleSpinner(state.status === 'idle' || state.status === 'loading');
  if (state.status === 'ready') {
    host.setScopes(state.initResponse.scopes);
  }
});
```

* The listener fires only on change, so also read the current state.
* The state stays `'idle'` until a token is available, so treat `'idle'` like `'loading'` while your host mints a token.
* `'loading'` means nothing usable yet; a background refetch stays `'ready'`.
* `'error'` with an `initResponse` means a refetch failed but the previous init is still valid. Show error UI only when `initResponse` is absent.
* Forward `state.error.message`, not the whole error, which carries the gateway response body.
* Only a top-level provider (a `RootWidgetProvider` or standalone `WidgetProvider`) publishes here. Nested overrides are visible only through `useWidgetConfig()`.

***

## Examples

### Gating the app on init

```tsx
function App() {
  return (
    <RootWidgetProvider
      baseUrl={API_URL}
      widgetToken={token}
      disclosuresAcceptance={<AcceptDisclosuresWidget />}
    >
      <WidgetReadyGate>
        <AppShell />
      </WidgetReadyGate>
    </RootWidgetProvider>
  );
}

function WidgetReadyGate({ children }: { children: React.ReactNode }) {
  const { initResponse } = useWidgetConfig();
  const loading = useWidgetLoading();
  const error = useWidgetError();
  const refetch = useRefetchWidget();

  if (loading && !initResponse) return <Skeleton />;
  if (error && !initResponse) return <ErrorBanner onRetry={refetch} />;
  return <>{children}</>;
}

function CardsNavItem() {
  const { initResponse } = useWidgetConfig();
  if (!initResponse?.scopes.includes('cards:read')) return null;
  return <NavLink to="/cards">Cards</NavLink>;
}
```

Gate on `initResponse`, not on `loading` alone: during a token rotation the previous response stays visible so on-screen widgets don't unmount.

### Per-subtree token override

```tsx
<RootWidgetProvider baseUrl={API_URL} widgetToken={platformToken}>
  <MainContent />
  <WidgetProvider widgetToken={partnerToken}>
    <PartnerWidget />
  </WidgetProvider>
</RootWidgetProvider>
```

`PartnerWidget` gets the partner's `initResponse`, and `useRefetchWidget()` inside it refetches with `partnerToken`.

### `organizationId` scoping

```tsx
<RootWidgetProvider
  baseUrl={API_URL}
  widgetToken={token}
  organizationId={currentOrgId}
>
  <OrgDashboard />
  <WidgetProvider organizationId={otherOrgId}>
    <OtherOrgPanel />
  </WidgetProvider>
  <WidgetProvider organizationId={null}>
    <GlobalPanel />
  </WidgetProvider>
</RootWidgetProvider>
```

`organizationId={null}` clears org scoping for that subtree.

***

## Analytics

The package sends a few anonymous PostHog events (provider lifecycle, API status and timing by route template, uncaught widget errors); no query strings, path IDs, pageviews or person profiles. Set `analytics={false}` on `RootWidgetProvider` or a standalone `WidgetProvider` to turn it off; on a nested provider the prop is ignored.

***

## TypeScript

```ts
import type {
  WidgetConfigInput,
  WidgetConfig,
  WidgetInitResponse,
  WidgetInitState,
  WidgetProviderLabels,
  WidgetProviderProps,
  RootWidgetProviderProps,
  LinkComponent,
} from '@tesouro/embedded-components-react';
import {
  UIFramework,
  type EmbeddedClient,
} from '@tesouro/embedded-components-react';
```

`WidgetInitResponse` is the init payload you gate on:

```ts
interface WidgetInitResponse {
  bankName?: string | null;
  disclosuresRequired?: 'NOT_REQUIRED' | 'REQUIRED';
  organizationId?: string | null;
  organizationTypes?: Array<
    | 'BANK'
    | 'EMBEDDED'
    | 'PLATFORM'
    | 'TRANSACTOR'
    | 'VERTICAL_SOFTWARE_PROVIDER'
  > | null;
  scopes: string[];
  status?: 'ACTIVE' | 'INACTIVE' | 'INVITED' | 'NOT_FOUND';
  userId?: string | null;
  vspName?: string | null;
}
```

# Component token refresh (/providers/widget-token-refresh-provider)

Fetches a widget token from your endpoint and refreshes it before it expires. Feed its live token into `RootWidgetProvider`, or use `RefreshingRootWidgetProvider`, which does both.

***

## Overview

Mount `WidgetTokenRefreshProvider` once at the app root with a `fetcher`. Inside it, read the live token with `useWidgetToken()` and pass `state.widgetToken` to `RootWidgetProvider` (or any `WidgetProvider`) as `widgetToken`. When the token rotates, the new value flows through props and descendant widgets refetch automatically.

For the common case of one refresh provider feeding one `RootWidgetProvider`, use `RefreshingRootWidgetProvider` instead.

### The fetcher

You implement one function that calls your backend and returns the token:

```ts
type WidgetTokenFetcher = () => Promise<{
  widgetToken: string;
  exp?: number;
}>;
```

* It is called on mount, `leadSeconds` before each `exp`, and on `refresh()`.
* Return `exp`, the token's expiry in unix seconds. Without it, no proactive refresh is scheduled.
* Throw (or reject) on failure. The provider retries with backoff.
* A result whose `exp` is already in the past counts as a failure.

Your endpoint creates the token on your server with
[`@tesouro/embedded-components-widget-token`](/providers/widget-tokens)
and can return its `{ widgetToken, exp }` result unchanged.

***

## Quick Start

```tsx
import {
  RefreshingRootWidgetProvider,
  type WidgetTokenFetcher,
} from '@tesouro/embedded-components-react';

const fetcher: WidgetTokenFetcher = async () => {
  const res = await fetch('/api/widget-token', { method: 'POST' });
  if (!res.ok) throw new Error('Widget token request failed');
  return res.json();
};

export function App() {
  return (
    <RefreshingRootWidgetProvider
      baseUrl="https://api.tesouro.com"
      fetcher={fetcher}
    >
      <Dashboard />
    </RefreshingRootWidgetProvider>
  );
}
```

### Manual composition

Use the two providers directly when you need to render UI between them, such as a loading splash.

```tsx
import {
  WidgetTokenRefreshProvider,
  RootWidgetProvider,
  useWidgetToken,
} from '@tesouro/embedded-components-react';

export function App() {
  return (
    <WidgetTokenRefreshProvider fetcher={fetcher}>
      <AppShell />
    </WidgetTokenRefreshProvider>
  );
}

function AppShell() {
  const { state } = useWidgetToken();
  if (state.status !== 'ready') return <Loading />;
  return (
    <RootWidgetProvider
      baseUrl="https://api.tesouro.com"
      widgetToken={state.widgetToken}
    >
      <Dashboard />
    </RootWidgetProvider>
  );
}
```

***

## Behaviour

* While a refresh is in flight or retrying, the last good token stays in use. The state only becomes `error` once that token's `exp` has passed.
* The provider doesn't write to the global widget config. Non-React hosts can mirror the token with `onToken={(t) => updateGlobalWidgetConfig({ widgetToken: t })}`; in React, pass it through props instead.
* The provider emits no analytics. Refresh failures surface only through `useWidgetToken()`, so monitor your token endpoint yourself if you need alerts.
* A subtree that needs a different token should pass `widgetToken` to its own `WidgetProvider`. The refresher doesn't touch it.
* To stop refreshing (for example on logout), unmount the provider.

***

## Props

| Prop          | Type                                                   | Default      | Description                                                                                                                   |
| ------------- | ------------------------------------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `fetcher`     | `() => Promise<{ widgetToken: string; exp?: number }>` | **Required** | Returns a token and its `exp` (unix seconds); see The fetcher.                                                |
| `leadSeconds` | `number`                                               | `60`         | Refresh this many seconds before `exp`. Lower it for very short-lived tokens.                                                 |
| `onToken`     | `(widgetToken: string) => void`                        | `undefined`  | Called once per distinct token, for telemetry, persistence, or mirroring the token into the global store for non-React hosts. |
| `children`    | `ReactNode`                                            | `undefined`  | The subtree that reads the token via `useWidgetToken()`.                                                                      |

***

## `useWidgetToken()`

Returns the token state from the nearest `WidgetTokenRefreshProvider`. Throws outside one.

* `state`: the current `WidgetTokenState` (below). Use it to feed `RootWidgetProvider` or show status.
* `refresh()`: forces a refresh and resolves when it settles. Only needed for user-initiated retries.
* `getState()`: reads the latest state synchronously, for non-React callbacks that need the token right after `await refresh()`.

| State                                                              | When                                                                                                          |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `{ status: 'loading' }`                                            | Until the first fetch resolves.                                                                               |
| `{ status: 'ready', widgetToken, exp?, isRefreshing, lastError? }` | A usable token is present. `lastError` is set when a refresh failed but the current token hasn't yet expired. |
| `{ status: 'error', error }`                                       | The first fetch failed, or the current token expired while refreshes kept failing.                            |

```tsx
function TokenStatusBadge() {
  const { state, refresh } = useWidgetToken();
  if (state.status === 'loading') return <Spinner />;
  if (state.status === 'error') {
    return <button onClick={refresh}>Retry</button>;
  }
  return state.isRefreshing ? <Spinner /> : <span>Token current</span>;
}
```

***

## `RefreshingRootWidgetProvider`

Mounts `WidgetTokenRefreshProvider` and `RootWidgetProvider` together and wires the live token into `widgetToken`. This is the recommended setup. While the token is loading or errored it passes `widgetToken={null}`, so widget init waits for a real token. Both `useWidgetToken()` and `useWidgetConfig()` work in its children.

### Props

`RefreshingRootWidgetProviderProps` is `Omit<RootWidgetProviderProps, 'widgetToken'> & WidgetTokenRefreshProviderProps`. `widgetToken` isn't accepted.

| Prop                            | Source                       | Notes                                                                                                    |
| ------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------- |
| `baseUrl`                       | `RootWidgetProvider`         | Falls back to the global store when omitted.                                                             |
| `organizationId`                | `RootWidgetProvider`         | Pass `null` to clear the org scope; falls back to the global store when omitted.                         |
| `configClient`                  | `RootWidgetProvider`         | Optional post-creation hook for the scoped HTTP client.                                                  |
| `gatewayRouting`                | `RootWidgetProvider`         | Overrides widget-gateway routing; unset, it applies automatically for known Tesouro API hosts.           |
| `linkComponent`                 | `RootWidgetProvider`         | Component used in place of plain `<a>` tags inside widgets.                                              |
| `uiFramework`                   | `RootWidgetProvider`         | `'shadcn'` (default) or `'tecton'`; inherits down the provider cascade.                                  |
| `analytics`                     | `RootWidgetProvider`         | Enables anonymous analytics (default `true`); set `false` to disable capture.                            |
| `unstable_initResponseOverride` | `RootWidgetProvider`         | First-party hosts only. Skips the init fetch and supplies host-authored identity for the subtree.        |
| `disclosuresAcceptance`         | `RootWidgetProvider`         | Accept surface shown when the user owes disclosures. Cascades to nested providers.                       |
| `providerLabels`                | `RootWidgetProvider`         | Overrides the copy a provider renders in place of a widget (error fallback, disclosures gate). Cascades. |
| `fetcher`                       | `WidgetTokenRefreshProvider` | Required.                                                                                                |
| `leadSeconds`                   | `WidgetTokenRefreshProvider` | Default `60`.                                                                                            |
| `onToken`                       | `WidgetTokenRefreshProvider` | Fires once per distinct token.                                                                           |
| `children`                      | Both                         | Rendered inside both providers.                                                                          |

***

## Non-React hosts: `@tesouro/embedded-components-react/core`

The `/core` subpath exports `createWidgetTokenManager` with no React runtime, for web-component shells and vanilla-JS embeds. Subscribe first, then call `start()`. On teardown, call the returned `unsubscribe()` and `stop()`.

```ts
import { createWidgetTokenManager } from '@tesouro/embedded-components-react/core';
import { updateGlobalWidgetConfig } from '@tesouro/embedded-components-react';

const manager = createWidgetTokenManager({ fetcher, leadSeconds: 60 });

const unsubscribe = manager.subscribe(() => {
  const state = manager.getState();
  if (state.status === 'ready') {
    updateGlobalWidgetConfig({ widgetToken: state.widgetToken });
  }
});

manager.start();
```

The manager exposes `start()`, `stop()`, `refresh()`, `getState()` and `subscribe(listener)`. `/core` also exports the `CreateWidgetTokenManagerOptions`, `WidgetTokenFetcher`, `WidgetTokenManager` and `WidgetTokenState` types.

***

## TypeScript

```ts
import type {
  WidgetTokenRefreshProviderProps,
  WidgetTokenFetcher,
  WidgetTokenManager,
  WidgetTokenState,
} from '@tesouro/embedded-components-react';
```

# Widget tokens (/providers/widget-tokens)

Server-side helpers for minting widget tokens used by Tesouro's embedded
components. Mints an [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693)
token-exchange request and encrypts it as a JWE (`A256KW` + `A256GCM`) using
your shared widget secret.

> **Server-only.** This package handles your `clientSecret` and `widgetSecret`,
> so keep it on the server and never bundle it into client code. It runs in any
> server runtime — a Next.js Route Handler, an Express server, or a Lambda
> handler.

## Installation

```bash
yarn add @tesouro/embedded-components-widget-token
# or
npm install @tesouro/embedded-components-widget-token
```

## Usage

### One-shot

```ts
import { createWidgetToken } from '@tesouro/embedded-components-widget-token';

const { widgetToken, exp } = await createWidgetToken({
  clientId: process.env.TESOURO_CLIENT_ID!,
  clientSecret: process.env.TESOURO_CLIENT_SECRET!,
  widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
  userId: 'user_123',
  userEmail: 'user@example.com',
  organizationReference: 'partner-org-123',
});
```

`exp` is the token's expiration time in seconds since epoch — return it to the
client so it can refresh before expiry.

### Pre-configured factory

When the same credentials are reused across many requests, bind them once with
`configureCreateWidgetToken`:

```ts
import { configureCreateWidgetToken } from '@tesouro/embedded-components-widget-token';

const mintWidgetToken = configureCreateWidgetToken({
  clientId: process.env.TESOURO_CLIENT_ID!,
  clientSecret: process.env.TESOURO_CLIENT_SECRET!,
  widgetSecret: process.env.TESOURO_WIDGET_SECRET!,
  organizationReference: 'partner-org-123',
});

const { widgetToken, exp } = await mintWidgetToken({
  userId: 'user_123',
  userEmail: 'user@example.com',
});
```

### In a route handler

Call the factory from an authenticated route on your server and return
`{ widgetToken, exp }` to the browser.
[Getting started](/components/getting-started)
has a complete Next.js route handler and the provider that calls it.

> **Identity must come from the server, not the caller.** Resolve `userId`,
> `userEmail`, and `organizationReference` from your authenticated session,
> never from the request body. `organizationReference` names the business the
> user belongs to, so bind it once in `configureCreateWidgetToken` only when
> every user of that server belongs to the same business. Your route mints a valid
> token for whatever identity it receives, so trusting client-supplied identity
> lets any caller impersonate another user.

## API

### `createWidgetToken(params): Promise<WidgetTokenResult>`

| Param                   | Type     | Required | Description                                                                                                                                                                  |
| ----------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clientId`              | `string` | yes      | Tesouro-issued client identifier. Used as JWE `kid` and as `iss`/`client_id` in the payload.                                                                                 |
| `clientSecret`          | `string` | yes      | Tesouro-issued client secret. Carried as `client_secret` inside the encrypted payload.                                                                                       |
| `widgetSecret`          | `string` | yes      | Shared symmetric key used to encrypt the JWE.                                                                                                                                |
| `userId`                | `string` | yes      | Subject (`sub`) of the inner identity token.                                                                                                                                 |
| `userEmail`             | `string` | yes      | Email claim of the inner identity token.                                                                                                                                     |
| `organizationReference` | `string` | yes      | Partner-resolved organization/business reference. Carried as `organization_reference` in the outer encrypted payload. Resolve this server-side, not from request body input. |
| `expirationInSeconds`   | `number` | no       | Token TTL in seconds. Defaults to `DEFAULT_EXPIRATION_IN_SECONDS` (480 — 8 minutes).                                                                                         |

Returns:

```ts
interface WidgetTokenResult {
  widgetToken: string; // compact-serialized JWE
  exp: number; // expiration in seconds since epoch (UTC)
}
```

Throws if `clientId`, `clientSecret`, `widgetSecret`, or `organizationReference` is missing or empty.

### `configureCreateWidgetToken(creds)`

Binds `clientId`, `clientSecret`, `widgetSecret`, and optional defaults for
`organizationReference` and `expirationInSeconds`, and returns a function that
only takes per-request `userId` / `userEmail` plus optional per-call overrides
for `organizationReference` and `expirationInSeconds`. `organizationReference`
is still required: the returned function throws unless it is bound here or
passed per call.

### `DEFAULT_EXPIRATION_IN_SECONDS`

`480` — the default token lifetime in seconds (8 minutes).

## Token format

`createWidgetToken` produces a JWE with the following protected header:

```json
{
  "alg": "A256KW",
  "enc": "A256GCM",
  "kid": "<clientId>",
  "typ": "JWT",
  "cty": "JWT"
}
```

The encrypted payload is an RFC 8693 token-exchange request whose
`subject_token` is an unsigned identity JWT (`alg: none`) bearing the user's
`sub`, `email`, `iss`, `aud`, `iat`, and `exp` claims. `organizationReference`
is minted at the outer encrypted payload level as `organization_reference`; it
is not added to the inner `subject_token`.

Backends that don't run Node build the same token from the widget token
protocol in Authentication › User token, in the
[Embedded Banking](https://docs.tesouro.com/embedded-banking/guides/authentication/user-token)
or [FinOps](https://docs.tesouro.com/finops/guides/authentication/user-token)
docs.

# Accept Disclosures (/components/accept-disclosures)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Shows the bank partner's required disclosure documents with an agreement checkbox and an Accept button. The document set, titles and order come from your program's backend configuration, so you pass no URLs.

***

## Quick Start

Mount it when widget init reports `status: 'INVITED'`, **or** `disclosuresRequired: 'REQUIRED'` with `disclosuresAccepted: false`. Don't gate on `REQUIRED` alone: an invited user in a `NOT_REQUIRED` org must still accept to activate.

```tsx
import {
  AcceptDisclosuresWidget,
  useWidgetConfig,
} from '@tesouro/embedded-components-react';

function Gate({ children }: { children: React.ReactNode }) {
  const { initResponse } = useWidgetConfig();
  const isInvitee = initResponse?.status === 'INVITED';
  const owesAcceptance =
    initResponse?.disclosuresRequired === 'REQUIRED' &&
    !initResponse.disclosuresAccepted;

  return isInvitee || owesAcceptance ? <AcceptDisclosuresWidget /> : children;
}
```

## Behaviour

* **Accept** activates an invited user and records acceptance in one step. On success the widget awaits `onAccepted`, locks the control so acceptance can't be posted twice, then refreshes widget init so your gate clears.
* If `onAccepted` rejects, the widget shows an inline error and stays retryable.
* If a newer disclosure version is published while the user is reading, Accept is refused and the widget reloads the current documents.
* The widget renders nothing until the documents and the tenant's bank name load. A failed load shows an error with retry.
* An org with no documents still shows Accept, using the `noDocuments*` labels.
* Use a normal `widgetToken`. Never pass an application (APP) / M2M token.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                | Default        | Description                                                                               |
| ---------------- | ----------------------------------- | -------------- | ----------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                            | Inherited      | API base URL for this widget's scoped client.                                             |
| `widgetToken`    | `string \| null`                    | Inherited      | Widget auth token (same provider contract as other widgets). Don't pass an APP/M2M token. |
| `organizationId` | `string \| null`                    | Inherited      | Organization scope, sent as `x-organization-id`.                                          |
| `configClient`   | `(client) => client`                | Inherited      | Post-creation hook for the scoped HTTP client.                                            |
| `linkComponent`  | `LinkComponent`                     | Inherited      | Host link component override.                                                             |
| `uiFramework`    | `UIFramework`                       | Inherited      | UI framework selection for the provider cascade.                                          |
| `errorFallback`  | `ReactNode \| (props) => ReactNode` | Built-in alert | Rendered when the subtree throws.                                                         |
| `onError`        | `(error, info) => void`             | `undefined`    | Error-boundary telemetry hook.                                                            |
| `analytics`      | `boolean`                           | `true`         | Honored only when this is the analytics owner.                                            |

### Widget props

| Prop         | Type                                     | Default        | Description                                                                                                                                                                                           |
| ------------ | ---------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `labels`     | `Partial<AcceptDisclosuresWidgetLabels>` | English labels | Override widget copy. `{documents}` in `agreementTextTemplate` is replaced with the document titles. The `noDocuments*` labels apply when there are no documents. Document titles aren't overridable. |
| `onAccepted` | `() => void \| Promise<void>`            | `undefined`    | Called after the atomic accept succeeds and before init refresh; awaited before the widget locks. Reject to surface an error and keep Accept retryable.                                               |

## TypeScript

```ts
import type {
  AcceptDisclosuresWidgetProps,
  AcceptDisclosuresWidgetLabels,
} from '@tesouro/embedded-components-react';
```

# Accounts Quick View (/components/accounts-quick-view)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

A compact dashboard tile with the organization's total available balance across every bank account, the account count, and a **View accounts** link into your accounts page.

***

## Quick Start

```tsx
import { AccountsQuickViewWidget } from '@tesouro/embedded-components-react';

export function DashboardTiles() {
  return (
    <AccountsQuickViewWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
      onTileClick={() => router.push('/accounts')}
    />
  );
}
```

All widget props are optional. The widget reads no URL or router; you own navigation via `onTileClick`.

## Behaviour

* The total and the count use live balances, from the same read as `BalancesWidget`. The total is in the first account's currency; accounts in any other currency are left out of it rather than converted, and the count line then says so ("3 of 5 accounts, USD only", the `accountCountInCurrency` label). The amount is formatted at the currency's own precision, so 12,000 yen shows as ¥12,000; `BalancesWidget` currently assumes two decimals, so the two disagree for zero- and three-decimal currencies.
* The whole tile is one button when you pass `onTileClick`. Without it the tile is static, with no chevron and no **View accounts** link.
* The widget renders nothing when the token lacks `bank_account:read:org`. Write scopes aren't needed.
* The widget also renders nothing for an org with no accounts. A failed load shows an error with **Retry**.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                                                                                                |
| ---------------- | ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget. When omitted, inherits from the nearest ancestor `WidgetProvider` or `RootWidgetProvider`. |
| `widgetToken`    | `string \| null`             | Inherited | Auth token for this widget. Triggers a `/api/widget-gateway/init` fetch. Pass `null` to explicitly suppress auth. Omit to inherit from the parent.                         |
| `organizationId` | `string \| null`             | Inherited | Scopes API calls to a specific organization. Doesn't trigger a re-fetch on its own. Pass `null` to clear the org scope for this subtree. Omit to inherit from the parent.  |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. Omit to inherit from the parent.                                         |

### Widget props

| Prop          | Type                                     | Default        | Description                                                                                     |
| ------------- | ---------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------- |
| `labels`      | `Partial<AccountsQuickViewWidgetLabels>` | English labels | Override the tile copy (title, link text, account count, loading and error text).               |
| `onTileClick` | `() => void`                             | `undefined`    | When set, the whole tile is clickable and calls this. Wire it to your accounts page.            |
| `className`   | `string`                                 | `undefined`    | Added to the tile's root. The tile is 320px wide by default; a rule on this class overrides it. |
| `style`       | `CSSProperties`                          | `undefined`    | Inline styles on the tile's root.                                                               |

## Examples

### Custom width

The tile is 320px wide by default and never wider than its container. The widget's own styles sit in a cascade layer, so any rule in your stylesheet wins without `!important`:

```css
.accounts-tile {
  display: block;
  width: auto;
}
```

```tsx
<AccountsQuickViewWidget className="accounts-tile" />
// or inline
<AccountsQuickViewWidget style={{ width: '100%' }} />
```

### Custom labels (i18n)

```tsx
<AccountsQuickViewWidget
  labels={{
    title: 'Saldo disponible total',
    viewAccounts: 'Ver cuentas',
    accountCount: (count) => `${count} ${count === 1 ? 'cuenta' : 'cuentas'}`,
  }}
/>
```

See the exported `AccountsQuickViewWidgetLabels` type for every key.

## TypeScript

```ts
import type {
  AccountsQuickViewWidgetProps,
  AccountsQuickViewWidgetLabels,
} from '@tesouro/embedded-components-react';
```

# Balances (/components/balances)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Shows the organization's embedded bank account balances, up to a configurable number of rows, with a total when there are several accounts and an optional **View all accounts** link.

***

## Quick Start

```tsx
import { BalancesWidget } from '@tesouro/embedded-components-react';

export function AccountsSummary() {
  return (
    <BalancesWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
      maxAccounts={5}
      onBalanceRowClick={(accountId) => router.push(`/accounts/${accountId}`)}
      onViewAllAccountsClick={() => router.push('/accounts')}
    />
  );
}
```

All widget props are optional. The widget reads no URL or router; you own navigation via the two callbacks.

## Behaviour

* The total balance appears only when more than one account exists, all accounts have loaded, and the token grants `bank_account:read:org`. Write scopes aren't needed.
* **View all accounts** appears only when you pass `onViewAllAccountsClick` and there are more than `maxAccounts` accounts.
* Rows are clickable only when you pass `onBalanceRowClick`.
* A failed load shows an error with **Retry**. An org with no accounts shows **No balances found**.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                                                                                                |
| ---------------- | ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget. When omitted, inherits from the nearest ancestor `WidgetProvider` or `RootWidgetProvider`. |
| `widgetToken`    | `string \| null`             | Inherited | Auth token for this widget. Triggers a `/api/widget-gateway/init` fetch. Pass `null` to explicitly suppress auth. Omit to inherit from the parent.                         |
| `organizationId` | `string \| null`             | Inherited | Scopes API calls to a specific organization. Doesn't trigger a re-fetch on its own. Pass `null` to clear the org scope for this subtree. Omit to inherit from the parent.  |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. Omit to inherit from the parent.                                         |

### Widget props

| Prop                     | Type                            | Default        | Description                                                                 |
| ------------------------ | ------------------------------- | -------------- | --------------------------------------------------------------------------- |
| `maxAccounts`            | `number`                        | `5`            | Maximum rows rendered in-card; additional accounts use the view-all CTA.    |
| `labels`                 | `Partial<BalancesWidgetLabels>` | English labels | Override shell copy (title, errors, view-all label, total balance tooltip). |
| `onBalanceRowClick`      | `(accountId: string) => void`   | `undefined`    | When set, balance rows are clickable and receive the account id.            |
| `onViewAllAccountsClick` | `() => void`                    | `undefined`    | When set and more than `maxAccounts` exist, shows **View all accounts**.    |

## Examples

### Custom labels (i18n)

```tsx
<BalancesWidget
  labels={{
    widgetTitle: 'Cuentas',
    noBalancesFound: 'No se encontraron saldos',
    loadErrorRetry: 'Reintentar',
  }}
/>
```

See the exported `BalancesWidgetLabels` type for every key.

## TypeScript

```ts
import type {
  BalancesWidgetProps,
  BalancesWidgetLabels,
} from '@tesouro/embedded-components-react';
```

# Bank Account Onboarding (/components/bank-account-onboarding)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Walks an applicant through bank-account onboarding: business details, personal details, optional additional owners, and a result screen. The widget handles saving, validation, submission and waiting for the account to open; you supply auth and two callbacks.

***

## Quick Start

The widget renders inline and fills its container, so mounting it is what shows it. Give the container a real height, typically a dedicated onboarding page or a route you navigate to from your own trigger (for example `OfferWidget`'s primary CTA).

```tsx
import { BankAccountOnboardingWidget } from '@tesouro/embedded-components-react';

export function OnboardingPage() {
  return (
    <div style={{ height: '100vh', minHeight: 600 }}>
      <BankAccountOnboardingWidget
        baseUrl="https://api.example.com"
        widgetToken="wt_live_abc123"
        organizationId="org_xyz"
        onEmbeddedOnboardingCompletedSuccessfully={handleComplete}
        onNavigateToDashboard={goToDashboard}
      />
    </div>
  );
}
```

## Behaviour

* The widget renders nothing until widget init and the organization's application status resolve, and nothing at all when the user is already `ACTIVE` / `INACTIVE`. Each suppression emits an `onboarding.suppressed` analytics event and calls `onSuppressed(reason)` — see Suppression reasons.
* A draft your backend pre-populated resumes as the applicant's own when it holds their work email: the widget picks up its application client secret, so you pass no application ID or secret. Without the email it shows the "Application in progress" card.
* One mounted widget serves one applicant. Remount it (a new React `key`) whenever the applicant changes. Swapping `widgetToken` in place is treated as a token rotation and keeps the form.
* `onEmbeddedOnboardingCompletedSuccessfully` fires only when the account is fully open. An application still under review, declined, or slow to provision shows a "we'll email you" pending screen instead.
* The first step has no Back button and there is no Cancel control, since the widget has nowhere to go back to; Back between completed steps still works.
* **Save and continue later** saves the application and confirms with a toast, leaving the applicant on the same step. **Delete** removes it, confirms with a toast, and starts a fresh application. The toasts render inside the widget, so keep it mounted.
* Disclosure acceptance is recorded once the account opens. If that fails, the success screen still shows and `AcceptDisclosuresWidget` is the retry path.

If the organization already has an application, the widget renders by its status:

| Application                                   | Renders                           |
| --------------------------------------------- | --------------------------------- |
| None, `DELETED` or `CANCELED`                 | The form                          |
| `DRAFT` started by the applicant              | The form, resumed                 |
| `DRAFT` started by someone else               | "Application in progress" card    |
| `SUBMITTED` (either applicant)                | "Under review" card               |
| `DENIED` (either applicant), within 30 days   | "Not approved" card               |
| `DENIED` more than 30 days ago, or `COMPLETE` | Nothing (`onboarding.suppressed`) |

The card replaces the form. Its copy comes from `labels.applicationStatus`. Set `showApplicationStatusRefresh` to add a **Check again** button to the card; the widget does not re-read the status on window focus. If a re-check finds a status the widget has no copy for, the card switches to `labels.applicationStatus.unavailable` and keeps the button, so the user can try again.

### Suppression reasons

Not every reason means an account exists. If you unlock account-scoped navigation from `onSuppressed`, branch on the reason rather than treating them alike.

| `OnboardingSuppressionReason`               | Value                                          | Account provisioned?                                            |
| ------------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------- |
| `UserAlreadyOnboarded`                      | `user_already_onboarded`                       | Yes                                                             |
| `OrganizationApplicationComplete`           | `organization_application_complete`            | Yes                                                             |
| `UserInvitedToOrganization`                 | `user_invited_to_organization`                 | No — pending disclosure acceptance                              |
| `OrganizationApplicationStartedByOther`     | `organization_application_started_by_other`    | No — another member's application is still in flight            |
| `OrganizationApplicationDenialExpired`      | `organization_application_denial_expired`      | No — the application was denied more than 30 days ago           |
| `OrganizationApplicationStatusUnrecognized` | `organization_application_status_unrecognized` | No — the application has a status the widget does not recognize |

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                       |
| ---------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget.                   |
| `widgetToken`    | `string \| null`             | Inherited | Bearer token for widget requests. `null` clears auth at this level.                               |
| `organizationId` | `string \| null`             | Inherited | Organization ID forwarded as the `x-organization-id` header.                                      |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. |

### Widget props

| Prop                                        | Type                                            | Default        | Description                                                                                                                                                                                                                            |
| ------------------------------------------- | ----------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onEmbeddedOnboardingCompletedSuccessfully` | `() => void`                                    | **Required**   | Called when the application is approved and the account is open.                                                                                                                                                                       |
| `onNavigateToDashboard`                     | `() => void`                                    | **Required**   | Called when the user clicks the dashboard CTA on the result screen.                                                                                                                                                                    |
| `initialBusinessDetails`                    | `Partial<HostSupplied<BusinessDetailsValues>>`  | `undefined`    | Prefills step 2 (business details); the user can still edit.                                                                                                                                                                           |
| `initialPersonalDetails`                    | `Partial<HostSupplied<PersonalDetailsValues>>`  | `undefined`    | Prefills step 1 (personal details); the user can still edit.                                                                                                                                                                           |
| `initialAdditionalOwners`                   | `HostSuppliedOwner[]`                           | `[]`           | Prefills the owners list on step 2.                                                                                                                                                                                                    |
| `labels`                                    | `BankAccountOnboardingWidgetLabels`             | English labels | Per-step and shell copy overrides, merged with the English defaults.                                                                                                                                                                   |
| `bankLogoSrc`                               | `string`                                        | `undefined`    | Bank logo URL for the result screen; falls back to the bank name as text.                                                                                                                                                              |
| `disclosureLinks`                           | `DisclosureLinks`                               | `undefined`    | URLs for Terms of Use, Privacy Policy, Electronic Communication and Patriot Act on the Terms step. You resolve these; omit to leave unset.                                                                                             |
| `onSuppressed`                              | `(reason: OnboardingSuppressionReason) => void` | `undefined`    | Called once the widget decides to render nothing, with the reason. Use it to swap in your own content instead of leaving the empty render.                                                                                             |
| `showApplicationStatusRefresh`              | `boolean`                                       | `false`        | Adds a **Check again** button to the application status card that re-reads the organization's application. Never shown over the form or result screens.                                                                                |
| `onResultPhaseChange`                       | `(isInResultPhase: boolean) => void`            | `undefined`    | Called with `true` once the widget starts processing a submitted application, and with `false` if it unmounts after that. While `true` it keeps its processing or result screen up whatever the status read says, so leave it mounted. |

## Examples

### Overriding labels

Pass only the strings you want to change; everything else falls back to English. See the exported `BankAccountOnboardingWidgetLabels` type for every key. `APPLICATION_STATUS_LABELS_EN` holds the English status-card copy, so an override can reuse it.

```tsx
<BankAccountOnboardingWidget
  labels={{
    stepIndicator: {
      personal: 'Personal',
      business: 'Negocio',
      review: 'Revisión',
      terms: 'Términos',
      done: 'Listo',
    },
    businessDetails: { heading: 'Datos de la empresa' },
    result: { successHeadingLine1: 'Tu cuenta está lista' },
  }}
  onEmbeddedOnboardingCompletedSuccessfully={handleComplete}
  onNavigateToDashboard={goToDashboard}
/>
```

## TypeScript

```ts
import type {
  BankAccountOnboardingWidgetProps,
  BankAccountOnboardingWidgetLabels,
  DisclosureLinks,
  OnboardingSuppressionReason,
  ApplicationStatusLabels,
  BusinessDetailsValues,
  BusinessDetailsLabels,
  PersonalDetailsValues,
  PersonalDetailsLabels,
  AdditionalOwner,
  AdditionalOwnersLabels,
  ResultLabels,
} from '@tesouro/embedded-components-react';
```

# Bank Accounts (/components/bank-accounts)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Lists the organization's Tesouro bank accounts, lets the user open an account with team access, and shows each account's details.

***

## Quick Start

```tsx
import { BankAccountsWidget } from '@tesouro/embedded-components-react';

export function BankingPage() {
  return <BankAccountsWidget isBankingTaglineVisible />;
}
```

Under `RefreshingRootWidgetProvider` or `WidgetProvider`, the auth props are inherited.

## Behaviour

* The widget ships no bank artwork or document URLs. Supply `bankLogoSrc`, `depositAgreementUrl`, `bankAddress` and `supportTeamUrl` yourself; each one you omit is left out.
* Without `depositAgreementUrl`, the create-account legal copy drops the clause that asks the user to agree to the deposit agreement. Both wordings are overridable via `labels.createLegalTemplate` and `labels.createLegalTemplateWithoutAgreement`.
* The bank name comes from widget init, not from a prop.
* The widget reads no URL or router. By default it switches between list and details itself; to own navigation, pass `selectedAccountId` and handle `onSelectedAccountIdChange`.
* When the organization or signed-in user changes, the open account is cleared. In controlled mode the widget calls `onSelectedAccountIdChange(null)` instead. A token refresh doesn't clear it.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type             | Default   | Description                            |
| ---------------- | ---------------- | --------- | -------------------------------------- |
| `baseUrl`        | `string`         | Inherited | API base URL for this widget subtree.  |
| `widgetToken`    | `string \| null` | Inherited | Widget token used by `WidgetProvider`. |
| `organizationId` | `string \| null` | Inherited | Organization scope for REST requests.  |

### Widget props

| Prop                        | Type                                       | Default        | Description                                                                                                       |
| --------------------------- | ------------------------------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------- |
| `isBankingTaglineVisible`   | `boolean`                                  | `true`         | Shows the banking tagline at the top of the widget. The page heading above it belongs to your page.               |
| `bankLogoSrc`               | `string`                                   | `undefined`    | Bank logo for the tagline row. Omit and the row shows the bank name alone.                                        |
| `bankLogoAlt`               | `string`                                   | `undefined`    | Alt text when `bankLogoSrc` is set.                                                                               |
| `depositAgreementUrl`       | `string`                                   | `undefined`    | Deposit-agreement PDF linked from the create-account legal copy. Omit and the clause naming it isn't rendered.    |
| `bankAddress`               | `string`                                   | `undefined`    | Bank postal address shown in the account-details domestic wire panel.                                             |
| `supportTeamUrl`            | `string`                                   | `undefined`    | Support URL linked from the account-details domestic wire copy.                                                   |
| `labels`                    | `Partial<BankAccountsWidgetLabels>`        | English labels | Overrides list/create-account UI copy.                                                                            |
| `accountDetailsLabels`      | `Partial<AccountDetailsWidgetLabels>`      | English labels | Overrides the internal account details UI copy.                                                                   |
| `featureLabels`             | `Partial<BankAccountsWidgetFeatureLabels>` | English labels | Overrides feature-level copy (account fallback name, create/edit/copy toasts, and account-details export toasts). |
| `data-testid`               | `string`                                   | `undefined`    | Optional test id forwarded to the widget root.                                                                    |
| `selectedAccountId`         | `string \| null`                           | `undefined`    | Controls the open details account.                                                                                |
| `defaultSelectedAccountId`  | `string \| null`                           | `undefined`    | Initial uncontrolled details account.                                                                             |
| `onSelectedAccountIdChange` | `(id: string \| null) => void`             | `undefined`    | Called when the user opens details or goes back.                                                                  |

## Examples

### Selected account in a URL query param

```tsx
'use client';

import { BankAccountsWidget } from '@tesouro/embedded-components-react';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';

export function AccountsPage() {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();

  return (
    <BankAccountsWidget
      selectedAccountId={searchParams.get('bankAccountId')}
      onSelectedAccountIdChange={(id) => {
        const params = new URLSearchParams(searchParams.toString());
        if (id) params.set('bankAccountId', id);
        else params.delete('bankAccountId');
        const qs = params.toString();
        router.push(qs ? `${pathname}?${qs}` : pathname);
      }}
    />
  );
}
```

## TypeScript

```ts
import type {
  AccountDetailsWidgetLabels,
  BankAccountsWidgetFeatureLabels,
  BankAccountsWidgetLabels,
  BankAccountsWidgetProps,
} from '@tesouro/embedded-components-react';
```

# Banking Compliance Banner (/components/banking-compliance-banner)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Shows "Business Banking provided by" the business customer's bank, plus the FDIC official sign when that bank is FDIC insured. Put it where your app presents banking products, usually the page header.

***

## Quick Start

```tsx
import { BankingComplianceBannerWidget } from '@tesouro/embedded-components-react';

export function BankingHeader() {
  return (
    <BankingComplianceBannerWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
      bankLogoSrc="https://cdn.example.com/bank-logo.svg"
    />
  );
}
```

All widget props are optional. The widget needs no scopes beyond a valid token.

## Behaviour

* The attribution always renders. Without `bankLogoSrc` it names the bank in text, falling back to "our partner bank" when the name isn't known yet.
* The FDIC sign renders only when the bank is confirmed FDIC insured. It stays hidden while that answer loads, when the request fails, and when the bank isn't insured.
* The FDIC sign keeps its regulated navy, or white on a dark theme, whatever your theme's colors are.
* Override `fdicStatement` only to translate it. Its wording is set by regulation.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                                                                                                |
| ---------------- | ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget. When omitted, inherits from the nearest ancestor `WidgetProvider` or `RootWidgetProvider`. |
| `widgetToken`    | `string \| null`             | Inherited | Auth token for this widget. Triggers a `/api/widget-gateway/init` fetch. Pass `null` to explicitly suppress auth. Omit to inherit from the parent.                         |
| `organizationId` | `string \| null`             | Inherited | Scopes API calls to a specific organization. Doesn't trigger a re-fetch on its own. Pass `null` to clear the org scope for this subtree. Omit to inherit from the parent.  |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. Omit to inherit from the parent.                                         |

### Widget props

| Prop          | Type                                     | Default        | Description                                                         |
| ------------- | ---------------------------------------- | -------------- | ------------------------------------------------------------------- |
| `bankLogoSrc` | `string`                                 | `undefined`    | URL of the bank's logo, shown in place of its name.                 |
| `bankLogoAlt` | `string`                                 | Bank name      | Alt text for the logo.                                              |
| `labels`      | `Partial<BankingComplianceBannerLabels>` | English labels | Override the attribution, fallback name and FDIC copy.              |
| `className`   | `string`                                 | `undefined`    | Class names added to the banner's root element, for layout spacing. |

## Examples

### Custom labels (i18n)

```tsx
<BankingComplianceBannerWidget
  labels={{
    providedByPrefix: 'Banca empresarial ofrecida por',
    providerNameFallback: 'nuestro banco asociado',
  }}
/>
```

See the exported `BankingComplianceBannerLabels` type for every key.

## TypeScript

```ts
import type {
  BankingComplianceBannerWidgetProps,
  BankingComplianceBannerLabels,
} from '@tesouro/embedded-components-react';
```

# Bill Pay (/components/bill-pay)

Part of: [FinOps](https://docs.tesouro.com/finops)

A bill pay widget with Tesouro widget auth and theming: a Bills tab for uploading, reviewing and paying bills, and a Vendors tab for the vendors they are paid to.

***

## Quick Start

```tsx
import { BillPayWidget } from '@tesouro/embedded-components-react';

export function BillsPage() {
  return (
    <BillPayWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
    />
  );
}
```

***

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type             | Default   | Description                             |
| ---------------- | ---------------- | --------- | --------------------------------------- |
| `baseUrl`        | `string`         | Inherited | API base URL for widget initialization. |
| `widgetToken`    | `string \| null` | Inherited | Widget auth token.                      |
| `organizationId` | `string \| null` | Inherited | Organization scope for the widget.      |

### Widget props

| Prop                | Type                   | Default     | Description                                                                                                                                                                    |
| ------------------- | ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `initialTab`        | `'bills' \| 'vendors'` | `'bills'`   | Tab to open on initial mount.                                                                                                                                                  |
| `poweredByBankName` | `string`               | `undefined` | Bank shown as "Powered by \{bank}" next to the source account when paying a bill. Omit it to hide the badge.                                                                   |
| `onSettingsClick`   | `() => void`           | `undefined` | Opens your bill pay settings surface from a cog beside the bill list's "Add bill" button. The cog renders only when this is supplied and the user's scopes reach that surface. |

## TypeScript

```ts
import type { BillPayWidgetProps } from '@tesouro/embedded-components-react';
```

# Card Details (/components/card-details)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Details for one credit or debit card: status, form factor, program, masked number, and copyable cardholder, nickname and billing-address rows. It renders panel content rather than its own drawer, so it fits on a page of your own or inside chrome you already have.

***

## Quick Start

```tsx
import { CardDetailsWidget } from '@tesouro/embedded-components-react';

export function CardPage({ cardId }: { cardId: string }) {
  return <CardDetailsWidget cardId={cardId} cardProgram="credit" />;
}
```

`cardId` and `cardProgram` are both required. Under `RefreshingRootWidgetProvider` or `WidgetProvider` the auth props are inherited. If you only need details next to a card list, `CardsWidget` already composes this panel in its own sheet.

## Behaviour

* **Program:** `cardProgram` must be exactly `'credit'` or `'debit'`. Any other value, including `undefined`, fetches nothing and shows the error screen with no retry, plus a console message. If you resolve the program asynchronously, wait for it before mounting.
* **Debit-only features:** reveal, lock, rename, billing address and card controls appear only with `cardProgram="debit"`. A credit panel never shows them, so don't plan a credit layout around them.
* **Reveal:** View card fetches the number, expiry and security code only when pressed, and keeps nothing after Hide, close or a card switch. A refused (403) reveal shows `featureLabels.revealNotPermittedDescription`. It isn't offered on a card pending activation.
* **Lock:** needs the `debit_card:lock:org` scope, otherwise the control is hidden. It isn't offered on a physical card pending activation or a closed card. A locked card can still be revealed.
* **Rename:** needs `debit_card:issue:org`, the same scope that issues cards. It isn't offered on a closed card, and a blank name can't be saved.
* **Billing address and card controls** (allowed categories, spend limit, per-transaction max) show when the card has them and are omitted otherwise. The address omits the country, and limits scoped to one merchant or category aren't shown.
* **Missing nickname or cardholder:** the row shows `labels.fields.emptyValue` (shared by both rows) with no copy button, and the title falls back to `labels.header.untitledCard`.
* **Activation:** Activate Card appears only for a `PHYSICAL` card in `PENDING_ACTIVATION`, when the token holds `credit_card:activate:org` or `debit_card:activate:org` for that program and the viewer is the card's own cardholder. On a credit card the expiry and security code are only checked for format, so don't treat credit activation as proof the cardholder has the card. Setting a PIN isn't part of the flow.
* **Closing:** the widget reads no URL or router; you own navigation through `onClose`. Omit it on a standalone page. Pass it inside your own drawer or dialog, and hide your chrome's close button. On the activation success screen, **Done** calls `onClose` (no Done without it), and the X returns to the details. There is no activation callback, so read the card's status if you need to know.
* **Updates:** after a lock, rename or activation, the card and any list behind it refresh themselves. Nothing to wire.
* **Not found** appears for any 404, which covers both a card that doesn't exist and one the reader can't access.
* Copy does nothing when the host page blocks clipboard access.
* **Nesting:** inside another widget's provider, leave the auth props off so it inherits. Passing `baseUrl` or `widgetToken` adds a redundant init request each time it opens.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type             | Default   | Description                            |
| ---------------- | ---------------- | --------- | -------------------------------------- |
| `baseUrl`        | `string`         | Inherited | API base URL for this widget subtree.  |
| `widgetToken`    | `string \| null` | Inherited | Widget token used by `WidgetProvider`. |
| `organizationId` | `string \| null` | Inherited | Organization scope for REST requests.  |

### Widget props

Nested label groups merge one group at a time, so overriding one string keeps the rest of its group.

| Prop             | Type                                    | Default        | Description                                                                                                                    |
| ---------------- | --------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `cardId`         | `string`                                | **Required**   | Id of the card to show.                                                                                                        |
| `cardProgram`    | `'credit' \| 'debit'`                   | **Required**   | Which issuing program `cardId` belongs to.                                                                                     |
| `onClose`        | `() => void`                            | `undefined`    | Called by the panel's close control and the activation **Done** button. Omit it and neither is rendered.                       |
| `labels`         | `PartialDeep<CardDetailsWidgetLabels>`  | English labels | Overrides the panel's copy. See the exported `CardDetailsWidgetLabels` type for every key.                                     |
| `activateLabels` | `PartialDeep<ActivateCardPanelLabels>`  | English labels | Overrides the Activate Card form's copy. See the exported `ActivateCardPanelLabels` type for every key.                        |
| `featureLabels`  | `PartialDeep<CardDetailsFeatureLabels>` | English labels | Overrides status, form-factor and program wording, toasts, and the refused-reveal sentence. See `CardDetailsFeatureLabels`.    |
| `cardArtSrc`     | `string`                                | `undefined`    | Full plastic art for the card face (the same asset you pass `CreateCardWidget`). Takes priority over `bankLogoSrc`.            |
| `bankLogoSrc`    | `string`                                | `undefined`    | Bank logo for the card face; ignored when `cardArtSrc` is set. The widget ships no bank artwork, so omit it and no logo shows. |
| `bankLogoAlt`    | `string`                                | `undefined`    | Alt text for `bankLogoSrc`. Falls back to `labels.cardFace.bankLogoAlt`.                                                       |

## Examples

### Inside your own drawer

```tsx
<Drawer open={Boolean(cardId)} onOpenChange={setOpen}>
  <DrawerContent>
    <CardDetailsWidget
      cardId={cardId}
      cardProgram="debit"
      onClose={() => setOpen(false)}
    />
  </DrawerContent>
</Drawer>
```

The widget imposes no width; the surface around it owns that.

## TypeScript

```ts
import type {
  ActivateCardPanelLabels,
  CardDetailsFeatureLabels,
  CardDetailsWidgetLabels,
  CardDetailsWidgetProps,
} from '@tesouro/embedded-components-react';
```

# Counterparts (/components/counterparts)

Part of: [FinOps](https://docs.tesouro.com/finops)

Manages an organization's customers or vendors: a searchable list with money columns, a details sheet, and create, edit and delete flows, including addresses and a vendor's ACH payment method.

***

## Quick Start

```tsx
import { CounterpartsWidget } from '@tesouro/embedded-components-react';

export function CustomersPage() {
  return (
    <CounterpartsWidget
      counterpartType="customer"
      baseUrl="https://api.tesouro.com"
      widgetToken="wt_..."
      organizationId="org_..."
    />
  );
}
```

Pass `counterpartType="vendor"` for vendors. Data loads once `widgetToken` and `organizationId` resolve.

## Behaviour

* Permissions come from the widget token's scopes; there are no permission props. Without `counterpart:read`, `counterpart:read:org` or `counterpart:read:own` the widget shows an access-restricted state and loads no data.
* Create, edit (including address and payment method) and delete actions appear only with `counterpart:create`, `counterpart:update` or `counterpart:delete` respectively, or `counterpart:write:org`. The `:own` variants don't enable them.
* Customers show **Billed amount**, **Received amount** and **Amount due**; vendors show **Billed amount**, **Total paid to date** and **Balance due**.
* The list filters by search and by type (individuals or companies). Applied filters show as removable chips with a "Clear all" action.
* The list and open details refresh after every change; you don't need to refetch.
* Addresses are US only: country is fixed to the United States with a US state picker.
* Contact and VAT details can't be managed in this widget.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                | Default        | Description                                                       |
| ---------------- | ----------------------------------- | -------------- | ----------------------------------------------------------------- |
| `baseUrl`        | `string`                            | Inherited      | API base URL for this widget's scoped client.                     |
| `widgetToken`    | `string \| null`                    | Inherited      | Auth token. Queries wait until this and `organizationId` resolve. |
| `organizationId` | `string \| null`                    | Inherited      | Organization scope sent as `x-organization-id`.                   |
| `configClient`   | `(client) => client`                | Inherited      | Post-creation hook for the scoped HTTP client.                    |
| `linkComponent`  | `LinkComponent`                     | Inherited      | Host link component override.                                     |
| `uiFramework`    | `'shadcn' \| 'tecton' \| null`      | Inherited      | Presentation framework selection carried by the provider cascade. |
| `errorFallback`  | `ReactNode \| (props) => ReactNode` | Built-in alert | Rendered when the widget subtree throws.                          |
| `onError`        | `(error, info) => void`             | `undefined`    | Error-boundary telemetry hook.                                    |
| `analytics`      | `boolean`                           | `true`         | Honored only when this widget provider is the analytics owner.    |

### Widget props

Each label prop is merged over the English defaults, so pass only the strings you want to change.

See the exported label types listed under TypeScript for every key.

| Prop                 | Type                                             | Default             | Description                                                                                                                                     |
| -------------------- | ------------------------------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `counterpartType`    | `'customer' \| 'vendor'`                         | **Required**        | Selects the customer or vendor surface.                                                                                                         |
| `pageSizeOptions`    | `number[]`                                       | `[10, 25, 50, 100]` | Page-size choices shown by the table. The list opens at the first one.                                                                          |
| `onViewAllDocuments` | `(counterpart: CounterpartActionTarget) => void` | `undefined`         | Shows a "View all" action by the details sheet's recent bills/invoices and passes the counterpart's `id` and `name`. Hidden when omitted.       |
| `onCreateDocument`   | `(counterpart: CounterpartActionTarget) => void` | `undefined`         | Runs the details sheet's "Send a payment" (vendor) or "Issue an invoice" (customer) button. Hidden when omitted; you check the scopes it needs. |
| `screenLabels`       | `Partial<CounterpartsScreenLabels>`              | English labels      | Table, search/filter, empty, error and access-restricted copy.                                                                                  |
| `formLabels`         | `PartialDeep<CounterpartFormSheetLabels>`        | English labels      | Create/edit form copy.                                                                                                                          |
| `detailsLabels`      | `Partial<CounterpartDetailsSheetLabels>`         | English labels      | Details sheet copy.                                                                                                                             |
| `bankAccountLabels`  | `PartialDeep<BankAccountFormSheetLabels>`        | English labels      | Vendor payment-method form copy.                                                                                                                |
| `addressLabels`      | `PartialDeep<AddressFormSheetLabels>`            | English labels      | Address form copy.                                                                                                                              |
| `deleteLabels`       | `Partial<ConfirmDeleteDialogLabels>`             | English labels      | Delete-dialog copy.                                                                                                                             |
| `messageLabels`      | `Partial<CounterpartMessageLabels>`              | English labels      | Validation, API failure and delete-prompt messages.                                                                                             |

## Examples

### Link the details sheet to your documents

```tsx
<CounterpartsWidget
  counterpartType="vendor"
  onViewAllDocuments={({ id }) => router.push(`/bills?vendor=${id}`)}
  onCreateDocument={({ id }) => router.push(`/bills/new?vendor=${id}`)}
/>
```

### Custom labels

```tsx
<CounterpartsWidget
  counterpartType="customer"
  screenLabels={{ createButton: 'Add client' }}
  detailsLabels={{ customerSubtitleLabel: 'Client' }}
  messageLabels={{ genericError: 'We could not save this client.' }}
/>
```

## TypeScript

```ts
import type {
  CounterpartsWidgetProps,
  CounterpartMode,
  CounterpartActionTarget,
  CounterpartsScreenLabels,
  CounterpartFormSheetLabels,
  CounterpartDetailsSheetLabels,
  BankAccountFormSheetLabels,
  AddressFormSheetLabels,
  ConfirmDeleteDialogLabels,
  CounterpartMessageLabels,
} from '@tesouro/embedded-components-react';
```

# Credit Cards (/components/credit-cards)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Lists an organization's **credit** cards one page at a time, with a "Show my cards" toggle and per-row Activate. Selecting a row opens the card's details in a side sheet. There is no built-in credit issuance flow, so Create card appears only when you supply `onCreateCard`.

***

## Quick Start

```tsx
import { CreditCardsWidget } from '@tesouro/embedded-components-react';

export function CreditCardsPage() {
  return <CreditCardsWidget />;
}
```

Auth props are inherited from a parent `RefreshingRootWidgetProvider` or `WidgetProvider`. For debit cards, use [`DebitCardsWidget`](/components/debit-cards), which also has the built-in create sheet.

## Behaviour

* **Create card** shows only when the token holds `credit_card:issue:org` and you pass `onCreateCard`, such as `onCreateCard={() => setCreateOpen(true)}`.
* **Activate** on a row shows only for cards awaiting activation, and only with `credit_card:activate:org` and `onActivateCard`. Without the callback, the cardholder can still activate from the details panel (see [`CardDetailsWidget`](/components/card-details)).
* On credit, the activation form's expiry and security code are checked in the browser only, so a well-formed guess activates the card. Don't treat activation as proof the cardholder holds the card.
* A token without `credit_card:read:org` shows the list's empty state, not an error.
* The list renders no heading of its own; put yours in `headerStart`. If both card lists share a page, give each its own heading.
* The widget reads no URL or router. In uncontrolled mode the open card is cleared when the organization or user changes; in controlled mode the widget calls `onSelectedCardIdChange(null)` instead. Paging doesn't close the open card.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type             | Default   | Description                            |
| ---------------- | ---------------- | --------- | -------------------------------------- |
| `baseUrl`        | `string`         | Inherited | API base URL for this widget subtree.  |
| `widgetToken`    | `string \| null` | Inherited | Widget token used by `WidgetProvider`. |
| `organizationId` | `string \| null` | Inherited | Organization scope for REST requests.  |

### Widget props

| Prop                        | Type                                    | Default        | Description                                                                                                               |
| --------------------------- | --------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `pagination`                | `{ paginationToken?, pageSize? }`       | `undefined`    | Controlled cursor and page size. Supply with `onPaginationChange`.                                                        |
| `defaultPagination`         | `{ paginationToken?, pageSize? }`       | First page, 10 | Initial cursor and page size when uncontrolled. Ignored when `pagination` is supplied.                                    |
| `onPaginationChange`        | `(pagination: CardsPagination) => void` | `undefined`    | Called on every page or page-size change. Persist the whole object, not the token alone.                                  |
| `labels`                    | `PartialDeep<CardsLabels>`              | English labels | Overrides the list screen's copy, including the details sheet's screen-reader name under `detailsSheet`.                  |
| `featureLabels`             | `PartialDeep<CardsFeatureLabels>`       | English labels | Overrides status and form-factor vocabulary, the empty-cell placeholder and name fallbacks.                               |
| `selectedCardId`            | `string \| null`                        | `undefined`    | Controlled selection of the card whose details panel is open. `null` closes it; omit for uncontrolled.                    |
| `defaultSelectedCardId`     | `string \| null`                        | `undefined`    | Initial selection when uncontrolled. Ignored when `selectedCardId` is supplied.                                           |
| `onSelectedCardIdChange`    | `(cardId: string \| null) => void`      | `undefined`    | Fires whenever the open card changes, including on close (`null`). Supplying it does **not** change what renders.         |
| `cardDetailsLabels`         | `PartialDeep<CardDetailsWidgetLabels>`  | English labels | Label overrides forwarded into the details panel.                                                                         |
| `cardDetailsFeatureLabels`  | `PartialDeep<CardDetailsFeatureLabels>` | English labels | Overrides the details panel's status, form-factor and program copy and its copy-success toast.                            |
| `cardDetailsActivateLabels` | `PartialDeep<ActivateCardPanelLabels>`  | English labels | Overrides the copy of the Activate Card form the details panel opens.                                                     |
| `bankLogoSrc`               | `string`                                | `undefined`    | Bank logo for the details panel's card face. Omit it and the face renders no logo.                                        |
| `bankLogoAlt`               | `string`                                | `undefined`    | Alt text for `bankLogoSrc`.                                                                                               |
| `onCreateCard`              | `() => void`                            | `undefined`    | Your own Create card handler. Required for the button to render.                                                          |
| `headerStart`               | `ReactNode`                             | `undefined`    | Your markup for the start of the header row, beside Create card. Put your heading here; you choose the element and level. |
| `onActivateCard`            | `(cardId: string) => void`              | `undefined`    | Called when a row's Activate button is clicked. Omit it and the button isn't rendered.                                    |

## Examples

### Controlled with a URL query param

Bind the selection to your router. A card deep-linked this way has its panel open on first paint.

```tsx
<CreditCardsWidget
  selectedCardId={searchParams.get('cardId')}
  onSelectedCardIdChange={(cardId) =>
    router.push(cardId ? `${pathname}?cardId=${cardId}` : pathname)
  }
/>
```

### Your own details route instead of the built-in panel

Pin the selection to `null` and navigate from the callback. Row clicks are still reported, and the panel never opens.

```tsx
<CreditCardsWidget
  selectedCardId={null}
  onSelectedCardIdChange={(cardId) => {
    if (cardId) router.push(`/credit-cards/${cardId}`);
  }}
/>
```

## TypeScript

```ts
import type {
  ActivateCardPanelLabels,
  CardDetailsFeatureLabels,
  CardDetailsWidgetLabels,
  CardsFeatureLabels,
  CardsLabels,
  CardsPagination,
  CreditCardsWidgetProps,
} from '@tesouro/embedded-components-react';
```

`CardsLabels`, `CardsFeatureLabels` and `CardsPagination` are shared with `DebitCardsWidget`.

# Customers (/components/customers)

Part of: [FinOps](https://docs.tesouro.com/finops)

Manages an organization's customers: a searchable list with money columns, a details sheet, and create, edit and delete flows.

***

## Quick Start

```tsx
import { CustomersWidget } from '@tesouro/embedded-components-react';

export function CustomersPage() {
  return (
    <CustomersWidget
      baseUrl="https://api.tesouro.com"
      widgetToken="wt_..."
      organizationId="org_..."
    />
  );
}
```

It is the customers-only view of [`CounterpartsWidget`](/components/counterparts) and shares its label types.

## Behaviour

* Create, edit and delete actions are gated by the user's widget-init scopes.
* Queries wait until `widgetToken` and `organizationId` resolve.
* Addresses are US-only: country is fixed to the United States with a US state picker.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                | Default        | Description                                                       |
| ---------------- | ----------------------------------- | -------------- | ----------------------------------------------------------------- |
| `baseUrl`        | `string`                            | Inherited      | API base URL for this widget's scoped client.                     |
| `widgetToken`    | `string \| null`                    | Inherited      | Auth token. Queries wait until this and `organizationId` resolve. |
| `organizationId` | `string \| null`                    | Inherited      | Organization scope sent as `x-organization-id`.                   |
| `configClient`   | `(client) => client`                | Inherited      | Post-creation hook for the scoped HTTP client.                    |
| `linkComponent`  | `LinkComponent`                     | Inherited      | Host link component override.                                     |
| `uiFramework`    | `'shadcn' \| 'tecton' \| null`      | Inherited      | Presentation framework selection carried by the provider cascade. |
| `errorFallback`  | `ReactNode \| (props) => ReactNode` | Built-in alert | Rendered when the widget subtree throws.                          |
| `onError`        | `(error, info) => void`             | `undefined`    | Error-boundary telemetry hook.                                    |
| `analytics`      | `boolean`                           | `true`         | Honored only when this widget provider is the analytics owner.    |

### Widget props

Every copy prop is merged over built-in English defaults.

| Prop                 | Type                                          | Default             | Description                                                                                                                  |
| -------------------- | --------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `pageSizeOptions`    | `number[]`                                    | `[10, 25, 50, 100]` | Page-size choices shown by the table. The list opens at the first one.                                                       |
| `onViewAllDocuments` | `(customer: CounterpartActionTarget) => void` | `undefined`         | Shows a "View all" action on the details sheet's recent invoices and passes the customer's `id` and `name`; omit to hide it. |
| `onCreateDocument`   | `(customer: CounterpartActionTarget) => void` | `undefined`         | Handles "Issue an invoice" with the customer's `id` and `name`; omit to hide the button. You check invoice scopes.           |
| `screenLabels`       | `Partial<CounterpartsScreenLabels>`           | English labels      | Table, search, filter, empty and error copy.                                                                                 |
| `formLabels`         | `Partial<CounterpartFormSheetLabels>`         | English labels      | Create/edit customer form copy.                                                                                              |
| `detailsLabels`      | `Partial<CounterpartDetailsSheetLabels>`      | English labels      | Details sheet copy.                                                                                                          |
| `addressLabels`      | `PartialDeep<AddressFormSheetLabels>`         | English labels      | Address form copy.                                                                                                           |
| `deleteLabels`       | `Partial<ConfirmDeleteDialogLabels>`          | English labels      | Delete-dialog copy.                                                                                                          |
| `messageLabels`      | `Partial<CounterpartMessageLabels>`           | English labels      | Validation, API failure and delete-prompt copy.                                                                              |

## Examples

### Linking to your invoice pages

```tsx
<CustomersWidget
  onViewAllDocuments={({ id }) => navigate(`/invoices?customer=${id}`)}
  onCreateDocument={({ id }) => navigate(`/invoices/new?customer=${id}`)}
/>
```

## TypeScript

```ts
import type {
  CounterpartActionTarget,
  CustomersWidgetProps,
} from '@tesouro/embedded-components-react';
```

`CounterpartActionTarget` is `{ id: string; name: string }`. The label types are the same shapes `CounterpartsWidget` uses; see [`CounterpartsWidget.md`](/components/counterparts#typescript) for their fields.

# Debit Cards (/components/debit-cards)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Lists an organization's **debit** cards one page at a time, with a "Show my cards" toggle, Create card and per-row Activate. Selecting a row opens the card's details in a side sheet, and Create card opens the built-in debit issuance flow in the same sheet.

***

## Quick Start

```tsx
import { DebitCardsWidget } from '@tesouro/embedded-components-react';

export function DebitCardsPage() {
  return <DebitCardsWidget />;
}
```

Auth props are inherited from a parent `RefreshingRootWidgetProvider` or `WidgetProvider`. For credit cards, use [`CreditCardsWidget`](/components/credit-cards).

## Behaviour

* **Create card** shows only when the token holds `debit_card:issue:org` and either you pass `onCreateCard` or the token also holds `user:read:org` and `bank_account:read:org` (which the built-in sheet needs). Passing `onCreateCard` replaces the built-in sheet.
* After a successful create, **View card** opens the new card's details. It is omitted when the token lacks `debit_card:read:org`.
* **Activate** on a row shows only for cards awaiting activation, and only with `debit_card:activate:org` and `onActivateCard`. Without the callback, the cardholder can still activate from the details panel (see [`CardDetailsWidget`](/components/card-details)).
* A token without `debit_card:read:org` shows the list's empty state, not an error.
* The list renders no heading of its own; put yours in `headerStart`. If both card lists share a page, give each its own heading.
* The widget reads no URL or router. In uncontrolled mode the open card is cleared when the organization or user changes; in controlled mode the widget calls `onSelectedCardIdChange(null)` instead. Paging doesn't close the open card.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type             | Default   | Description                            |
| ---------------- | ---------------- | --------- | -------------------------------------- |
| `baseUrl`        | `string`         | Inherited | API base URL for this widget subtree.  |
| `widgetToken`    | `string \| null` | Inherited | Widget token used by `WidgetProvider`. |
| `organizationId` | `string \| null` | Inherited | Organization scope for REST requests.  |

### Widget props

| Prop                        | Type                                    | Default        | Description                                                                                                               |
| --------------------------- | --------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `pagination`                | `{ paginationToken?, pageSize? }`       | `undefined`    | Controlled cursor and page size. Supply with `onPaginationChange`.                                                        |
| `defaultPagination`         | `{ paginationToken?, pageSize? }`       | First page, 10 | Initial cursor and page size when uncontrolled. Ignored when `pagination` is supplied.                                    |
| `onPaginationChange`        | `(pagination: CardsPagination) => void` | `undefined`    | Called on every page or page-size change. Persist the whole object, not the token alone.                                  |
| `labels`                    | `PartialDeep<CardsLabels>`              | English labels | Overrides the list screen's copy, including the sheets' screen-reader names under `detailsSheet` and `createSheet`.       |
| `featureLabels`             | `PartialDeep<CardsFeatureLabels>`       | English labels | Overrides status and form-factor vocabulary, the empty-cell placeholder and name fallbacks.                               |
| `selectedCardId`            | `string \| null`                        | `undefined`    | Controlled selection of the card whose details panel is open. `null` closes it; omit for uncontrolled.                    |
| `defaultSelectedCardId`     | `string \| null`                        | `undefined`    | Initial selection when uncontrolled. Ignored when `selectedCardId` is supplied.                                           |
| `onSelectedCardIdChange`    | `(cardId: string \| null) => void`      | `undefined`    | Fires whenever the open card changes, including on close (`null`). Supplying it does **not** change what renders.         |
| `cardDetailsLabels`         | `PartialDeep<CardDetailsWidgetLabels>`  | English labels | Label overrides forwarded into the details panel.                                                                         |
| `cardDetailsFeatureLabels`  | `PartialDeep<CardDetailsFeatureLabels>` | English labels | Overrides the details panel's status, form-factor and program copy and its copy-success toast.                            |
| `cardDetailsActivateLabels` | `PartialDeep<ActivateCardPanelLabels>`  | English labels | Overrides the copy of the Activate Card form the details panel opens.                                                     |
| `bankLogoSrc`               | `string`                                | `undefined`    | Bank logo for the details panel's card face, ignored when `cardArtSrc` is set. Omit both and the face renders no logo.    |
| `bankLogoAlt`               | `string`                                | `undefined`    | Alt text for `bankLogoSrc`.                                                                                               |
| `cardArtSrc`                | `string`                                | `undefined`    | Card art for the create sheet and the details card face. Omitted, the sheet uses a neutral plastic.                       |
| `createCardLabels`          | `PartialDeep<CreateCardWidgetLabels>`   | English labels | Label overrides forwarded into the built-in create sheet.                                                                 |
| `createCardFeatureLabels`   | toast / untitled-funding overrides      | English labels | Overrides the create sheet's toasts, untitled funding-account fallback and pending-activation success copy.               |
| `onCreateCard`              | `() => void`                            | `undefined`    | Your own Create card handler. When supplied, the built-in sheet doesn't open.                                             |
| `headerStart`               | `ReactNode`                             | `undefined`    | Your markup for the start of the header row, beside Create card. Put your heading here; you choose the element and level. |
| `onActivateCard`            | `(cardId: string) => void`              | `undefined`    | Called when a row's Activate button is clicked. Omit it and the button isn't rendered.                                    |

## Examples

### Controlled with a URL query param

Bind the selection to your router. A card deep-linked this way has its panel open on first paint.

```tsx
<DebitCardsWidget
  selectedCardId={searchParams.get('cardId')}
  onSelectedCardIdChange={(cardId) =>
    router.push(cardId ? `${pathname}?cardId=${cardId}` : pathname)
  }
/>
```

### Your own details route instead of the built-in panel

Pin the selection to `null` and navigate from the callback. Row clicks are still reported, and the panel never opens.

```tsx
<DebitCardsWidget
  selectedCardId={null}
  onSelectedCardIdChange={(cardId) => {
    if (cardId) router.push(`/debit-cards/${cardId}`);
  }}
/>
```

## TypeScript

```ts
import type {
  ActivateCardPanelLabels,
  CardDetailsFeatureLabels,
  CardDetailsWidgetLabels,
  CardsFeatureLabels,
  CardsLabels,
  CardsPagination,
  DebitCardsWidgetProps,
} from '@tesouro/embedded-components-react';
```

`CardsLabels`, `CardsFeatureLabels` and `CardsPagination` are shared with `CreditCardsWidget`.

# Expense Management (/components/expense-management)

Part of: [FinOps](https://docs.tesouro.com/finops)

A complete expenses surface: My and Team transactions tabs with Action Items, a searchable transactions table, receipt upload, and a full-screen transaction review dialog.

***

## Quick Start

```tsx
import { ExpenseManagementWidget } from '@tesouro/embedded-components-react';

export function ExpensesPage() {
  return (
    <ExpenseManagementWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
      bankDisplayName="Example Bank"
    />
  );
}
```

The widget renders no page heading; your page supplies it.

## Behaviour

* The Team tab appears when the token's scopes include org-wide expense read access, or when the signed-in user manages at least one direct report (their Team rows are scoped to those reports). When Team is available the widget opens on it.
* The settings entry point renders only when you pass `onSettingsClick` and the user's scopes allow it. The widget never navigates itself.
* Receipt upload uses the widget's own upload and mailbox address by default. If you supply `receiptUpload.onFileUpload`, the built-in receipt-matching tracker doesn't run.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                       |
| ---------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget.                   |
| `widgetToken`    | `string \| null`             | Inherited | Bearer token for widget requests. `null` clears auth at this level.                               |
| `organizationId` | `string \| null`             | Inherited | Organization ID forwarded as the `x-organization-id` header.                                      |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. |

### Widget props

| Prop                              | Type                                           | Default     | Description                                                                                                                      |
| --------------------------------- | ---------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `bankDisplayName`                 | `string`                                       | `undefined` | Bank name shown in the attribution header.                                                                                       |
| `bankLogoSrc`                     | `string`                                       | `undefined` | URL for the bank logo in the attribution header.                                                                                 |
| `bankLogoAlt`                     | `string`                                       | `undefined` | Alt text for the bank logo.                                                                                                      |
| `isBankingTaglineVisible`         | `boolean`                                      | `undefined` | Shows the "Business Banking provided by" tagline beside the header.                                                              |
| `onSettingsClick`                 | `() => void`                                   | `undefined` | Renders a scope-gated settings entry point in the header; navigate from this callback.                                           |
| `receiptUpload`                   | `object`                                       | `undefined` | Overrides the receipt-upload control's props, such as `onFileUpload` / `onAwaitMatching`; see Receipt upload. |
| `screenLabels`                    | `Partial<ExpensesScreenLabels>`                | English     | Tab labels and other screen shell copy.                                                                                          |
| `headerLabels`                    | `Partial<ExpensesHeaderLabels>`                | English     | Bank-attribution header copy.                                                                                                    |
| `transactionsTableLabels`         | `Partial<ExpensesTransactionsTableLabels>`     | English     | Transactions table column headers and empty/loading copy.                                                                        |
| `filterBarLabels`                 | `Partial<ExpensesTransactionsFilterBarLabels>` | English     | Search/filter bar copy.                                                                                                          |
| `actionItemsLabels`               | `Partial<ActionItemsListLabels>`               | English     | Action Items list copy.                                                                                                          |
| `statusBadgeLabels`               | `Partial<ExpenseStatusBadgeLabels>`            | English     | Status pill copy, shared between the table and the review dialog.                                                                |
| `transactionDetailsLabels`        | `Partial<TransactionDetailsLabels>`            | English     | Review dialog copy.                                                                                                              |
| `transactionDetailsCardLabels`    | `Partial<AccordionItemCardLabels>`             | English     | Copy for the accordion cards inside the review dialog.                                                                           |
| `transactionDetailsActionsLabels` | `Partial<TransactionDetailsActionsLabels>`     | English     | Copy for the review dialog's action buttons.                                                                                     |
| `fileViewerLabels`                | `Partial<FileViewerLabels>`                    | English     | Copy for the receipt/attachment file viewer inside the review dialog.                                                            |

## Examples

### Bank attribution, settings entry point and custom upload

```tsx
<ExpenseManagementWidget
  bankDisplayName="Example Bank"
  bankLogoSrc="/assets/bank-logo.svg"
  isBankingTaglineVisible
  onSettingsClick={() => router.push('/settings/expenses')}
  receiptUpload={{
    onFileUpload: (file) => myUploadHandler(file),
  }}
/>
```

### Receipt upload

The "Upload receipts" button in the header opens a popover with a drag-and-drop file picker and a copyable forwarding email address. `receiptUpload` overrides its props; every field is optional and falls back to the widget's own upload, matching tracker and mailbox address.

| Field             | Type                                                      | Description                                                                                                          |
| ----------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `emailAddress`    | `string`                                                  | Forwarding email address shown with a copy button.                                                                   |
| `onFileUpload`    | `(file: File) => unknown \| Promise<unknown>`             | Called once per selected file and awaited; a rejection counts as a failed upload.                                    |
| `onAwaitMatching` | `(uploadedCount: number) => Promise<ReceiptMatchSummary>` | Awaited after every file in a batch uploads. Resolve once matching finishes.                                         |
| `isUploading`     | `boolean`                                                 | Host loading flag. While `true` (or while a batch runs) the popover shows a processing state and blocks new uploads. |

* `onFileUpload` is called once per file, up to 10 files per batch. The widget shows a loading toast, then a success, partial or failure toast based on how many calls resolved.
* With `onAwaitMatching`, a fully successful batch keeps its toast open through receipt matching, then reports all matched, a partial count, or "still processing". If it rejects, the toast falls back to the plain upload success message.
* `ReceiptMatchSummary` is `{ total, matched, unmatched, unreadable, stillProcessing }`, each a `number` count of the batch's receipts. `stillProcessing` counts receipts that never settled in time; it is not a failure.

## TypeScript

```ts
import type {
  ExpenseManagementWidgetProps,
  TransactionDetailsLabels,
  TransactionDetailsActionsLabels,
  AccordionItemCardLabels,
  FileViewerLabels,
} from '@tesouro/embedded-components-react';
```

Auth fields are inherited from `WidgetProviderProps`; see [`WidgetProvider`](/providers/widget-provider) for cascade rules.

# Insights (/components/insights)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Shows onboarding-style insights derived from the organization's embedded and external bank accounts, in **Active** and **Dismissed** tabs. Users can dismiss insights, and the widget remembers dismissals per organization.

***

## Quick Start

```tsx
import { useState } from 'react';
import { InsightsWidget } from '@tesouro/embedded-components-react';

export function DashboardInsights() {
  const [routingEnabled, setRoutingEnabled] = useState(false);

  return (
    <InsightsWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
      routingEnabled={routingEnabled}
      onRoutingToggleChange={setRoutingEnabled}
      onLinkExternalAccountClick={() => openLinkExternalAccountModal()}
    />
  );
}
```

All widget props are optional. Pass the callbacks to wire the routing switch and the **Link account** CTA into your app.

## Behaviour

* The payment-routing onboarding insight appears only when the widget token grants `user:write:org`. There is no visibility prop.
* Dismissals are saved per organization and don't carry over to other orgs. Without an organization id, dismiss has no effect.
* Dismiss is optimistic. If saving fails, the insight returns to the **Active** tab.
* If every account read fails, the widget shows an error with **Retry**. If only one of the two reads fails, it silently shows the insights it could build and offers no retry.
* Insights that need an account list the token can't read aren't shown.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                                                                                                |
| ---------------- | ---------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget. When omitted, inherits from the nearest ancestor `WidgetProvider` or `RootWidgetProvider`. |
| `widgetToken`    | `string \| null`             | Inherited | Auth token for this widget. Triggers a `/api/widget-gateway/init` fetch. Pass `null` to explicitly suppress auth. Omit to inherit from the parent.                         |
| `organizationId` | `string \| null`             | Inherited | Scopes API calls and dismiss persistence to a specific organization. Pass `null` to clear org scope. Omit to inherit from the parent.                                      |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. Omit to inherit from the parent.                                         |

### Widget props

| Prop                         | Type                             | Default        | Description                                                                                         |
| ---------------------------- | -------------------------------- | -------------- | --------------------------------------------------------------------------------------------------- |
| `routingEnabled`             | `boolean`                        | `false`        | Controlled switch state for the routing onboarding insight.                                         |
| `onRoutingToggleChange`      | `(enabled: boolean) => void`     | `undefined`    | When set, controls the routing switch from the host; otherwise the widget keeps local toggle state. |
| `onLinkExternalAccountClick` | `() => void`                     | `undefined`    | When set, the connect-external-account insight shows a **Link account** CTA.                        |
| `labels`                     | `Partial<InsightsWidgetLabels>`  | English labels | Shell copy (tabs, empty, error).                                                                    |
| `singleInsightLabels`        | `Partial<SingleInsightLabels>`   | English labels | Per-row UI strings, such as the dismiss button's `aria-label`.                                      |
| `featureLabels`              | `Partial<InsightsFeatureLabels>` | English labels | Generated insight body copy and action labels.                                                      |

## Examples

### Custom labels (i18n)

```tsx
<InsightsWidget
  labels={{ activeTab: 'Activas', dismissedTab: 'Descartadas' }}
  featureLabels={{
    createAccountText: 'Cree una cuenta para comenzar.',
    verifyExternalAccountText: (nickname) => `Verifique ${nickname}.`,
  }}
/>
```

See the exported `InsightsWidgetLabels`, `SingleInsightLabels` and `InsightsFeatureLabels` types for every key.

## TypeScript

```ts
import type {
  InsightsWidgetProps,
  InsightsWidgetLabels,
  SingleInsightLabels,
  InsightsFeatureLabels,
} from '@tesouro/embedded-components-react';
```

# Invoicing (/components/invoicing)

Part of: [FinOps](https://docs.tesouro.com/finops)

Lists, creates, edits and acts on an organization's invoices, including sending by email and recording payments.

***

## Quick Start

```tsx
import { ReceivablesWidget } from '@tesouro/embedded-components-react';

export function ReceivablesPage({ widgetToken }: { widgetToken: string }) {
  return (
    <ReceivablesWidget
      baseUrl="https://api.tesouro.com"
      widgetToken={widgetToken}
      organizationId="org_..."
    />
  );
}
```

The widget loads the first page once auth and organization scope resolve, and opens its own sheets and dialogs for details, create/edit, send and payment.

## Behaviour

* This replaces the earlier `InvoicingWidget`, which took `pageTitleComponent` and `finopsThemeColors`. Neither exists now: pass your page title as `headerStart`.
* The widget handles invoices only: there are no quote or credit-note tabs, and create always makes an invoice.
* After a create succeeds, the widget calls `onReceivableCreated`. A duplicate does not call it, as in the legacy widget. A duplicate, draft or recurring series opens its details. Issuing an invoice doesn't: a follow-up modal offers to email it when the customer has an email address. After any create, the modal also offers to save typed items that aren't in the catalog as products.
* The list matches the Monite one: due date, status, number, customer, issue date and amount, with "Pending" for a number or date not yet set. It opens sorted by due date, newest first, and clearing a sort returns to that order. With no status picked, it hides canceled, recurring and failed invoices.
* Changing sort, page size, any filter or search returns to the first page.
* A filter bar offers search, plus a Filters menu for due date, created on and issue date ranges, multi-select status, amount (exact or range) and customer. Each applied filter shows as a dismissible chip, and Clear all removes every one.
* The widget renders no heading of its own. Pass your page title as `headerStart`, and the sponsor bank's logo as `bankAttribution` for the "Business Banking provided by" line beneath it. Both sit in a header row with the invoice settings button and "Create Invoice".
* The widget reads the widget token's scopes. Without `invoice:read:org` it shows an access-restricted state instead of the list. Without `invoice:write:org` it hides Create and the details panel's actions, so a read-only user can browse and open invoices but not change them. The invoice settings button needs `organization_settings:write:org` as well as a handler.
* The invoice form sets payment reminders (before the due date and overdue); issuing is blocked when the customer has reminders off or no email.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                               | Default                    | Description                                                                                                                                 |
| ---------------- | -------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                                           | Inherited                  | API base URL used to create the scoped embedded client for this widget.                                                                     |
| `widgetToken`    | `string \| null`                                   | Inherited                  | Auth token for widget initialization and REST calls. Pass `null` to explicitly suppress auth for this subtree.                              |
| `organizationId` | `string \| null`                                   | Inherited                  | Organization scope sent as `x-organization-id`. Pass `null` to clear inherited org scope; omit to inherit or fall back to widget init data. |
| `configClient`   | `(client: EmbeddedClient) => EmbeddedClient`       | Inherited                  | Optional hook for customizing the scoped HTTP client after built-in auth configuration.                                                     |
| `linkComponent`  | `LinkComponent`                                    | Inherited                  | Host link component used by shared embedded UI surfaces that render links.                                                                  |
| `uiFramework`    | `'shadcn' \| 'tecton' \| null`                     | Inherited, then `'shadcn'` | UI framework selection for this widget tree. `null` and `undefined` resolve to the default shadcn implementation.                           |
| `errorFallback`  | `ReactNode \| (props: FallbackProps) => ReactNode` | Built-in fallback          | Custom render output for the `WidgetProvider` error boundary.                                                                               |
| `onError`        | `(error: unknown, info: ErrorInfo) => void`        | `undefined`                | Telemetry callback invoked when the provider error boundary catches an error.                                                               |
| `analytics`      | `boolean`                                          | `true`                     | Controls package analytics only when this widget is an analytics owner. Nested providers inherit the owning setting.                        |

### Widget props

Each label prop is merged over the English defaults, so pass only the strings you want to change.

See the exported label types listed under TypeScript for every key.

| Prop                       | Type                                                 | Default             | Description                                                                                                                                                                                                                                   |
| -------------------------- | ---------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `initialView`              | `'create'`                                           | `undefined`         | Opens the create-invoice form once on mount, when the user can create invoices. Closing it does not reopen it.                                                                                                                                |
| `initialCounterpartId`     | `string`                                             | `undefined`         | With `initialView: 'create'`, preselects this customer in that first create form only. Otherwise, opens the invoice list filtered to this customer, with a chip naming them. Clear all removes it.                                            |
| `pageSizeOptions`          | `number[]`                                           | `[10, 25, 50, 100]` | Rows-per-page choices. The first page loads at the first choice, or `10` when the list is empty.                                                                                                                                              |
| `onReceivableCreated`      | `(id: string, type: ReceivableDocumentType) => void` | `undefined`         | Called after a create succeeds. Not called for a duplicate.                                                                                                                                                                                   |
| `onReceivableOpened`       | `(id: string, type: ReceivableDocumentType) => void` | `undefined`         | Called when the user opens a row's details panel.                                                                                                                                                                                             |
| `onTemplateSettingsClick`  | `() => void`                                         | `undefined`         | Shows the invoice settings button in the header, for users holding `organization_settings:write:org`, and calls this when it is clicked.                                                                                                      |
| `headerStart`              | `ReactNode`                                          | `undefined`         | Rendered at the start of the header row. The widget renders no heading of its own, so pass your page title here.                                                                                                                              |
| `bankAttribution`          | `ReceivableBankAttribution`                          | `undefined`         | `{ logoSrc, logoAlt }` for the sponsor bank. Shows "Business Banking provided by" and the logo under `headerStart`.                                                                                                                           |
| `embeddedBankName`         | `string`                                             | `undefined`         | Sponsor bank name shown as "Powered by \{embeddedBankName}" next to embedded accounts in the invoice payment-account picker. Omitted when unset.                                                                                              |
| `screenLabels`             | `ReceivablesScreenLabelOverrides`                    | English labels      | Header, table, empty, error and status badge copy.                                                                                                                                                                                            |
| `filterBarLabels`          | `InvoicesFilterBarLabelOverrides`                    | English labels      | Invoice filter bar copy: field names, search, amount bounds, the customer panel and Clear all.                                                                                                                                                |
| `filterLabels`             | `InvoicesFilterLabelOverrides`                       | English labels      | Invoice filter copy built from state: date presets, status descriptions and chip wording.                                                                                                                                                     |
| `formLabels`               | `Partial<ReceivableFormDialogLabels>`                | English labels      | Create/edit form copy, including the unsaved-changes prompt; the save and issue buttons are in `messageLabels`.                                                                                                                               |
| `previewLabels`            | `Partial<ReceivablePreviewLabels>`                   | English labels      | Copy of the live invoice preview beside the create/edit form.                                                                                                                                                                                 |
| `detailsLabels`            | `ReceivableDetailsPanelLabelOverrides`               | English labels      | Details panel copy. Status badge copy comes from `screenLabels.statusBadge`.                                                                                                                                                                  |
| `auditHistoryLabels`       | `Partial<ReceivableAuditHistoryLabels>`              | English labels      | Audit history row copy. Document and status names follow `messageLabels` and `screenLabels.statusBadge` unless overridden here.                                                                                                               |
| `paymentLabels`            | `Partial<ReceivablePaymentDialogLabels>`             | English labels      | Manual payment dialog copy.                                                                                                                                                                                                                   |
| `actionLabels`             | `Partial<ReceivableActionDialogLabels>`              | English labels      | Confirmation dialog buttons; per-action titles come from `messageLabels`.                                                                                                                                                                     |
| `actionsModalLabels`       | `Partial<ReceivableActionsModalLabels>`              | English labels      | Copy of the modal shown after a create, and of its email step on Send for an issued invoice: the email step and the save-items-as-products step. Its titles, email-step dismiss buttons, toasts and select-all toggle are in `messageLabels`. |
| `counterpartFormLabels`    | `PartialDeep<CounterpartFormSheetLabels>`            | English labels      | Inline customer creation copy.                                                                                                                                                                                                                |
| `measureUnitsLabels`       | `Partial<MeasureUnitsManagerLabels>`                 | English labels      | Measure-unit manager copy.                                                                                                                                                                                                                    |
| `measureUnitDeleteLabels`  | `Partial<MeasureUnitDeleteDialogLabels>`             | English labels      | Measure-unit delete confirmation copy.                                                                                                                                                                                                        |
| `messageLabels`            | `Partial<ReceivableMessageLabels>`                   | English labels      | Validation, fallback, details panel, reminder and confirmation messages, including the form's save and issue buttons and a new reminder's subject and body.                                                                                   |
| `productMessageLabels`     | `Partial<ProductMessageLabels>`                      | English labels      | Product validation and error messages, and the measure-unit toasts.                                                                                                                                                                           |
| `counterpartMessageLabels` | `Partial<CounterpartMessageLabels>`                  | English labels      | Customer validation and error messages.                                                                                                                                                                                                       |

## Examples

### With a page title and bank attribution

```tsx
<ReceivablesWidget
  headerStart={<h1>Invoicing</h1>}
  bankAttribution={{ logoSrc: bankLogoUrl, logoAlt: `${bankName} logo` }}
  onTemplateSettingsClick={() => router.push('/settings/invoice')}
/>
```

### Custom labels

```tsx
<ReceivablesWidget
  screenLabels={{
    createInvoiceButton: 'New invoice',
    statusBadge: { issued: 'Sent', partially_paid: 'Part paid' },
  }}
  messageLabels={{ genericError: 'Unable to complete this action.' }}
/>
```

## TypeScript

```ts
import type {
  ReceivablesWidgetProps,
  ReceivableDocumentType,
  ReceivablesScreenLabels,
  ReceivablesScreenLabelOverrides,
  ReceivableStatusBadgeLabels,
  InvoicesFilterBarLabelOverrides,
  InvoicesFilterLabelOverrides,
  ReceivableFormDialogLabels,
  ReceivablePreviewLabels,
  ReceivableDetailsPanelLabels,
  ReceivableDetailsPanelLabelOverrides,
  ReceivablePaymentDialogLabels,
  ReceivableActionDialogLabels,
  ReceivableActionsModalLabels,
  ReceivableBankAttribution,
  ReceivableMessageLabels,
  CounterpartFormSheetLabels,
  CounterpartMessageLabels,
  MeasureUnitsManagerLabels,
  MeasureUnitDeleteDialogLabels,
  ProductMessageLabels,
} from '@tesouro/embedded-components-react';
```

# Linked Accounts (/components/linked-accounts)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Lists the organization's external bank accounts and lets the user connect, edit, verify with micro-deposits, and unlink them.

***

## Quick Start

```tsx
import { LinkedAccountsWidget } from '@tesouro/embedded-components-react';

export function LinkedAccountsPage() {
  return (
    <LinkedAccountsWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
    />
  );
}
```

## Behaviour

* Actions follow the token's scopes. The widget waits for widget init before fetching and hides what the token can't do:
  * **Read** (`embedded:externalbankaccount:read` or `external_bank_account:read:org`) lists accounts. Without it the widget shows the empty state.
  * **Write** (`embedded:externalbankaccount:write` or `external_bank_account:write:org`) enables connect, edit, verify and unlink.
  * **Micro-deposit** (`embedded:externalbankaccount:micro-deposit` or `external_bank_account:micro_deposit:org`) starts micro-deposits during connect and from unverified rows.
  * **Organization read** (`organization:read:org`) pre-fills the connect wizard's legal name on account. Without it the field starts empty and the user types the name.
* The widget shows a skeleton while the widget token is missing or widget init is loading, so a missing token looks like loading rather than an error.
* **Add external account** opens a three-step wizard: bank details, team member access (administrators are always on), and a success screen.
  * Bank details asks whether the account is **Personal** or **Business** (required). The choice is sent as `accountHolderType` so the bank uses the right ACH SEC code.
  * Bank details also asks for the **Legal name on account** (required), sent as `nameOnAccount`. It is pre-filled with the organization's legal name, not its DBA, and the user can edit it.
  * The success screen is titled "Verification deposits sent". It names the account type (for example "business checking") and the last four digits, then lists two next steps: check the bank account, then return to verify the amounts. One **Done** button closes it. It does not name the external bank, because the widget does not know which bank the account is at.
  * In the feature labels, `{last4}` is shown bold and `{type}` takes one of the `*Type` labels.
  * Without the micro-deposit scope no deposits are sent, so the screen is titled "Account added", uses the `summaryAccountAddedDescription` feature label and shows no next steps.
* Verify is available only after micro-deposits were sent. Each amount must be between $0.01 and $0.99.
* Edit and unlink success show a toast. The widget mounts its own toaster, so you need no toast setup.
* `isLinkAccountOpen` opens the **Add external account** wizard from your own control, for example a returned transfer's **Link a new account** button in `TransfersWidget`. It never opens without the write scope, and every opening starts on the first step.
* Failed actions show an inline alert above the rows or inside the open dialog.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                                 | Default           | Description                                                                                                                                            |
| ---------------- | ---------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `baseUrl`        | `string`                                             | Inherited         | API base URL. Passing a value creates a scoped client for this widget.                                                                                 |
| `widgetToken`    | `string \| null`                                     | Inherited         | Widget token used for `/api/widget-gateway/init` and REST requests. Pass `null` to explicitly suppress auth and disable fetching.                      |
| `organizationId` | `string \| null`                                     | Inherited         | Organization header for external-account requests. When omitted across the whole cascade, the provider falls back to the init response's organization. |
| `configClient`   | `(client: EmbeddedClient) => EmbeddedClient`         | Inherited         | Optional hook for customizing the scoped generated REST client after the built-in auth interceptor is applied.                                         |
| `linkComponent`  | `LinkComponent`                                      | Inherited         | Link renderer used by nested shared UI primitives. Omit to inherit, then fall back to a plain anchor.                                                  |
| `uiFramework`    | `'shadcn' \| 'tecton' \| null`                       | Inherited         | Presentation framework selection for widgets that support multiple UI implementations. `null` and `undefined` inherit, then fall back to `shadcn`.     |
| `errorFallback`  | `ReactNode \| ((props: FallbackProps) => ReactNode)` | Built-in fallback | Rendered when the provider's error boundary catches a render-time exception.                                                                           |
| `onError`        | `(error: unknown, info: ErrorInfo) => void`          | `undefined`       | Called when the provider error boundary catches an error.                                                                                              |
| `analytics`      | `boolean`                                            | `true`            | Honored only when this widget is the analytics owner. Nested widgets inherit the outer owner.                                                          |

### Widget props

| Prop                      | Type                                         | Default        | Description                                                                                                                             |
| ------------------------- | -------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `labels`                  | `Partial<LinkedAccountsWidgetLabels>`        | English labels | Override UI copy. `stepIndicator` takes `{current}` and `{total}`; `verifySuccessDescription` and `unlinkDescription` take `{account}`. |
| `featureLabels`           | `Partial<LinkedAccountsWidgetFeatureLabels>` | English labels | Override row fallback text, the account-number prefix, and the edit/unlink success toasts.                                              |
| `isLinkAccountOpen`       | `boolean`                                    | Uncontrolled   | Controls the **Add external account** wizard. When set, the widget only reports open/close requests and waits for you.                  |
| `onLinkAccountOpenChange` | `(open: boolean) => void`                    | `undefined`    | Called on every open and close of the wizard, whether or not it is controlled.                                                          |

## Examples

### Inherit auth from a root provider

```tsx
import {
  LinkedAccountsWidget,
  RootWidgetProvider,
} from '@tesouro/embedded-components-react';

export function BankingPage() {
  return (
    <RootWidgetProvider
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
    >
      <LinkedAccountsWidget
        organizationId="org_xyz"
        labels={{ title: 'External accounts', connectButton: 'Add account' }}
        featureLabels={{ accountLabel: 'Account #:' }}
      />
    </RootWidgetProvider>
  );
}
```

See the exported `LinkedAccountsWidgetLabels` and `LinkedAccountsWidgetFeatureLabels` types for every key.

## TypeScript

```ts
import type {
  LinkedAccountsWidgetFeatureLabels,
  LinkedAccountsWidgetLabels,
  LinkedAccountsWidgetProps,
} from '@tesouro/embedded-components-react';
```

# Offer (/components/offer)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

An offer card and modal that invites the user to apply for a product (today, a bank account) and then shows where their application stands.

The card and its "Learn more" modal present your product copy (logo, captions, "How it works" steps, benefits). Its Apply now and modal CTAs call `onPrimaryCtaClick`, which you typically wire to show `BankAccountOnboardingWidget` inline. Once an application exists, the card swaps its CTAs for the application's status (see Behaviour), and it hides itself when the user already has an account.

***

## Quick Start

Pass your tenant-specific `content`; the package looks up no white-label or bank assets itself.

`BankAccountOnboardingWidget` renders inline and fills its container, so the CTA either navigates to a page that hosts it or swaps it in place of the card inside a container with a real height:

```tsx
import { useState } from 'react';
import {
  OfferWidget,
  BankAccountOnboardingWidget,
} from '@tesouro/embedded-components-react';

function ProspectOnboarding({ content }) {
  const [isOnboarding, setIsOnboarding] = useState(false);

  if (isOnboarding) {
    return (
      <div style={{ height: '100vh', minHeight: 600 }}>
        <BankAccountOnboardingWidget
          onEmbeddedOnboardingCompletedSuccessfully={handleComplete}
          onNavigateToDashboard={goToDashboard}
        />
      </div>
    );
  }

  return (
    <OfferWidget
      content={content}
      onPrimaryCtaClick={() => setIsOnboarding(true)}
    />
  );
}
```

## Behaviour

* Renders nothing until widget init and the organization's application status load.
* Hidden when the user is already `ACTIVE` / `INACTIVE` or `INVITED`, or the organization's bank-account application is `COMPLETE`.
* Otherwise the card follows the organization's application:

| Application status     | Started by the user                        | Started by a colleague         |
| ---------------------- | ------------------------------------------ | ------------------------------ |
| none                   | Normal offer                               | Normal offer                   |
| `DRAFT`                | Normal offer, CTA reads Resume application | Status chip + message, no CTAs |
| `SUBMITTED`            | Status chip + message, no CTAs             | Status chip + message, no CTAs |
| `DENIED`               | Status chip + message, no CTAs             | Status chip + message, no CTAs |
| `DELETED` / `CANCELED` | Normal offer                               | Normal offer                   |
| `COMPLETE`             | Hidden                                     | Hidden                         |

* Resume application calls the same `onPrimaryCtaClick`; `BankAccountOnboardingWidget` resumes the draft by itself.
* The bank name in status messages comes from widget init.
* The widget renders `content` as given, so your sponsor bank must approve that copy before it goes live.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                         | Default   | Description                                                                                       |
| ---------------- | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                     | Inherited | Base URL of the API. When set, a scoped HTTP client is created for this widget.                   |
| `widgetToken`    | `string \| null`             | Inherited | Bearer token for widget requests. `null` clears auth at this level.                               |
| `organizationId` | `string \| null`             | Inherited | Organization ID forwarded as the `x-organization-id` header.                                      |
| `configClient`   | `(client) => EmbeddedClient` | Inherited | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. |

### Widget props

| Prop                | Type                                   | Default        | Description                                                                                                                         |
| ------------------- | -------------------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `content`           | `OfferWidgetContent`                   | **Required**   | Host-resolved card and modal copy, images, "How it works" steps, and benefits.                                                      |
| `onPrimaryCtaClick` | `() => void`                           | **Required**   | Called when the applicant clicks Apply now on the card or the modal CTA (default label: Start application). Closes the modal first. |
| `labels`            | `PartialDeep<OfferWidgetLabels>`       | English labels | Override the card and modal copy. See the exported `OfferWidgetLabels` type for every key.                                          |
| `statusLabels`      | `PartialDeep<OfferWidgetStatusLabels>` | English labels | Override the Resume application label and each status message. Heading and body are functions of `{ bankName }`.                    |

## TypeScript

```ts
import type {
  OfferWidgetProps,
  OfferWidgetContent,
  OfferWidgetLabels,
  OfferWidgetStatusLabels,
  OfferWidgetBenefit,
  OfferWidgetHowItWorksStep,
  OfferWidgetImage,
} from '@tesouro/embedded-components-react';
import { OfferBenefitIcon } from '@tesouro/embedded-components-react';
```

# Products (/components/products)

Part of: [FinOps](https://docs.tesouro.com/finops)

Manages an organization's products and services: a filterable, sortable table with create, edit, view and delete flows, plus measure-unit management.

***

## Quick Start

```tsx
import { ProductsWidget } from '@tesouro/embedded-components-react';

export function ProductsPage() {
  return (
    <ProductsWidget
      baseUrl="https://api.tesouro.com"
      widgetToken={widgetToken}
      organizationId={organizationId}
    />
  );
}
```

You supply only auth and optional label overrides; the widget owns data, loading, pagination and permissions.

## Behaviour

* The widget gates on **invoice** scopes, not `product:*`. `invoice:read:org` is required to view it (otherwise it shows "Access restricted"); `invoice:write:org` enables create, edit, delete and measure-unit management. A token carrying only `product:*` scopes is denied.
* Prices are entered and shown in major units (such as dollars); the widget converts to the API's minor units.
* Only **Name** is sortable. The filter bar has a name search and a **Filters** menu with type (product/service) and measure unit, each applied filter shown as a removable chip; pagination is Previous/Next.
* A product is edited inline in its details sheet. Save stays disabled until a field changes, and Cancel or a successful save return to the product's details.
* Deleting a measure unit that products use shows a warning listing the affected products.
* Products are priced in USD. There is no currency picker: a new product is created in USD, and editing an existing product keeps the currency it was saved with.
* The form's unit picker selects an existing measure unit only. Units are created, renamed and deleted from the list's **Manage units of measure** button.
* When the API rejects a save, each validation error appears under the field that caused it; anything that names no field appears above the form.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                | Default        | Description                                                       |
| ---------------- | ----------------------------------- | -------------- | ----------------------------------------------------------------- |
| `baseUrl`        | `string`                            | Inherited      | API base URL for this widget's scoped client.                     |
| `widgetToken`    | `string \| null`                    | Inherited      | Auth token. Queries idle until this and `organizationId` resolve. |
| `organizationId` | `string \| null`                    | Inherited      | Organization scope, sent as `x-organization-id`.                  |
| `configClient`   | `(client) => client`                | Inherited      | Post-creation hook for the scoped HTTP client.                    |
| `linkComponent`  | `LinkComponent`                     | Inherited      | Host link component override.                                     |
| `errorFallback`  | `ReactNode \| (props) => ReactNode` | Built-in alert | Rendered when the subtree throws.                                 |
| `onError`        | `(error, info) => void`             | `undefined`    | Error-boundary telemetry hook.                                    |
| `analytics`      | `boolean`                           | `true`         | Honored only when this is the analytics owner.                    |

### Widget props

Each label prop is merged over the English defaults; nested label groups merge per group.

| Prop                      | Type                                     | Default             | Description                                                  |
| ------------------------- | ---------------------------------------- | ------------------- | ------------------------------------------------------------ |
| `pageSizeOptions`         | `number[]`                               | `[10, 25, 50, 100]` | Rows-per-page choices.                                       |
| `screenLabels`            | `PartialDeep<ProductsScreenLabels>`      | English labels      | Table/header/filter/empty/error copy.                        |
| `formLabels`              | `PartialDeep<ProductFormSheetLabels>`    | English labels      | Create sheet and inline edit form copy.                      |
| `detailsLabels`           | `PartialDeep<ProductDetailsSheetLabels>` | English labels      | Details sheet copy.                                          |
| `deleteLabels`            | `Partial<ProductDeleteDialogLabels>`     | English labels      | Product delete confirmation copy.                            |
| `measureUnitsLabels`      | `Partial<MeasureUnitsManagerLabels>`     | English labels      | Measure-units manager copy.                                  |
| `measureUnitDeleteLabels` | `Partial<MeasureUnitDeleteDialogLabels>` | English labels      | Measure-unit delete confirmation copy.                       |
| `messageLabels`           | `Partial<ProductMessageLabels>`          | English labels      | Validation, error and toast messages produced by the widget. |

## TypeScript

```ts
import type {
  ProductsWidgetProps,
  ProductsScreenLabels,
  ProductFormSheetLabels,
  ProductDetailsSheetLabels,
  ProductDeleteDialogLabels,
  MeasureUnitsManagerLabels,
  MeasureUnitDeleteDialogLabels,
  ProductMessageLabels,
} from '@tesouro/embedded-components-react';
```

# Settings (/components/settings)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking), [FinOps](https://docs.tesouro.com/finops)

A single navigable settings surface combining profile, team, invoice, bill-pay, expense, accounting, legal and help sections.

***

## Quick Start

```tsx
import { SettingsWidget } from '@tesouro/embedded-components-react';

export function SettingsPage() {
  return (
    <SettingsWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
    />
  );
}
```

## Behaviour

* Most built-in sections are scope-gated, and scopes arrive with widget init. A section the user's scopes hide falls back to the first visible one, and nothing is reported through `onSelectedSectionChange`, so your URL can name a section the user isn't looking at.
* Sections from `additionalSections` aren't scope-gated. An id that collides with a built-in section is ignored; use that section's `*Content` prop instead.
* The Help "Contact Us" line is hidden until you set `helpContactUrl`, and the Profile contact line is unlinked until you set `profileContactUrl`.
* The widget reads no URL or router; you own navigation via `selectedSection` and `onSelectedSectionChange`.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type             | Default   | Description                                               |
| ---------------- | ---------------- | --------- | --------------------------------------------------------- |
| `baseUrl`        | `string`         | Inherited | API base URL for widget initialization and REST requests. |
| `widgetToken`    | `string \| null` | Inherited | Widget token used by the provider stack.                  |
| `organizationId` | `string \| null` | Inherited | Organization scope for the widget.                        |

### Widget props

| Prop                      | Type                                         | Default                           | Description                                                                                                  |
| ------------------------- | -------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `acceptInviteRedirectUri` | `string`                                     | Same-origin `/accept-invite`      | Where Team invite emails link to; set it when your landing route differs or the origin isn't allowlisted.    |
| `labels`                  | `Partial<Labels>`                            | English labels                    | Overrides the settings heading copy.                                                                         |
| `featureLabels`           | `Partial<FeatureLabels>`                     | English labels                    | Overrides section labels.                                                                                    |
| `profileContent`          | `ReactNode`                                  | Profile widget                    | Replaces the profile section content.                                                                        |
| `teamContent`             | `ReactNode`                                  | Team widget                       | Replaces the team section content.                                                                           |
| `invoiceContent`          | `ReactNode`                                  | Invoice settings widget           | Replaces invoice settings content.                                                                           |
| `billPayContent`          | `ReactNode`                                  | Approval policies settings        | Replaces bill-pay settings content.                                                                          |
| `accountingContent`       | `ReactNode`                                  | Chart of accounts widget          | Replaces accounting content.                                                                                 |
| `expenseContent`          | `ReactNode`                                  | Expense policies and requirements | Replaces expense content.                                                                                    |
| `legalContent`            | `ReactNode`                                  | Bank disclosures widget           | Replaces the Legal section content.                                                                          |
| `profileContactUrl`       | `string`                                     | `undefined`                       | Link for the Profile section's "contact your bank" line; unlinked when unset.                                |
| `selectedSection`         | `SettingsWidgetSectionId`                    | `undefined`                       | The section to show (controlled). Pair with `onSelectedSectionChange`; leave unset to let the widget own it. |
| `defaultSelectedSection`  | `SettingsWidgetSectionId`                    | First visible section             | The section to open on; read once per mount and ignored when `selectedSection` is set.                       |
| `onSelectedSectionChange` | `(section: SettingsWidgetSectionId) => void` | `undefined`                       | Called with the section the user selected, whether or not `selectedSection` is supplied.                     |
| `additionalSections`      | `SettingsWidgetAdditionalSection[]`          | `undefined`                       | Host-owned `{ id, label, content, after? }` sections; `after` names the built-in section to place it behind. |
| `helpContactUrl`          | `string`                                     | `undefined`                       | Link (URL or `mailto:`) for the Help section's "Contact Us" line; hidden when unset.                         |
| `helpFaq`                 | `HelpFaqSection[]`                           | `HELP_FAQ_EN`                     | FAQ shown in the Help section. Extend the exported `HELP_FAQ_EN` to add your own questions.                  |
| `helpContent`             | `ReactNode`                                  | Help widget                       | Replaces the help section content.                                                                           |

## Examples

### Deep-linking to a section

```tsx
const section = searchParams.get('section') ?? undefined;

<SettingsWidget
  selectedSection={section}
  onSelectedSectionChange={(next) =>
    router.replace(`/settings?section=${next}`)
  }
/>;
```

To open on a section without owning the selection, use `defaultSelectedSection={SettingsSection.Team}` on its own.

### Contributing a section

```tsx
<SettingsWidget
  additionalSections={[
    {
      id: 'security',
      label: 'Security',
      content: <SecurityPage />,
      after: SettingsSection.Profile,
    },
  ]}
/>
```

## TypeScript

```ts
import {
  SettingsSection,
  type SettingsWidgetProps,
  type SettingsWidgetSectionId,
  type SettingsWidgetAdditionalSection,
  type SettingsWidgetLabels,
  type SettingsWidgetFeatureLabels,
} from '@tesouro/embedded-components-react';
```

`SettingsSection` names the built-in section ids: `profile`, `team`, `invoice`, `expense`, `bill-pay`, `accounting`, `legal`, `help`.

# Transfers (/components/transfers)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking)

Lists recent transfers across the organization's embedded bank accounts and lets the user send a book or ACH transfer.

***

## Quick Start

```tsx
import { TransfersWidget } from '@tesouro/embedded-components-react';

export function TransfersPage() {
  return (
    <TransfersWidget
      baseUrl="https://api.example.com"
      widgetToken="wt_live_abc123"
      organizationId="org_xyz"
    />
  );
}
```

Omit the auth props when the widget sits inside a configured `RootWidgetProvider` or `WidgetProvider`.

## Behaviour

* The **Transfer money** button shows only when the token has `embedded:transfer:write` and `embedded:bankaccount:read`.
* Verified, active external accounts appear in the modal only when the token also has `embedded:externalbankaccount:read` and `payment_rail:ach`. Without them, the modal lists embedded accounts only.
* The widget shows a skeleton until every page of accounts has loaded, an error with **Retry** if a query fails, and an empty state when there are no transfers.
* The list covers every bank account the user can see, newest first, 25 transfers to a page by default. **Previous** and **Next** move between pages. Changing the page size, the organization or the signed-in user returns the list to the first page.
* Each row shows the date, the From and To accounts, the status and the amount. External accounts carry an **External** badge, and hovering an account names its kind. A failed amount is struck through, and a returned transfer shows a short reason under its status.
* Clicking a row, or pressing Enter or Space on it, opens a **Transfer details** panel with the amount, status, accounts, who started the transfer (or "System") and when, in the browser's time zone.
* A returned ACH transfer's panel explains the return, says the money was refunded when it left one of the user's accounts, and says transfers to that external account are paused. It never shows the raw return code. Pass `onLinkAccountClick` to add a **Link a new account** button, for example to open `LinkedAccountsWidget`'s wizard through its `isLinkAccountOpen` prop.
* `isTransferMoneyOpen` opens the modal even when the widget hides its own button. The token's scopes still gate the submit.
* Every opening starts on the first step with nothing selected; you can't preselect a step or accounts.

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop             | Type                                               | Default        | Description                                                                                                                                                                |
| ---------------- | -------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl`        | `string`                                           | Inherited      | Base URL of the API. When set, a scoped HTTP client is created for this widget. When omitted, inherits from the nearest ancestor `WidgetProvider` or `RootWidgetProvider`. |
| `widgetToken`    | `string \| null`                                   | Inherited      | Auth token for this widget. Triggers a `/api/widget-gateway/init` fetch. Pass `null` to explicitly suppress auth. Omit to inherit from the parent.                         |
| `organizationId` | `string \| null`                                   | Inherited      | Scopes API calls to a specific organization. Doesn't trigger a re-fetch on its own. Pass `null` to clear the org scope for this subtree. Omit to inherit from the parent.  |
| `configClient`   | `(client) => EmbeddedClient`                       | Inherited      | Optional post-creation hook for the scoped HTTP client. Runs after the built-in auth interceptor. Omit to inherit from the parent.                                         |
| `linkComponent`  | `LinkComponent`                                    | Inherited      | Component used in place of plain `<a>` tags inside widget UI.                                                                                                              |
| `errorFallback`  | `ReactNode \| (props: FallbackProps) => ReactNode` | Built-in alert | Custom fallback for the built-in error boundary wrapping this widget.                                                                                                      |
| `onError`        | `(error: unknown, info: ErrorInfo) => void`        | `undefined`    | Called once per caught render error before the fallback renders.                                                                                                           |
| `analytics`      | `boolean`                                          | `true`         | Opt out of analytics capture for a standalone (root) provider by passing `false`. Ignored on nested providers.                                                             |

### Widget props

| Prop                         | Type                                    | Default          | Description                                                                                              |
| ---------------------------- | --------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------- |
| `labels`                     | `Partial<TransfersWidgetLabels>`        | English labels   | Override list and modal copy.                                                                            |
| `featureLabels`              | `Partial<TransfersWidgetFeatureLabels>` | English defaults | Override fallback text, the viewing range prefix, default currency and ACH SEC code.                     |
| `isTransferMoneyOpen`        | `boolean`                               | Uncontrolled     | Controls the transfer modal. When set, the widget only reports open/close requests and waits for you.    |
| `defaultIsTransferMoneyOpen` | `boolean`                               | `false`          | Initial modal state when uncontrolled. Ignored when `isTransferMoneyOpen` is set.                        |
| `onTransferMoneyOpenChange`  | `(open: boolean) => void`               | `undefined`      | Called on every open and close: the button, the modal's own dismissals, and the close after a transfer.  |
| `onLinkAccountClick`         | `() => void`                            | `undefined`      | Called from a returned transfer's **Link a new account** button. The button shows only when this is set. |

## Examples

### Open the modal from your own button

```tsx
import { useState } from 'react';
import { TransfersWidget } from '@tesouro/embedded-components-react';

export function TransfersPage() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Send money</button>
      <TransfersWidget
        isTransferMoneyOpen={isOpen}
        onTransferMoneyOpenChange={setIsOpen}
      />
    </>
  );
}
```

Wire `onTransferMoneyOpenChange` back into your state, or the modal stays pinned to your value.

### Custom copy

```tsx
<TransfersWidget
  labels={{
    widgetTitle: 'Transferencias',
    transferMoneyCta: 'Transferir dinero',
  }}
  featureLabels={{ defaultCurrencyCode: 'USD', defaultSecCode: 'CCD' }}
/>
```

See the exported `TransfersWidgetLabels` and `TransfersWidgetFeatureLabels` types for every key. The English defaults are exported as `TRANSFERS_WIDGET_LABELS_EN` and `TRANSFERS_FEATURE_LABELS_EN`.

## TypeScript

```ts
import type {
  TransfersWidgetProps,
  TransfersWidgetLabels,
  TransfersWidgetFeatureLabels,
} from '@tesouro/embedded-components-react';
```

# Widget Suite (/components/widget-suite)

Part of: [Embedded Banking](https://docs.tesouro.com/embedded-banking), [FinOps](https://docs.tesouro.com/finops)

A whole banking page in one component: a section menu over the widgets behind it, served by a single widget-init call. Unlike a widget, a suite owns the page it is placed on, so mount one per page.

***

## Quick Start

```tsx
import { WidgetSuite } from '@tesouro/embedded-components-react';

export function BankingPage() {
  return (
    <WidgetSuite
      baseUrl="https://api.tesouro.com"
      widgetToken={widgetToken}
      organizationId={organizationId}
    />
  );
}
```

`sections` defaults to `WIDGET_SUITE_DEFAULT_SECTIONS`, the full surface in menu order. Pass a list to narrow or reorder it, for example `sections={['dashboard', 'accounts', 'cards']}`.

## Behaviour

* A section you list is still hidden when the user's token doesn't earn it, so `sections` is a ceiling, not a promise of what renders.
* Before the menu paints, the suite shows placeholders while loading, a retry state if widget init fails, the disclosure acceptance surface when the session owes the bank's disclosures, full-screen onboarding whenever the onboarding widget has something to show for the organization's application (the form, or its status card with a **Check again** button), or a "We can't show your application" retry state when it has nothing to show (a denial more than 30 days old, or an unrecognized status). An invited teammate goes straight to the disclosure gate.
* When onboarding completes, the suite switches to the menu on its own, without a reload.
* The composed widgets inherit the suite's auth, organization and query cache, so the whole page makes one widget-init call.
* Only the open section is addressable. To deep-link an account, card or settings tab, mount that widget directly instead.
* Passing `section` without `onSectionChange` freezes the menu.
* When the token doesn't earn the section you asked for, the suite silently shows the first section the user can see and doesn't call `onSectionChange`.
* The Dashboard's balance rows and CTAs change section like a menu click and are reported through `onSectionChange`.
* In Cards, the debit Create card action shows only when the token holds the issue, roster and funding scopes.
* The suite takes no data or loading props. Those belong to the widgets it mounts.

### Sections

| Section id  | What it mounts                                                          |
| ----------- | ----------------------------------------------------------------------- |
| `dashboard` | Balances and Insights side by side                                      |
| `accounts`  | `BankAccountsWidget`                                                    |
| `cards`     | `CreditCardsWidget` and `DebitCardsWidget`, per program the token reads |
| `transfers` | `TransfersWidget`                                                       |
| `bill-pay`  | `BillPayWidget`                                                         |
| `invoicing` | `ReceivablesWidget`                                                     |
| `expenses`  | a placeholder, until the expenses surface is ready                      |
| `settings`  | `SettingsWidget`, which resolves its own sections from the same scopes  |

## Props

**Auth / scope props (shared)**

From `WidgetProviderProps`. Omit to inherit from a parent provider.

| Prop                    | Type                                | Default        | Description                                                                                             |
| ----------------------- | ----------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------- |
| `baseUrl`               | `string`                            | Inherited      | API base URL for this widget's scoped client.                                                           |
| `widgetToken`           | `string \| null`                    | Inherited      | Auth token. Queries idle until this and `organizationId` resolve.                                       |
| `organizationId`        | `string \| null`                    | Inherited      | Organization scope, sent as `x-organization-id`.                                                        |
| `configClient`          | `(client) => client`                | Inherited      | Post-creation hook for the scoped HTTP client.                                                          |
| `linkComponent`         | `LinkComponent`                     | Inherited      | Host link component override.                                                                           |
| `uiFramework`           | `UIFramework`                       | Inherited      | UI framework selection for the provider cascade.                                                        |
| `errorFallback`         | `ReactNode \| (props) => ReactNode` | Built-in alert | Rendered when the subtree throws.                                                                       |
| `onError`               | `(error, info) => void`             | `undefined`    | Error-boundary telemetry hook.                                                                          |
| `analytics`             | `boolean`                           | `true`         | Honored only when this is the analytics owner.                                                          |
| `gatewayRouting`        | `boolean`                           | `true`         | Routes calls through the Tesouro widget gateway. Turn it off to call the API directly.                  |
| `disclosuresAcceptance` | `ReactNode`                         | Built-in       | Replaces the disclosure acceptance surface the gate shows.                                              |
| `providerLabels`        | `Partial<WidgetProviderLabels>`     | Built-in copy  | The provider's own copy: its error fallback and disclosure gate. Not part of the suite's `labels` tree. |

### Widget props

| Prop                        | Type                              | Default                         | Description                                                                                                                                                 |
| --------------------------- | --------------------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sections`                  | `readonly WidgetSuiteSectionId[]` | `WIDGET_SUITE_DEFAULT_SECTIONS` | Which sections the suite offers, in menu order. A listed section is still hidden when the token doesn't earn it.                                            |
| `section`                   | `WidgetSuiteSectionId`            | `undefined`                     | Which section is showing. Supplying it makes the menu controlled for the lifetime of the mount, so pair it with `onSectionChange`.                          |
| `defaultSection`            | `WidgetSuiteSectionId`            | first of `sections`             | Which section the suite opens on. Read once per mount, and ignored when `section` is supplied.                                                              |
| `onSectionChange`           | `(section) => void`               | `undefined`                     | Called when the user moves the suite. Never fired on mount or for the entitlement fallback.                                                                 |
| `labels`                    | `WidgetSuiteLabelOverrides`       | `WIDGET_SUITE_LABELS_EN`        | Every string on the page in one tree, including the copy of each section's widget. Override any leaf and its siblings keep their defaults.                  |
| `showSectionTitle`          | `boolean`                         | `true`                          | Shows a heading above each section, named as its menu item is. Set `false` when your page already heads the suite.                                          |
| `onboardingDisclosureLinks` | `DisclosureLinks`                 | `undefined`                     | Legal disclosure URLs for the onboarding agreement step, shown when the organization has no application yet. Omitted fields render as non-clickable text.   |
| `cardArtSrc`                | `string`                          | `undefined`                     | Card artwork URL for the debit Create card sheet and card face; omitted, a neutral plastic and the bank logo are used. Credit Create card is always hidden. |

## Examples

### Section in the URL

Pass `section` and `onSectionChange` together to own navigation. Use `defaultSection` instead when you only need to choose where the suite opens.

```tsx
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import {
  WidgetSuite,
  WIDGET_SUITE_DEFAULT_SECTIONS,
  type WidgetSuiteSectionId,
} from '@tesouro/embedded-components-react';

function toSection(value: string | null): WidgetSuiteSectionId | undefined {
  const known: readonly string[] = WIDGET_SUITE_DEFAULT_SECTIONS;
  return value && known.includes(value)
    ? (value as WidgetSuiteSectionId)
    : undefined;
}

export function BankingPage() {
  const router = useRouter();
  const params = useSearchParams();

  return (
    <WidgetSuite
      section={toSection(params.get('page')) ?? 'dashboard'}
      onSectionChange={(next) => router.replace(`?page=${next}`)}
    />
  );
}
```

Never pass `section={undefined}` while controlling the suite: that hands control back to it. `router.replace` keeps Back from stepping through every section visited.

### Labels

A group under `sections` takes the same label props as the widget behind it. `providerLabels` is separate.

```tsx
<WidgetSuite
  labels={{
    menu: { accounts: 'Bank accounts' },
    sectionTitle: { bankTagline: '{bankName} holds your deposits' },
    sections: {
      cards: { creditCards: { labels: { emptyTitle: 'No credit cards yet' } } },
    },
  }}
/>
```

See the exported `WidgetSuiteLabels` type for every key, and `WIDGET_SUITE_LABELS_EN` for the defaults.

## TypeScript

```ts
import type {
  WidgetSuiteProps,
  WidgetSuiteSectionId,
  WidgetSuiteLabels,
  WidgetSuiteLabelOverrides,
} from '@tesouro/embedded-components-react';
```

Each label group is also exported under its own name (for example `WidgetSuiteMenuLabels` and `WidgetSuiteCardsLabels`).
