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>
This commit is contained in:
93
app/lib/arcadia/tenants.ts
Normal file
93
app/lib/arcadia/tenants.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user