@ln80/product-console-sdk
v0.6.1
Published
Headless React SDK for embedding product-console features (support, waitlist, contact, abuse reports) into apps and websites.
Readme
@ln80/product-console-sdk
Headless React SDK for embedding product-console features — support ticketing, waitlist, contact/inquiry, and abuse reports — into your SaaS app or marketing site.
The SDK is headless: it owns state machines, data fetching, proof-of-work, attachment uploads, and typed error handling, and exposes everything as hooks and render-prop controllers. You bring the UI, styling, and theming.
Install
npm i @ln80/product-console-sdk @tanstack/react-query react react-domreact, react-dom, and @tanstack/react-query are peer dependencies.
Entry points
Each feature is a separate subpath export so you only load what you use:
| Import | What it provides |
|---|---|
| @ln80/product-console-sdk | Types only (no runtime) |
| @ln80/product-console-sdk/provider | ProductConsoleProvider, useProductConsole, useSdkClient |
| @ln80/product-console-sdk/support | Embedded ticketing: provider, hooks, controllers |
| @ln80/product-console-sdk/waitlist | useJoinWaitlist, JoinWaitlist, useHoneypot |
| @ln80/product-console-sdk/contact | useSubmitInquiry, ContactForm, INQUIRY_CATEGORIES, useHoneypot |
| @ln80/product-console-sdk/trust | useSubmitAbuseReport, AbuseReportForm, ABUSE_CATEGORIES, useHoneypot |
Guides: Support integration · Public forms (waitlist / contact / abuse report) — agent-friendly, shipped with the package.
Setup
Wrap your app once with the provider, pointing it at your product-console API
(including the /_pc/api prefix):
import { ProductConsoleProvider } from "@ln80/product-console-sdk/provider";
<ProductConsoleProvider config={{ apiBaseUrl: "https://api.example.com/_pc/api" }}>
<App />
</ProductConsoleProvider>;The provider creates and owns a TanStack Query client unless you pass your own
via the queryClient prop.
Support (embedded ticketing)
Supply the customer identity from your authenticated session, then use the hooks/controllers to build the UI.
import {
SupportProvider,
SupportTicketList,
SupportTicketThread,
CreateSupportTicket,
} from "@ln80/product-console-sdk/support";
<SupportProvider identity={{ customerId: user.id, email: user.email, name: user.name }}>
{/* New ticket */}
<CreateSupportTicket onSuccess={({ ticketId }) => open(ticketId)}>
{({ state, ui, actions }) => (
<form
onSubmit={(e) => {
e.preventDefault();
actions.submit({ subject, content });
}}
>
{/* ...your inputs... */}
<button disabled={!ui.canSubmit}>Send</button>
{state.error && <p>{state.error.message}</p>}
</form>
)}
</CreateSupportTicket>
{/* List */}
<SupportTicketList>
{({ state, ui, actions }) =>
ui.isLoading ? <Spinner /> : state.tickets.map((t) => <Row key={t.id} t={t} />)
}
</SupportTicketList>
{/* Thread + reply composer (with attachments) */}
<SupportTicketThread ticketId={ticketId}>
{({ state, composer }) => (
<>
{state.ticket?.messages.map((m) => <Bubble key={m.id} m={m} />)}
<textarea
value={composer.state.content}
onChange={(e) => composer.actions.setContent(e.target.value)}
/>
<input
type="file"
multiple
onChange={(e) => e.target.files && composer.attachments.actions.add(e.target.files)}
/>
<button disabled={!composer.ui.canSend} onClick={() => composer.actions.send()}>
Reply
</button>
</>
)}
</SupportTicketThread>
</SupportProvider>;Every hook returns { state, ui, actions }. state.step is a typed state
machine; errors are surfaced on state.error (typed SdkError) and never
thrown.
Integrating support? See the agent-friendly Support integration guide — full API surface, a complete golden example, and do/don't rules. It ships with the package so coding assistants can read it from
node_modules.
Conventions
- Headless — no CSS, no DOM rendered by the SDK. Controllers render only what your render-prop returns.
- Typed errors in state — branch on
state.error.code. - Proof-of-work — computed automatically for endpoints that require it.
Build
yarn build # vite (ESM, per-feature entries) + tsc (.d.ts)
yarn typecheck
yarn lintSecurity note
When the optional support authorizer is configured, pass a host-minted token as
SupportIdentity.identityToken (sent as Authorization: Bearer <token>). The
authorizer is the security boundary; customerId is then a display/scoping hint
overridden by gateway context. Without an authorizer, customerId is an opaque
unsigned value and is spoofable by a malicious client (public PoW path).
