Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion apps/web/content/docs/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,58 @@ PayKit is an embedded Stripe billing framework for TypeScript apps. Define plans

PayKit aims to be a complete billing framework. It provides a wide range of features out of the box and allows you to extend it with plugins.

<Features />
<Features>
<FeatureCard>
**Products in Code**

Define plans and features as typed primitives.
</FeatureCard>

<FeatureCard>
**Webhooks Handled**

Verified, deduplicated, synced to your database automatically.
</FeatureCard>

<FeatureCard>
**Usage Billing**

Metered features with check() and report().

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This card writes check() and report() as plain text, but every other docs page (entitlements.mdx, database.mdx, subscription-billing.mdx, skills.mdx) wraps these API names in backticks. Wrap them so the function names render as inline code in both the card and the generated Markdown.

**Usage Billing**

Metered features with `check()` and `report()`.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/content/docs/introduction.mdx, line 28:

<comment>This card writes `check()` and `report()` as plain text, but every other docs page (entitlements.mdx, database.mdx, subscription-billing.mdx, skills.mdx) wraps these API names in backticks. Wrap them so the function names render as inline code in both the card and the generated Markdown.
```md
**Usage Billing**

Metered features with `check()` and `report()`.
```</comment>

<file context>
@@ -9,4 +9,58 @@ PayKit is an embedded Stripe billing framework for TypeScript apps. Define plans
+<FeatureCard>
+**Usage Billing**
+
+Metered features with check() and report().
+</FeatureCard>
+
</file context>
Suggested change
Metered features with check() and report().
Metered features with `check()` and `report()`.

</FeatureCard>

<FeatureCard>
**Built For Stripe**

Stripe subscriptions, webhooks, portal, and product sync built in.
</FeatureCard>

<FeatureCard>
**Plugin Ecosystem**

Dashboard, analytics, or build your own plugin.
</FeatureCard>

<FeatureCard>
**Local Billing State**

Billing state in your Postgres, joinable with your tables.
</FeatureCard>

<FeatureCard>
**CLI**

Init, push, and status. Scaffold, migrate, validate.
</FeatureCard>

<FeatureCard>
**Client SDK**

Browser-side billing calls with full type inference.
</FeatureCard>

<FeatureCard>
**Type-safe**

Plan IDs, feature IDs, events — all inferred from your schema.
</FeatureCard>
</Features>
11 changes: 10 additions & 1 deletion apps/web/source.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ export const docs = defineDocs({
dir: "./content/docs",
docs: {
postprocess: {
includeProcessedMarkdown: true,
includeProcessedMarkdown: {
stringify(node, _parent, state, info) {
if (
node.type === "mdxJsxFlowElement" &&
(node.name === "Features" || node.name === "FeatureCard")
) {
return state.containerFlow(node, info);
}
},
},
},
},
});
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/docs/docs-mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
RiLinkM,
} from "react-icons/ri";

