Root & data components
FartherShoreRoot and the self-managed, zero-prop data components.
FartherShoreRoot and the self-managed, zero-prop data components.
The component kit gives you a portal with almost no code. <FartherShoreRoot> is the single mount point — provider, bootstrap gate, theming, and managed auth in one wrapper — and under it every data component is self-managed: it works with zero props, fetching its own data through the SDK hooks. Props are optional overrides. Drop in <PlansTable/>, <UsageCard/>, <ApiKeysPanel/>, <BillingSummary/> and you have a working dashboard.
Components import from the /components subpath; load the stylesheet once.
import {
FartherShoreRoot,
PlansTable,
UsageCard,
ApiKeysPanel,
BillingSummary,
} from "@farthershore/farthershore-js/components";
import "@farthershore/farthershore-js/components/styles.css";
<FartherShoreRoot>One wrapper gives a host app everything self-managed. It owns the client provider, the bootstrap gate (one cached resolve round-trip, with splash + error states), the theme root (the brand color from the product's branding), and the managed auth layer (Clerk satellite or persona — picked from the environment automatically). No host glue.
Everything inside <FartherShoreRoot> renders only after the product resolves, so child components and useBoot() never see a null product.
import { createFartherShoreClient } from "@farthershore/farthershore-js";
import {
FartherShoreRoot,
PlansTable,
UsageCard,
ApiKeysPanel,
BillingSummary,
} from "@farthershore/farthershore-js/components";
import "@farthershore/farthershore-js/components/styles.css";
const fs = createFartherShoreClient.fromEnv({
coreUrl: import.meta.env.VITE_FS_CORE_URL,
});
export default function CronCloudPortal() {
return (
<FartherShoreRoot client={fs}>
<main className="fs-stack">
<PlansTable />
<UsageCard />
<BillingSummary />
<ApiKeysPanel />
</main>
</FartherShoreRoot>
);
}
That is a complete, working portal for the CronCloud product — plans, live usage, subscription state, and API-key management, all driven by the product's contract.
<FartherShoreRoot
client={fs}
appearance={{ /* theme overrides; brand color applies automatically */ }}
clerk={{ publishableKey: "pk_test_…" }} // clerk-strategy envs (public value)
splash={<MySplash />} // while the product resolves
renderError={(error, retry) => <Failed error={error} onRetry={retry} />}
renderCrash={(error, reset) => <Crashed error={error} onReset={reset} />}
envBadge={true} // auto "Test mode" badge; opt-out only
>
{/* … */}
</FartherShoreRoot>
appearance overrides the theme; the product's branding.primaryColor is applied as the brand token automatically (explicit wins).clerk supplies the Clerk connection on clerk-strategy environments. Portals served through the edge get it from the injected config, so you usually omit it. See Auth & sessions.renderError handles a resolve failure (bootstrap fetch failed); retry re-runs the resolve without a full reload. renderCrash handles a render throw in the gated subtree; reset clears the boundary.envBadge mounts the automatic "Test mode" indicator on preview/test environments — opt out with false, never opt in. It renders nothing on production.<FartherShoreRoot> wraps <FartherShoreProvider> and adds the gate. If you only need the client in the tree (no gate/theme/auth), use the bare <FartherShoreProvider> instead.
useBoot()Inside the root, useBoot() returns the resolved Bootstrap (product, branding, environment, plans). It never suspends or returns null because children render only after resolve.
import { useBoot } from "@farthershore/farthershore-js/components";
function Header() {
const boot = useBoot();
return <h1>{boot.branding.displayName}</h1>;
}
Each owns one Core endpoint and renders the matching surface with zero props. They accept className and expose a typed <Name>Props interface for wrapping. Mount only the ones you want.
| Component | Renders |
|---|---|
<PlansTable/> | The product's plan catalog with prices, grants, and a subscribe/checkout CTA. |
<UsageCard/> | Live metered usage for the period (used / included per meter). |
<ApiKeysPanel/> | The subscriber's API keys with create / revoke / rotate. |
<BillingSummary/> | Subscription status, plan, renewal, and a billing-portal link. |
<CreditBalance/> | The prepaid/credit balance for the current subscription. |
<FeaturePanel/> | A Gateway feature-invoke box (set a key, call a route). |
<UpgradePrompt/> | Upsell CTA — compose with a LimitExceededError / FeatureNotGrantedError. |
// Zero-prop is the norm; the only props are optional overrides.
<UsageCard />
<UsageCard className="my-card" />
<PlansTable />
<ApiKeysPanel />
<BillingSummary />
<UsageCard/> renders a row per metered dimension the subscriber's plan defines — for CronCloud that's the requests meter its @Plan limits enforce (600/min on Starter). Its only props are className and activeCompiledPlanId (override the plan it reads from). For the "N of M" capability view (e.g. "7 of 10 cron jobs used"), mount <CapabilityUsageCard/> instead. See usage & metering and plans.
<FartherShoreRoot>; data via the SDK hooks. Props are optional overrides.className is appended to the root; every component exports <Name>Props.PlansTable, UsageCard); auth/chrome primitives carry the Fs prefix (FsSignIn, FsUserButton) to avoid collisions.--fs-* tokens in styles.css (import once). Rules live in @layer farthershore so your host CSS wins without specificity wars.To reveal a subtree only to signed-in users, wrap it in RequireAuth. To gate by plan capability or feature, use <FeatureGate> / <RequireCapability>. Both are managed and self-fed under <FartherShoreRoot>.
import { RequireAuth } from "@farthershore/farthershore-js/components";
<RequireAuth fallback={<p>Sign in to manage keys.</p>}>
<ApiKeysPanel />
</RequireAuth>
A @Product can author a @Frontend({ nav, pages }) manifest (CronCloud declares a /cron nav entry gated on the managed-cron capability). The managed template renders it, but a fork that replaces the template silently drops it. <FsManifestNav> is the opt-in fix — it maps the manifest's nav entries to links so the authored nav survives a fork with one import. Zero-prop, accessible, and renders nothing when no manifest was authored.
import { FsManifestNav } from "@farthershore/farthershore-js/components";
// Renders boot.frontendManifest.nav; swap in a router <Link> via linkComponent.
<FsManifestNav activePath={location.pathname} />;
RequireAuth.<FeatureGate> / <RequireCapability>.