Admin kit
What this is: @platform/admin-kit — the primitives layer that every admin UI on Loop is built from. Components, hooks, theme tokens, auth context. Sits on top of @platform/sdk-*.
Who it’s for: anyone building an admin surface — internal teams porting from legacy admins, partners building their own admins on top of Loop’s SDKs, future you maintaining what’s already shipped.
What to read next: Auth model, Audit & PHI, Brands & multi-tenancy.
Why a shared kit
Admin panels rot. Without shared primitives, every page has its own loading state, its own error handling, its own data-fetching pattern, its own brand picker, its own audit viewer. You end up with five subtly-different admins across the platform — each one a re-derivation of the same wheel, all of them slightly worse than the last.
@platform/admin-kit cuts that off. Every admin page in the Loop ecosystem composes the same primitives. When we improve <DataTable>, every admin gets it. When the design tokens change, every admin re-skins. When <AuditLogPanel> learns a new column, it shows up everywhere.
What’s in the box
| Primitive | Purpose |
|---|---|
<AdminProvider> | Root context — session, brand, base URLs, TanStack Query client |
<AdminPage> | Page wrapper — auth gate + <ErrorBoundary> + <Suspense> + layout chrome |
<DataTable> | Generic sortable / paginated table fed by a useResourceList hook |
<ResourceForm> | Zod-validated form with optimistic updates and structured error rendering |
<ResourceDetail> | Read view + edit/delete actions + inline <AuditLogPanel> |
<AuditLogPanel> | Generic audit-row viewer — works against any service’s audit endpoint |
<BrandPicker> | Brand selector backed by @platform/brands, filtered by staff RBAC |
<RequireScope> | Declarative scope gate — <RequireScope scope="admin:identity">... |
<Skeleton> / <TableSkeleton> / <FormSkeleton> | Loading placeholders |
<ErrorPanel> | Renders structured AppError from @platform/contracts |
useResource(service, id) | Single-resource fetch with revalidation |
useResourceList(service, filters) | Paginated list with sort/filter |
useResourceMutation() | Create/update/delete with cache invalidation and rollback |
useAuditLog(service, resourceId) | Audit trail for a resource |
useBrandContext() | Current brand + brand list |
useStaffSession() | Current WorkOS staff session |
Plus the Tailwind v3 config + shadcn primitives (Button, Card, Dialog, Table, Form, Toast, ~25 more) and the Loop brand-token CSS layer that ties them together.
How a page is built
Every admin page in the Loop ecosystem looks roughly like this:
// apps/platform-admin/src/app/oauth-clients/page.tsx
"use client";
import { AdminPage, DataTable, useResourceList } from "@platform/admin-kit";
export default function OAuthClientsPage() {
const clients = useResourceList("identity", { resource: "oauth-clients" });
return (
<AdminPage title="OAuth clients" scope="admin:identity">
<DataTable
data={clients.data}
columns={[
{ header: "Name", accessor: "name" },
{ header: "Client ID", accessor: "client_id" },
{ header: "Type", accessor: "type" },
{ header: "Created", accessor: "created_at", format: "date" },
]}
onRowClick={(c) => router.push(`/oauth-clients/${c.id}`)}
skeleton={<TableSkeleton rows={10} />}
/>
</AdminPage>
);
}That’s it. No fetch logic. No loading state. No error boundary. No auth gate. No brand filter wiring. The hook + the components handle it. Mutation pages are similar — useResourceMutation returns a mutate function plus optimistic state.
Hard rules (enforced)
Convention checks block CI if these are violated in any admin app:
- No raw
fetch. Data access goes through@platform/sdk-*clients, and SDK calls go through admin-kit hooks. The convention check rejectsfetch(inapps/platform-admin/andpackages/admin-kit/. - No
style={{}}. All styling via Tailwind classes. The convention check rejects inline styles. - No legacy
@loop/*. Only@platform/*packages. - Every page wrapped in
<AdminPage>. The convention check verifies every route’s default export uses it. - Mutations render audit rows inline. Proves audit is real-time.
- Brand-aware by default. Pages that mutate must pass the current
brand_idviauseBrandContext().
These rules don’t exist to be pedantic. Each one prevents a specific failure mode we’ve seen in legacy admins (silent staleness, accidental cross-brand writes, drift between what the UI shows and what audit captures, etc.).
Auth
Admin pages run behind a WorkOS staff session. The detailed flow is in the auth model, but the short version:
- User visits
admin.{stage}.platform.loop.health→ middleware checks for session cookie - No cookie → redirect to
/sign-in→ redirect to WorkOS AuthKit - User picks an auth method (Magic Auth code today; SSO/passkey later)
- WorkOS redirects to
/callbackwith an authorization code /callbackroute exchanges the code for a user profile + access token, signs a session cookie, redirects to/admin- Subsequent requests carry the signed cookie;
useStaffSession()reads it
The access token from WorkOS is also what useResource* passes as the bearer when calling platform services. No second auth dance.
How partners would consume this
@platform/admin-kit is a real npm package (private registry today, public roadmap). A partner building their own admin on top of the Loop OAuth platform could:
- Install
@platform/admin-kit+ the specific@platform/sdk-*packages they need - Wrap their app in
<AdminProvider>with their own client_id / WorkOS config - Compose pages with the same primitives
They get the same auth flow, the same audit visibility, the same brand-scoping, the same error handling — without rewriting any of it. This is what “Connect with Loop” looks like at the SDK layer.
Absorption roadmap
| Phase | What | Status |
|---|---|---|
| 1 | apps/platform-admin canary (oauth-clients, content, audit) | Done (LOO-1909) |
| 2 | Absorb command-center/apps/ops-dashboard page-by-page | Pending |
| 3 | Absorb loop-platform/apps/admin (post services/content content surface) | Pending |
| 4 | Service-specific admin needs added as new pages | Ongoing |
The full plan is in docs/architecture/admin-kit-roadmap.md (internal).
Related
- Auth model — WorkOS staff session, OAuth, M2M
- Audit & PHI — what audit rows look like, why they show up inline
- Brands & multi-tenancy — brand context in admin
services/identity— the OAuth client management surfaceservices/content— the content management surface
Source ADRs
ADR-0037 (services structure), ADR-0052 (Connect with Loop), and the admin-kit-roadmap doc (LOO-1909).