The rows below are fixtures written into each demo file, not a live account. What the real surface passes comes from GET /admin/users/:id, and the api enforces the admin allowlist on that call.
StatsCard
One number with a label. These count what is on the page rather than what is in the database.
src/lib/docs/showcase/stats-card.tsx
import { StatsCard } from '@/components/Admin/StatsCard/StatsCard'
export function StatsCardDemo() {
return (
<div className="flex flex-wrap gap-10">
<StatsCard title="Matches" value={3} />
<StatsCard title="Offers held" value={2} />
<StatsCard title="Invites delivered" value="1 of 2" />
</div>
)
}User
- Name:
- Ada Lovelace
- Email:
- ada@example.com
- Clerk id:
- user_2pQ7hK3mVxYt1aB9cD0eF4gH5i
- User id:
- 3f6a2c18-9b41-4d0e-9a7f-2c85d1e4b730
- Created:
- Jul 14, 2026
Who a row is. There is no edit and no delete: nothing writes what someone is entitled to, and refunding in Stripe is the only revoke control.
src/lib/docs/showcase/user-summary-card.tsx
import type { AdminUserSummary } from '@ffd/shared'
import { UserSummaryCard } from '@/components/Admin/UserSummaryCard/UserSummaryCard'
const USER: AdminUserSummary = {
id: '3f6a2c18-9b41-4d0e-9a7f-2c85d1e4b730',
clerkUserId: 'user_2pQ7hK3mVxYt1aB9cD0eF4gH5i',
email: 'ada@example.com',
displayName: 'Ada Lovelace',
createdAt: '2026-07-14T09:12:04.000Z',
}
export function UserSummaryCardDemo() {
return <UserSummaryCard user={USER} />
}Entitlement
- proOne-time payment · since Jul 14, 2026
- demo-supportSubscription · since Aug 2, 2026
What a user may do and what they paid for it to be true. Features and offers are two lists because two offers can grant the same feature.
src/lib/docs/showcase/entitlements-card.tsx
import type { EntitlementsResponse } from '@ffd/shared'
import { EntitlementsCard } from '@/components/Admin/EntitlementsCard/EntitlementsCard'
// Three features from two offers, because two offers can grant the same
// feature and the counts are not meant to match.
const ENTITLEMENTS: EntitlementsResponse = {
features: ['boilerplate', 'lifetime-updates', 'demo-priority-support'],
offers: [
{ slug: 'pro', source: 'purchase', since: '2026-07-14T09:31:22.000Z' },
{
slug: 'demo-support',
source: 'subscription',
since: '2026-08-02T11:04:51.000Z',
},
],
}
export function EntitlementsCardDemo() {
return <EntitlementsCard entitlements={ENTITLEMENTS} />
}Billing history
- demo-supportActiveSubscription · Aug 2, 2026$49.00price_1UBAPn1DJWXEccvOw3Z1dOjV
- proPaidOne-time payment · Jul 14, 2026$169.00price_1PED7uP4McnT55wKre6G3Rjd
- Unknown offerCanceledSubscription · May 30, 2026$9.00price_1T9xQd1DJWXEccvOa2LmPq7R
- proPaidOne-time payment · Apr 11, 2026$169.00price_1PED7uP4McnT55wKre6G3Rjd$169.00 refunded on Apr 18, 2026
Every paid Stripe object recorded against a user, newest first, as one chronological list with a kind on each row. A refunded purchase says so, because it is the row that explains why access ended.
src/lib/docs/showcase/billing-history-card.tsx
import type { BillingEntry } from '@ffd/shared'
import { BillingHistoryCard } from '@/components/Admin/BillingHistoryCard/BillingHistoryCard'
// The third row is a price that has left the catalog, which is why its slug is
// null. The payment still happened, so the row still shows. The fourth is a
// fully refunded purchase, which is the one shape that no longer grants.
const ENTRIES: BillingEntry[] = [
{
id: 'sub_1UBAPn1DJWXEccvOw3Z1dOjV',
kind: 'subscription',
slug: 'demo-support',
stripePriceId: 'price_1UBAPn1DJWXEccvOw3Z1dOjV',
amount: 4_900,
currency: 'usd',
status: 'active',
occurredAt: '2026-08-02T11:04:51.000Z',
refundedAt: null,
amountRefunded: 0,
},
{
id: 'pi_3PEDaBP4McnT55wK1kQvJm2X',
kind: 'purchase',
slug: 'pro',
stripePriceId: 'price_1PED7uP4McnT55wKre6G3Rjd',
amount: 16_900,
currency: 'usd',
status: 'paid',
occurredAt: '2026-07-14T09:31:22.000Z',
refundedAt: null,
amountRefunded: 0,
},
{
id: 'sub_1T9xQd1DJWXEccvOa2LmPq7R',
kind: 'subscription',
slug: null,
stripePriceId: 'price_1T9xQd1DJWXEccvOa2LmPq7R',
amount: 900,
currency: 'usd',
status: 'canceled',
occurredAt: '2026-05-30T16:47:09.000Z',
refundedAt: null,
amountRefunded: 0,
},
{
id: 'pi_3NxKwQP4McnT55wK0aBcDe4F',
kind: 'purchase',
slug: 'pro',
stripePriceId: 'price_1PED7uP4McnT55wKre6G3Rjd',
amount: 16_900,
currency: 'usd',
status: 'paid',
occurredAt: '2026-04-11T08:15:03.000Z',
refundedAt: '2026-04-18T13:22:40.000Z',
amountRefunded: 16_900,
},
]
export function BillingHistoryCardDemo() {
return <BillingHistoryCard entries={ENTRIES} />
}Fulfillment
Not Found: ada-lovelacce is not a GitHub account.
The invite panel, and the only admin control that writes anything. The glyphs are the four states of the atomic claim. Re-send is live and posts to the api, which refuses it unless you are an administrator.
src/lib/docs/showcase/fulfillment-list.tsx
import type { AdminFulfillmentStatus } from '@ffd/shared'
import { FulfillmentList } from '@/components/Admin/FulfillmentList/FulfillmentList'
// Three of the four claim states. `lastError` is GitHub's own prose about a
// handle that does not exist, and it is admin-only. `attemptedAt` is null on the
// `pending` row because nothing has claimed it yet.
const FULFILLMENTS: AdminFulfillmentStatus[] = [
{
purchaseId: '0c94f1a7-52d8-4c33-8b6e-71a0f5c2d914',
githubHandle: 'ada',
state: 'delivered',
attempts: 1,
cooldownUntil: null,
attemptedAt: '2026-09-11T09:14:02.000Z',
lastError: null,
},
{
purchaseId: '6d21b830-9f4c-4a17-b0d5-3e8c7a45f261',
githubHandle: 'ada-lovelacce',
state: 'failed',
attempts: 3,
cooldownUntil: null,
attemptedAt: '2026-09-11T09:41:37.000Z',
lastError: 'Not Found: ada-lovelacce is not a GitHub account.',
},
{
purchaseId: 'b7e5c042-1d63-4f89-9c2a-84fb60d3e517',
githubHandle: null,
state: 'pending',
attempts: 0,
cooldownUntil: null,
attemptedAt: null,
lastError: null,
},
]
export function FulfillmentListDemo() {
return (
<FulfillmentList
userId="3f6a2c18-9b41-4d0e-9a7f-2c85d1e4b730"
fulfillments={FULFILLMENTS}
/>
)
}Not shown here
UserLookup, AttentionFulfillments, RequeueButton, and AdminGate paint from a live query or from the Clerk session, so what you saw in a frame would depend on whether you happen to be signed in. Read them at src/components/Admin/.