Files
arcadia-admin/app/lib/arcadia/tenants.ts
jules 080597d046 feat: tenants admin surface
Adds the first admin screen — /tenants — listing tenants from
GET /api/v1/admin/tenants with search, status badges, plan, created
date, and a per-row menu with suspend / activate / deactivate actions.

Hand-typed shapes in app/lib/arcadia/tenants.ts because arcadia's admin
endpoints aren't yet covered by /api/openapi (same 'ok'-placeholder
issue documented in lib-arcadia-client/scripts/sync-spec.mjs). When
the spec gains coverage, switch to arcadia.typed.GET(...) and drop the
manual types.

Trims the inherited consumer-app sidenav (Resources / Assistant / AI /
Library) down to admin-shaped items: Overview, Tenants, Audit log,
Settings. The unused route files stay in place; they just aren't linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:36:42 +10:00

94 lines
2.7 KiB
TypeScript

// Arcadia tenants API helpers.
//
// Hand-rolled because /api/v1/admin/tenants isn't covered by arcadia's
// OpenAPI spec (controller hasn't been wired into OpenApiSpex yet — same
// "ok"-placeholder issue as some other admin endpoints). When the spec
// gains coverage, switch to `arcadia.typed.GET("/api/v1/admin/tenants", ...)`
// and drop these manual types.
import type { ArcadiaClient } from "@crema/arcadia-client"
export type TenantStatus = "active" | "suspended" | "deactivated" | string
export interface TenantPlan {
name: string
limits: Record<string, unknown>
}
export interface TenantBranding {
logo_url: string | null
favicon_url: string | null
primary_color: string | null
secondary_color: string | null
accent_color: string | null
custom_css: string | null
settings: Record<string, unknown>
}
export interface TenantSettings {
timezone?: string
currency?: string
[key: string]: unknown
}
export interface TenantLocalization {
locale: string
timezone: string
currency: string
settings: Record<string, unknown>
}
export interface Tenant {
id: string
slug: string
name: string
status: TenantStatus
plan: TenantPlan
branding: TenantBranding
settings: TenantSettings
localization: TenantLocalization
email_settings: Record<string, unknown>
notification_settings: Record<string, unknown>
metadata: Record<string, unknown>
inserted_at: string
updated_at: string
}
export interface TenantListParams {
q?: string
status?: TenantStatus
page?: number
per_page?: number
}
export async function listTenants(
arcadia: ArcadiaClient,
params?: TenantListParams,
): Promise<Tenant[]> {
const queryParams: Record<string, string | number | boolean | null | undefined> | undefined = params
? { q: params.q, status: params.status, page: params.page, per_page: params.per_page }
: undefined
const res = await arcadia.GET<{ data: Tenant[] }>("/api/v1/admin/tenants", { params: queryParams })
return res.data
}
export async function getTenant(arcadia: ArcadiaClient, id: string): Promise<Tenant> {
const res = await arcadia.GET<{ data: Tenant }>(`/api/v1/admin/tenants/${id}`)
return res.data
}
export async function suspendTenant(arcadia: ArcadiaClient, id: string): Promise<Tenant> {
const res = await arcadia.POST<{ data: Tenant }>(`/api/v1/admin/tenants/${id}/suspend`)
return res.data
}
export async function activateTenant(arcadia: ArcadiaClient, id: string): Promise<Tenant> {
const res = await arcadia.POST<{ data: Tenant }>(`/api/v1/admin/tenants/${id}/activate`)
return res.data
}
export async function deactivateTenant(arcadia: ArcadiaClient, id: string): Promise<Tenant> {
const res = await arcadia.POST<{ data: Tenant }>(`/api/v1/admin/tenants/${id}/deactivate`)
return res.data
}