import { Features } from "@/components/docs/features";
import { FeatureCard, Features } from "@/components/docs/features";
import { MdxTab, MdxTabs, MdxTabsList, MdxTabsPanel, MdxTabsTab } from "@/components/docs/mdx-tabs";
import {
Anchor,
Expand Down Expand Up @@ -308,5 +308,6 @@ export const docsMdxComponents = {
TabsList: MdxTabsList,
TabsPanel: MdxTabsPanel,
TabsTab: MdxTabsTab,
FeatureCard,
Features,
} satisfies MDXComponents;
117 changes: 51 additions & 66 deletions apps/web/src/components/docs/features.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import type { ReactNode } from "react";
import { Children, isValidElement } from "react";
import {
RiBox3Line,
RiComputerLine,
Expand All @@ -13,75 +12,61 @@ import {
RiWebhookLine,
} from "react-icons/ri";

import { extractText } from "@/components/docs/react-node-text";
import { FrameCorners } from "@/components/ui/frame-corners";

const features: { icon: ReactNode; title: string; description: string }[] = [
{
icon: <RiBox3Line className="size-5" />,
title: "Products in Code",
description: "Define plans and features as typed primitives.",
},
{
icon: <RiWebhookLine className="size-5" />,
title: "Webhooks Handled",
description: "Verified, deduplicated, synced to your database automatically.",
},
{
icon: <RiSpeedUpLine className="size-5" />,
title: "Usage Billing",
description: "Metered features with check() and report().",
},
{
icon: <RiPlug2Line className="size-5" />,
title: "Built For Stripe",
description: "Stripe subscriptions, webhooks, portal, and product sync built in.",
},
{
icon: <RiPuzzle2Line className="size-5" />,
title: "Plugin Ecosystem",
description: "Dashboard, analytics, or build your own plugin.",
},
{
icon: <RiDatabase2Line className="size-5" />,
title: "Local Billing State",
description: "Billing state in your Postgres, joinable with your tables.",
},
{
icon: <RiTerminalBoxLine className="size-5" />,
title: "CLI",
description: "Init, push, and status. Scaffold, migrate, validate.",
},
{
icon: <RiComputerLine className="size-5" />,
title: "Client SDK",
description: "Browser-side billing calls with full type inference.",
},
{
icon: <RiShieldCheckLine className="size-5" />,
title: "Type-safe",
description: "Plan IDs, feature IDs, events — all inferred from your schema.",
},
];
const featureIcons: Record<string, typeof RiBox3Line> = {
"Products in Code": RiBox3Line,
"Webhooks Handled": RiWebhookLine,
"Usage Billing": RiSpeedUpLine,
"Built For Stripe": RiPlug2Line,
"Plugin Ecosystem": RiPuzzle2Line,
"Local Billing State": RiDatabase2Line,
CLI: RiTerminalBoxLine,
"Client SDK": RiComputerLine,
"Type-safe": RiShieldCheckLine,
};

export function Features({ children }: { children: ReactNode }) {
return (
<div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">{children}</div>
);
}

/** Renders a feature's Markdown title and description in the card layout. */
export function FeatureCard({ children }: { children: ReactNode }) {
const blocks = Children.toArray(children);
const [titleNode, descriptionNode] = blocks;

if (
blocks.length !== 2 ||
!isValidElement<{ children?: ReactNode }>(titleNode) ||
!isValidElement<{ children?: ReactNode }>(descriptionNode)
) {
throw new Error("FeatureCard needs a Markdown title and description paragraph.");
}

const title = extractText(titleNode.props.children).trim().replace(/\s+/g, " ");
const Icon = featureIcons[title];

if (!Icon || !extractText(descriptionNode.props.children).trim()) {
throw new Error(`FeatureCard has an unknown title or empty description: ${title}`);
}

export function Features() {
return (
<div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<div key={feature.title} className="relative h-full">
<FrameCorners radius="xs" />
<div className="group flex h-full flex-col gap-3 rounded-xs border border-border p-5 transition-colors hover:border-foreground/[0.08] hover:bg-foreground/[0.01]">
<span className="text-foreground/40 transition-colors group-hover:text-foreground/50">
{feature.icon}
</span>
<div className="flex flex-col gap-1">
<h3 className="!m-0 text-foreground/90 text-sm font-semibold">{feature.title}</h3>
<p className="!m-0 text-foreground/45 text-sm leading-relaxed">
{feature.description}
</p>
</div>
</div>
<div className="relative h-full">
<FrameCorners radius="xs" />
<div className="group flex h-full flex-col gap-3 rounded-xs border border-border p-5 transition-colors hover:border-foreground/[0.08] hover:bg-foreground/[0.01]">
<span className="text-foreground/40 transition-colors group-hover:text-foreground/50">
<Icon className="size-5" />
</span>
<div className="flex flex-col gap-1">
<h3 className="!m-0 text-foreground/90 text-sm font-semibold">{title}</h3>
<p className="!m-0 text-foreground/45 text-sm leading-relaxed">
{descriptionNode.props.children}
</p>
</div>
))}
</div>
</div>
);
}
Loading