Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
88bbd11
feat: add pymthouse plans list + subscribe checkout on Usage
eliteprox Aug 6, 2026
98a446e
chore(deps): pin @pymthouse/builder-sdk to published 0.6.2
eliteprox Aug 6, 2026
a7faf61
merge: sync with pymthouse-integration and pin SDK 0.6.3
eliteprox Aug 6, 2026
6ec9fc5
Wire dashboard payment methods and Connect invoices
eliteprox Aug 7, 2026
f09522d
Add billing payment-method row actions and Connect invoice copy.
eliteprox Aug 7, 2026
19deb57
feat(billing): route subscriptions by live state
eliteprox Aug 7, 2026
bbb8385
fix(usage): validate account-usage payloads instead of casting them
eliteprox Aug 8, 2026
c4a5c1f
refactor(keys): resolve the signer URL from issuer routing, not dashb…
eliteprox Aug 8, 2026
59b954a
fix(billing): gate cancel affordances on the live plan, add owner wallet
eliteprox Aug 8, 2026
f810872
feat(billing): drive wallet UI from billingState spend posture
eliteprox Aug 9, 2026
68f2b6c
feat(theme): default to system color scheme (#9)
adamsoffer Aug 9, 2026
bc55042
refactor(auth): update user ID handling and async methods in AuthContext
eliteprox Aug 11, 2026
6d4b24a
feat(billing): enhance billing plan handling and UI updates
eliteprox Aug 11, 2026
27e2718
feat(subscriptions): implement user subscription retrieval API and en…
eliteprox Aug 11, 2026
3189cd1
chore(dependencies): update @pymthouse/builder-sdk to version 0.6.4
eliteprox Aug 11, 2026
f3ee548
Rename the dashboard to the console throughout (#10)
adamsoffer Aug 12, 2026
0d19290
fix: point the Discord link at the current invite URL (#11)
rickstaa Aug 18, 2026
777fdf7
feat(wallet): enhance available runway calculation and add spendable …
eliteprox Aug 19, 2026
59ac765
feat(wallet): integrate wallet billing state and enhance usage display
eliteprox Aug 19, 2026
fc37064
Hide the Network nav group; land signed-out visitors on /login (#12)
adamsoffer Aug 24, 2026
75f5dc7
Update README
adamsoffer Aug 24, 2026
aa17564
Merge branch 'main' into feat/pymthouse-plans-subscribe
eliteprox Aug 24, 2026
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
16 changes: 7 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ THEGRAPH_API_KEY=
# Aliases: DISCOVERY_URL, LIVEPEER_DISCOVERY_SERVICE_URL
DISCOVERY_SERVICE_URL=https://discovery-service-production-8955.up.railway.app/v1/discovery/raw

# PymtHouse (usage + API keys + signer sessions)
# PymtHouse (usage + API keys + signer sessions + plans/subscribe)
# Production:
PYMTHOUSE_ISSUER_URL=https://pymthouse.com/api/v1/oidc
# Local pymthouse: http://localhost:3001/api/v1/oidc
# Public app client id (OAuth client_id) — used for Builder API path {clientId}
# Local pymthouse (`npm run dev` HTTPS): https://localhost:3001/api/v1/oidc
# (mkcert is trusted in the browser; Node fetch to localhost HTTPS usually works
# without PYMTHOUSE_ALLOW_INSECURE_HTTP when using the system/mkcert CA.)
PYMTHOUSE_PUBLIC_CLIENT_ID=
PYMTHOUSE_M2M_CLIENT_ID=
PYMTHOUSE_M2M_CLIENT_SECRET=
# Remote signer DMZ base included as signer_url in API-key exchange responses
# (fallback when the issuer does not return signer_url).
# Production: https://pymthouse-production.up.railway.app
# Local compose: http://127.0.0.1:8080
PYMTHOUSE_SIGNER_URL=https://pymthouse-production.up.railway.app
# Set to 1 for local dev when issuer uses http://127.0.0.1
# Signer DMZ URL is returned by issuer exchange / GET …/apps/{id}/signer/routing
# — do not set PYMTHOUSE_SIGNER_URL on the dashboard.
# Set to 1 for local http issuer only (not needed for https://localhost with mkcert)
PYMTHOUSE_ALLOW_INSECURE_HTTP=

# Live-runner discovery (orchestrator /discovery endpoint).
Expand Down
36 changes: 19 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,31 @@
```
app/
├── layout.tsx # Root layout — html/body, Geist fonts, theme bootstrap (FOUT-safe)
├── globals.css # Tailwind v4 @theme block + dashboard token layer
├── (app)/ # Dashboard chrome (sidebar, providers, keyboard shortcuts)
├── globals.css # Tailwind v4 @theme block + console token layer
├── (app)/ # Console chrome (sidebar, providers, keyboard shortcuts)
│ ├── layout.tsx
│ ├── page.tsx # / → Explore (public)
│ ├── home/page.tsx # /home → Dashboard home (auth-gated via SignInWall)
│ ├── jobs, usage, keys, settings # auth-gated
│ ├── models/[id] # public
│ ├── page.tsx # / → Explore (public; → /home when signed in)
│ ├── explore/page.tsx # /explore → Explore (public)
│ ├── home/page.tsx # /home → Console home (auth-gated via SignInWall)
│ ├── calls, usage, keys, settings # auth-gated
│ ├── apps/[id] # public
│ ├── orgs/[slug] # public
│ ├── network # public
│ └── error.tsx
├── (auth)/ # /login, /signup — no sidebar
└── not-found.tsx

components/
├── dashboard/ # Dashboard surfaces, providers (Auth, Theme), nav, charts, tables
├── console/ # Console surfaces, providers (Auth, Theme), nav, charts, tables
└── design-system/ # Vendored primitives — Badge, Button, Dialog, Drawer,
# ErrorState, Select, Skeleton, Tooltip, LivepeerLogo.
# TEMPORARY — replace with @livepeer/design-system when published.

lib/
├── dashboard/ # mock-data, types, utils, useStarredModels, model-stats, generate-card-visual
├── console/ # mock-data, types, utils, useStarredModels, model-stats, generate-card-visual
└── constants.ts # PORTAL_NAV_ITEMS, EXTERNAL_LINKS

public/images/dashboard/ # Model card visuals + daydream logo
public/images/console/ # Model card visuals + daydream logo
```

## Conventions
Expand All @@ -48,11 +50,11 @@ The Home (`/home`) shows `<FirstRunChecklist>` for any signed-in user where `loc

### Section headings

Use `<SectionHeader>` from `components/dashboard/SectionHeader.tsx`. Dashboard convention is `text-base font-semibold` headings; do not roll ad-hoc `<h2 className="text-base font-semibold">` markup.
Use `<SectionHeader>` from `components/console/SectionHeader.tsx`. Console convention is `text-base font-semibold` headings; do not roll ad-hoc `<h2 className="text-base font-semibold">` markup.

### KPI rows

Wrap any row of `<KpiCard>` / `<StatCard>` in `<KpiStrip cols={3 | 4}>` (`components/dashboard/KpiStrip.tsx`). Don't roll ad-hoc `grid grid-cols-2 sm:grid-cols-4` containers — they drift over time.
Wrap any row of `<KpiCard>` / `<StatCard>` in `<KpiStrip cols={3 | 4}>` (`components/console/KpiStrip.tsx`). Don't roll ad-hoc `grid grid-cols-2 sm:grid-cols-4` containers — they drift over time.

### Page max-widths

Expand All @@ -62,23 +64,23 @@ Each page picks one based on content type and uses it for every inner max-width
- `max-w-6xl` — data tables / charts (Network, Usage)
- `max-w-7xl` — catalogs / dense grids (Explore, Models list)

There's no shared `<DashboardPage>` wrapper because every page layers a sticky `<TabStrip>` between header and content, requiring the max-width to repeat 2-3 times intentionally per page.
There's no shared `<ConsolePage>` wrapper because every page layers a sticky `<TabStrip>` between header and content, requiring the max-width to repeat 2-3 times intentionally per page.

### Cards

Dashboard surfaces use inline classes — typically `rounded-xl border border-hairline bg-{dark-surface|dark-card|transparent}` with `p-4` (data-dense rows) or `p-6` (feature blocks).
Console surfaces use inline classes — typically `rounded-xl border border-hairline bg-{dark-surface|dark-card|transparent}` with `p-4` (data-dense rows) or `p-6` (feature blocks).

### Tables

Tables on the dashboard are intentionally bespoke — Home "Your runs", `UsageTab` activity log, and `PaymentTab` connected-providers all roll their own markup because their interaction patterns differ (sticky mobile/desktop headers, live pulse on most-recent row, highlighted target row, etc.). There is no shared `<DataTable>` primitive. If a future surface needs a generic sortable table, build it then — don't try to back-fit a primitive that compromises the existing surfaces.
Tables in the console are intentionally bespoke — Home "Your runs", `UsageTab` activity log, and `PaymentTab` connected-providers all roll their own markup because their interaction patterns differ (sticky mobile/desktop headers, live pulse on most-recent row, highlighted target row, etc.). There is no shared `<DataTable>` primitive. If a future surface needs a generic sortable table, build it then — don't try to back-fit a primitive that compromises the existing surfaces.

### Form-control focus ring

All form controls (`SearchInput`, `Select`, etc.) show `focus-visible:ring-1 focus-visible:ring-green-bright/30`. Don't ship border-only focus states.

### Loading + error states

Suspense boundaries on every dashboard route use `<DashboardPageSkeleton>` as the fallback (`components/dashboard/DashboardPageSkeleton.tsx`). The `(app)/error.tsx` segment-level boundary renders `<ErrorState>` (`components/design-system/ErrorState.tsx`) for any thrown render error, with a request ID + retry + Discord help link.
Suspense boundaries on every console route use `<ConsolePageSkeleton>` as the fallback (`components/console/ConsolePageSkeleton.tsx`). The `(app)/error.tsx` segment-level boundary renders `<ErrorState>` (`components/design-system/ErrorState.tsx`) for any thrown render error, with a request ID + retry + Discord help link.

### Color / token rules

Expand All @@ -102,11 +104,11 @@ Existing tokens in `globals.css` `@theme`: `--motion-duration-fast` (150ms, hove

## Design system — vendored, not final

`components/design-system/` is **vendored**. When `@livepeer/design-system` ships as a real package, swap the imports — keep `components/design-system/` as a barrel that re-exports from the package, or delete it entirely. The 38+ dashboard components import from `@/components/design-system/X`, so the swap-out is a single barrel-file change rather than a 38-file rewrite.
`components/design-system/` is **vendored**. When `@livepeer/design-system` ships as a real package, swap the imports — keep `components/design-system/` as a barrel that re-exports from the package, or delete it entirely. The 38+ console components import from `@/components/design-system/X`, so the swap-out is a single barrel-file change rather than a 38-file rewrite.

## Don't

- **No `next/image`** — use raw `<img>` tags. Some downstream primitives need direct CSS filter/absolute stacking that `next/image`'s wrapper breaks.
- **No global state** — local `useState` only. No state libraries.
- **No backend dependencies** — all data is mock (`lib/dashboard/mock-data.ts`). When the real API lands, replace the mock imports surgically; don't restructure the components around the data layer.
- **No backend dependencies** — all data is mock (`lib/console/mock-data.ts`). When the real API lands, replace the mock imports surgically; don't restructure the components around the data layer.
- **No new dependencies without discussion.** The dependency list is intentionally small.
50 changes: 27 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Livepeer Developer Dashboard
# Livepeer Console

The signed-in surface for developers using the Livepeer network — browse AI capabilities, manage API keys, monitor usage.
The signed-in surface for developers using the Livepeer network — manage API keys, monitor usage.

## Status

Early development. All data is mock-driven (`lib/dashboard/mock-data.ts`) — there is no backend wired in yet. Auth is stubbed in `components/dashboard/AuthContext.tsx`.
Early development. All data is mock-driven (`lib/console/mock-data.ts`) — there is no backend wired in yet. Auth is stubbed in `components/console/AuthContext.tsx`.

This repo was extracted from [`livepeer/website`](https://github.com/livepeer/website) (branch `claude/dashboard-updates`) and ships independently from the marketing site.

Expand All @@ -31,41 +31,45 @@ pnpm typecheck # tsc --noEmit
```
app/
├── layout.tsx # Root: html/body, Geist fonts, theme bootstrap
├── globals.css # Token layer + dashboard utilities
├── (app)/ # Dashboard chrome (sidebar, providers)
├── globals.css # Token layer + console utilities
├── (app)/ # Console chrome (sidebar, providers)
│ ├── layout.tsx
│ ├── page.tsx # / → Explore (public)
│ ├── page.tsx # / → Explore (public; → /home when signed in)
│ ├── explore/ # /explore (public)
│ ├── home/ # /home (auth-gated)
│ ├── jobs, usage, keys, settings # auth-gated
│ ├── models/[id] # public model detail
│ ├── calls, usage, keys, settings # auth-gated
│ ├── apps/[id] # public app detail + playground
│ ├── orgs/[slug] # public org profile
│ └── network # public network stats
└── (auth)/ # Login + signup (no sidebar)

components/
├── dashboard/ # All dashboard surfaces
├── console/ # All console surfaces
└── design-system/ # Vendored primitives — Badge, Button, Dialog,
# Drawer, ErrorState, Select, Skeleton, Tooltip,
# LivepeerLogo. Replace with @livepeer/design-system
# when that package ships.

lib/
├── dashboard/ # Mock data, types, utils
├── console/ # Mock data, types, utils
└── constants.ts # PORTAL_NAV_ITEMS + EXTERNAL_LINKS
```

## Routes

| URL | Auth | Surface |
| ------------------ | -------- | -------------------------------- |
| `/` | public | Explore — model catalog |
| `/home` | required | Dashboard home (your runs / KPIs) |
| `/jobs` | required | Run history |
| `/usage` | required | Account usage |
| `/keys` | required | API keys |
| `/settings` | required | Account settings |
| `/models/[id]` | public | Model detail + playground |
| `/network` | public | Network stats |
| `/login` | public | Sign in |
| `/signup` | public | Sign up |
| URL | Auth | Surface |
| -------------- | -------- | --------------------------------------------- |
| `/` | public | Explore — redirects to `/home` when signed in |
| `/explore` | public | Explore — app catalog |
| `/home` | required | Console home (your runs / KPIs) |
| `/calls` | required | Call history |
| `/usage` | required | Account usage |
| `/keys` | required | API keys |
| `/settings` | required | Account settings |
| `/apps/[id]` | public | App detail + playground |
| `/orgs/[slug]` | public | Organization's published apps |
| `/network` | public | Network stats (sidebar: "Stats") |
| `/login` | public | Sign in |
| `/signup` | public | Sign up |

See `CLAUDE.md` for dashboard conventions (KPI rows, tables, motion tokens, color rules).
See `CLAUDE.md` for console conventions (KPI rows, tables, motion tokens, color rules).
48 changes: 24 additions & 24 deletions app/(app)/apps/[...id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,44 @@ import {
ArrowUpRight,
Settings as SettingsIcon,
} from "lucide-react";
import { useAuth } from "@/components/dashboard/AuthContext";
import DashboardSubNav from "@/components/dashboard/DashboardSubNav";
import CostTag from "@/components/dashboard/CostTag";
import KeyBadge from "@/components/dashboard/KeyBadge";
import CallsTable from "@/components/dashboard/CallsTable";
import StatusDot from "@/components/dashboard/StatusDot";
import { useAuth } from "@/components/console/AuthContext";
import ConsoleSubNav from "@/components/console/ConsoleSubNav";
import CostTag from "@/components/console/CostTag";
import KeyBadge from "@/components/console/KeyBadge";
import CallsTable from "@/components/console/CallsTable";
import StatusDot from "@/components/console/StatusDot";
import {
effectiveVisibility,
setPipelineVisibility,
organizationSlug,
PIPELINE_APP_IDS,
SETTINGS_API_KEYS,
MOCK_RECENT_REQUESTS,
} from "@/lib/dashboard/mock-data";
import { useDiscoveryModel } from "@/lib/dashboard/useDiscoveryModel";
import DashboardPageSkeleton from "@/components/dashboard/DashboardPageSkeleton";
import { getAppIcon } from "@/lib/dashboard/utils";
import PlaygroundForm from "@/components/dashboard/playground/PlaygroundForm";
import JsonInput from "@/components/dashboard/playground/JsonInput";
import PlaygroundOutput from "@/components/dashboard/playground/PlaygroundOutput";
import TranscodingOutput from "@/components/dashboard/playground/TranscodingOutput";
import CodeSnippets from "@/components/dashboard/playground/CodeSnippets";
import WebcamPlayground from "@/components/dashboard/playground/WebcamPlayground";
} from "@/lib/console/mock-data";
import { useDiscoveryModel } from "@/lib/console/useDiscoveryModel";
import ConsolePageSkeleton from "@/components/console/ConsolePageSkeleton";
import { getAppIcon } from "@/lib/console/utils";
import PlaygroundForm from "@/components/console/playground/PlaygroundForm";
import JsonInput from "@/components/console/playground/JsonInput";
import PlaygroundOutput from "@/components/console/playground/PlaygroundOutput";
import TranscodingOutput from "@/components/console/playground/TranscodingOutput";
import CodeSnippets from "@/components/console/playground/CodeSnippets";
import WebcamPlayground from "@/components/console/playground/WebcamPlayground";
import {
RunnerGatewayProvider,
useRunnerGatewayContext,
} from "@/components/dashboard/playground/RunnerGatewayContext";
} from "@/components/console/playground/RunnerGatewayContext";
import {
buildLiveRunnerPayload,
extractRunnerResultText,
runnerGatewayPostUrl,
} from "@/lib/dashboard/runner-gateway-client";
import AppAnalytics from "@/components/dashboard/stats/AppAnalytics";
} from "@/lib/console/runner-gateway-client";
import AppAnalytics from "@/components/console/stats/AppAnalytics";
import {
OverviewTab,
SettingsTab,
} from "@/components/dashboard/AppDetailView";
import type { App, Pipeline, PipelineVisibility } from "@/lib/dashboard/types";
} from "@/components/console/AppDetailView";
import type { App, Pipeline, PipelineVisibility } from "@/lib/console/types";

// ─── Tabs ───
//
Expand Down Expand Up @@ -695,7 +695,7 @@ function JobsTab({
runs,
}: {
model: App;
runs: import("@/lib/dashboard/types").AccountActivityRow[];
runs: import("@/lib/console/types").AccountActivityRow[];
}) {
if (runs.length === 0) {
return (
Expand Down Expand Up @@ -794,7 +794,7 @@ export default function AppDetailPage() {
if (discovery.status === "loading") {
return (
<main id="main-content" className="flex flex-1 flex-col bg-dark">
<DashboardPageSkeleton maxWidth="5xl" withTabs kpiCount={0} withChart={false} />
<ConsolePageSkeleton maxWidth="5xl" withTabs kpiCount={0} withChart={false} />
</main>
);
}
Expand Down Expand Up @@ -1037,7 +1037,7 @@ export default function AppDetailPage() {
</div>

{/* Tabs — mobile scroll strip */}
<DashboardSubNav
<ConsoleSubNav
hideAt="md"
ariaLabel="App section"
tabs={tabs}
Expand Down
6 changes: 3 additions & 3 deletions app/(app)/calls/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import CallsView from "@/components/dashboard/CallsView";
import SignInWall from "@/components/dashboard/SignInWall";
import { useAuth } from "@/components/dashboard/AuthContext";
import CallsView from "@/components/console/CallsView";
import SignInWall from "@/components/console/SignInWall";
import { useAuth } from "@/components/console/AuthContext";

// Note: page metadata isn't valid in client components, so the previous
// `metadata` export moves out alongside this auth gate. Title/description for
Expand Down
8 changes: 4 additions & 4 deletions app/(app)/error.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

// Next.js App Router error boundary for the entire (dashboard) segment.
// Next.js App Router error boundary for the entire (app) segment.
// Catches render errors from any /* route and surfaces a recoverable
// ErrorState with a retry affordance + request ID instead of a blank panel.

import { useEffect } from "react";
import ErrorState from "@/components/design-system/ErrorState";

export default function DashboardError({
export default function ConsoleError({
error,
reset,
}: {
Expand All @@ -16,15 +16,15 @@ export default function DashboardError({
}) {
useEffect(() => {
// Surface to console for now. When real telemetry lands, route to it here.
console.error("[dashboard] render error:", error);
console.error("[console] render error:", error);
}, [error]);

return (
<main className="flex flex-1 flex-col bg-dark">
<div className="mx-auto w-full max-w-3xl px-5 pt-10 pb-16 lg:px-8">
<ErrorState
title="This page hit an error"
description="The dashboard view crashed while rendering. Try again, or include the request ID below if you reach out for help."
description="The console view crashed while rendering. Try again, or include the request ID below if you reach out for help."
requestId={error.digest}
onRetry={reset}
/>
Expand Down
Loading