All pages

Admin

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

Matches
3
Offers held
2
Invites delivered
1 of 2

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>
  )
}

UserSummaryCard

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} />
}

EntitlementsCard

Entitlement

boilerplatelifetime-updatesdemo-priority-support
  • 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} />
}

BillingHistoryCard

Billing history

  • demo-supportActive
    Subscription · Aug 2, 2026$49.00
    price_1UBAPn1DJWXEccvOw3Z1dOjV
  • proPaid
    One-time payment · Jul 14, 2026$169.00
    price_1PED7uP4McnT55wKre6G3Rjd
  • Unknown offerCanceled
    Subscription · May 30, 2026$9.00
    price_1T9xQd1DJWXEccvOa2LmPq7R
  • proPaid
    One-time payment · Apr 11, 2026$169.00
    price_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} />
}

FulfillmentList

Fulfillment

ada
1 attempt
Delivered
ada-lovelacce
3 attempts
Failed

Not Found: ada-lovelacce is not a GitHub account.

No GitHub handle recorded
0 attempts
Pending

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/.

Updated