@geenius/pricing
v0.17.0
Published
Geenius Pricing — SaaS pricing page components and plan management (React + SolidJS)
Readme
@geenius/pricing
Production-ready FSL pricing plans, comparison tables, billing controls, and admin helpers for Geenius apps.
Installation
pnpm add @geenius/pricingImports
import { DEFAULT_PRICING, configurePricing } from "@geenius/pricing";
import { DEFAULT_SAAS_PRICING, sortPlansByOrder } from "@geenius/pricing";
import { PricingTable, PricingPage, usePricing } from "@geenius/pricing/react";
import { PricingTable as CssPricingTable, cx } from "@geenius/pricing/react-css";
import "@geenius/pricing/react-css/styles.css";
import { createPricing, PricingPage as SolidPricingPage } from "@geenius/pricing/solidjs";
import { PricingTable as SolidCssPricingTable } from "@geenius/pricing/solidjs-css";
import "@geenius/pricing/solidjs-css/styles.css";
import { createConvexPricingProvider, schema } from "@geenius/pricing/convex";
import { createNeonPricingProvider } from "@geenius/pricing/neon";
import { createCloudflareKVPricingProvider } from "@geenius/pricing/cloudflareKV";
import { createMemoryPricingProvider } from "@geenius/pricing/memory";Variant class helpers are intentionally local. @geenius/pricing/react-css
exports cx for composing its package-scoped BEM classes, while the React Tailwind
variant keeps cn internal and does not export it as portable API.
The CSS variants keep .gn-pricing-* class names for selector isolation, but
their styles consume the unprefixed --gn-* design tokens from
@geenius/tokens.
Usage
Direct (props-driven)
import { PricingTable } from "@geenius/pricing/react";
const plans = [
{
id: "starter",
name: "Starter",
tier: "starter",
tagline: "For the first production workflow.",
monthlyPrice: 2900,
yearlyPrice: 27840,
currency: "USD",
features: [
{ id: "projects", name: "Projects", availability: "included", value: "3 active projects" },
],
limits: { users: 5, projects: 3 },
order: 0,
active: true,
},
];
export function PricingRoute() {
return (
<PricingTable
plans={plans}
currentPlan="starter"
onSelect={({ plan, interval, price }) =>
console.info("upgrade", plan.id, interval, price)
}
/>
);
}Provider-backed (recommended for full pages)
import { PricingProvider, PricingPage, usePricing } from "@geenius/pricing/react";
import { DEFAULT_SAAS_PRICING } from "@geenius/pricing";
export function App() {
return (
<PricingProvider initialPlans={DEFAULT_SAAS_PRICING} initialInterval="yearly">
<PricingPage onSelect={({ plan, interval }) => startCheckout(plan.id, interval)} />
</PricingProvider>
);
}React + Vanilla CSS
import { useState } from "react";
import { DEFAULT_SAAS_PRICING } from "@geenius/pricing";
import { PricingTable, cx } from "@geenius/pricing/react-css";
import "@geenius/pricing/react-css/styles.css";
export function CssPricingRoute() {
const [interval, setInterval] = useState<"monthly" | "yearly">("monthly");
return (
<PricingTable
className={cx("pricing-shell", "pricing-shell--css")}
plans={DEFAULT_SAAS_PRICING}
interval={interval}
onIntervalChange={setInterval}
showComparison
currentPlan="starter"
onSelect={({ plan }) => console.info("upgrade", plan.id)}
/>
);
}DB provider wiring
import { DEFAULT_SAAS_PRICING } from "@geenius/pricing";
import { createConvexPricingProvider } from "@geenius/pricing/convex";
import { createNeonPricingProvider, applyPricingMigrations } from "@geenius/pricing/neon";
import {
createCloudflareKVPricingProvider,
createMemoryKVNamespace,
} from "@geenius/pricing/cloudflareKV";
import { createMemoryPricingProvider } from "@geenius/pricing/memory";
const memoryProvider = createMemoryPricingProvider({ plans: DEFAULT_SAAS_PRICING });
const convexProvider = createConvexPricingProvider({ client: convexClient });
const neonProvider = createNeonPricingProvider({ executor });
await applyPricingMigrations(executor);
const kvProvider = createCloudflareKVPricingProvider({
namespace: createMemoryKVNamespace(),
});Public surface map
| Surface | Import |
| --- | --- |
| Shared types/utilities | @geenius/pricing |
| React (Tailwind, default) | @geenius/pricing/react |
| React (vanilla CSS) | @geenius/pricing/react-css + @geenius/pricing/react-css/styles.css |
| SolidJS | @geenius/pricing/solidjs |
| SolidJS (vanilla CSS) | @geenius/pricing/solidjs-css + @geenius/pricing/solidjs-css/styles.css |
| DB providers | @geenius/pricing/{convex,neon,cloudflareKV,memory} |
Themed React, React Native, and themed SolidJS variants may appear in package
metadata, but they are deferred until their variants.json entries switch to
inScope: true. The current supported matrix is the four UI surfaces listed
above plus the four DB providers.
The canonical Cloudflare KV consumer subpath is
@geenius/pricing/cloudflareKV. Private workspace metadata may use
@geenius/pricing-cloudflare-kv, but application imports and docs should not.
See .docs/DOCS/PACKAGES/PRICING.md for full component / hook / token reference.
Storybook
Pricing follows the V2 Storybook matrix: one stock Storybook v10 app per UI
variant under apps/storybook-<variant>/. Use the package scripts to exercise
the full app set instead of running only the reference React and Solid apps:
pnpm run test:storybook:build
pnpm run test:storybookThe CSS variants also publish explicit stylesheet entrypoints at
@geenius/pricing/react-css/styles.css and
@geenius/pricing/solidjs-css/styles.css.
Testing
The package gauntlet is driven by variants.json. Scripts that need package, Storybook, Playwright, size, or coverage fanout read that manifest instead of maintaining their own variant lists.
pnpm test:gauntletruns the PR-blocking source checks, type checks, package tests, publishing checks, size budgets, supply-chain audit, and license check.pnpm test:allextends the gauntlet with Storybook, db conformance, e2e, accessibility, visual, performance, and coverage layers.pnpm test:visualchecks the Chromium visual baselines for the in-scope UI variants.pnpm test:mutationruns the Stryker mutation pass for pure logic and is intended for the slower scheduled lane.
Contributing tests
Add or update variants in variants.json first, then add the variant source package, Storybook app, and e2e coverage that the manifest points to. Do not hardcode variant arrays in scripts, Playwright config, package scripts, or tests; import scripts/_variants.mjs or use the existing helper scripts.
When adding behavior, cover the nearest subpackage unit tests first, then add root parity or packed-smoke assertions if the change affects public exports, subpaths, or variant alignment.
License
FSL-1.1-Apache-2.0. See LICENSE.
