@get-asset/sdk

LockedUpsell

Shared machinery behind every chart card's locked / upsell state. LockedUpsell has no Rootof its own — each lockable card's own Root accepts locked and upselland provides the context these parts read. Compose them inside that card's Locked state gate.

Preview

Profit & Loss

2026
Unlock your profit & loss

See your full month-by-month profit and loss, plus insights, on a paid plan.

Anatomy

Import the primitive and compose the parts you need.

<ProfitLossCard.Root locked upsell={{ ctaLabel: "Upgrade now", onUpgrade }}>
<ProfitLossCard.Locked>
<LockedUpsell.Teaser>
{/* the report's normal chart/bars/metrics markup, rendered as a blurred, inert teaser */}
</LockedUpsell.Teaser>
<LockedUpsell.Overlay>
<LockedUpsell.Title />
<LockedUpsell.Description />
<LockedUpsell.Cta />
</LockedUpsell.Overlay>
</ProfitLossCard.Locked>
</ProfitLossCard.Root>

API reference

Root props

PropTypeDefaultDescription
lockedbooleanOn each lockable card's Root (default false). Gates the report for a viewer who isn't entitled to it — activates the Locked state (mutually exclusive with loading / error / empty / success) and skips the API fetch, including via retry(). Wins over the state override.
upsell{ title?, description?, ctaLabel?, onUpgrade? }On each lockable card's Root. Tenant-customizable copy + upgrade handler for the locked state. Resolved per field: the upsell prop, then the card's own default copy, then generic fallback copy.

Parts

Parts

Compose these inside a lockable card's `Locked` state gate — they read the copy and upgrade handler from that card's Root.

PartDescription
LockedUpsell.Teaser
asChild?
Wraps the report's normal markup as a blurred, inert teaser: aria-hidden, data-locked-teaser, pointer-events/user-select disabled, and the node set inert.
LockedUpsell.Overlay
asChild?
Wrapper for the lock badge, copy, and CTA. Renders role="group" with an aria-label of the resolved title.
LockedUpsell.Title
asChild?
Resolved upsell headline.
LockedUpsell.Description
asChild?
Resolved upsell disclaimer.
LockedUpsell.Cta
asChild?
Button that calls the tenant's onUpgrade on click. No-op when onUpgrade is omitted.

Hooks

PartDescription
useLockedUpsell()Hook returning { locked, upsell }, where upsell is the resolved { title, description, ctaLabel, onUpgrade }. Throws when used outside a lockable card's Root.

Behavior notes

  • No request fires while locked — including via retry(), which is a no-op when locked is set (TanStack Query's refetch() otherwise bypasses enabled).
  • With no data override, the card serves deterministic, built-in sample data as the teaser — healthy-looking and obviously generic. Passing a data override (e.g. months) still wins over that sample data.
  • Unlocking is the tenant's responsibility: re-render with locked set to false. The SDK never auto-unlocks.
  • locked wins over the state override — a card forced into state="loading" (or error/empty) still renders Locked when locked is also set.
  • Never pass real entitlement-gated data as the teaser — the teaser is meant to be a generic, illustrative sample, not the viewer's actual (gated) numbers.