@get-asset/sdk

Locked Upsell

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

Loading preview…

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.