import { useCallback, useEffect, useMemo, useState } from "react" import { Link } from "react-router" import { AlertTriangle, Loader2, MoreHorizontal, Pause, Play, Plus, RefreshCw, Search, X } from "lucide-react" import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-client" import { AppShell } from "~/components/layout/app-shell" import { Badge } from "~/components/ui/badge" import { Button } from "~/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "~/components/ui/dropdown-menu" import { Input } from "~/components/ui/input" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table" import { activateTenant, deactivateTenant, listTenants, suspendTenant, type Tenant, type TenantStatus } from "~/lib/arcadia/tenants" import { pageTitle } from "~/lib/page-meta" import { useSession } from "~/lib/session" export const meta = () => pageTitle("Tenants") export default function TenantsRoute() { const session = useSession() const arcadia = useArcadiaClient() const [tenants, setTenants] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [query, setQuery] = useState("") const refresh = useCallback(async () => { setError(null) setLoading(true) try { const list = await listTenants(arcadia) setTenants(list) } catch (err) { setError(err instanceof ArcadiaError ? err.message : "Failed to load tenants.") } finally { setLoading(false) } }, [arcadia]) useEffect(() => { if (session) refresh() }, [session, refresh]) const filtered = useMemo(() => { const q = query.trim().toLowerCase() if (!q) return tenants return tenants.filter( (t) => t.name.toLowerCase().includes(q) || t.slug.toLowerCase().includes(q) || t.id.toLowerCase().includes(q), ) }, [tenants, query]) if (!session) { return (
Sign in required Tenant administration requires an admin session.
) } return (

Tenants

Multi-tenant workspaces on this arcadia deployment.

setQuery(e.target.value)} className="pl-9" /> {query ? ( ) : null}
{filtered.length} of {tenants.length}
{error ? (
{error}
) : null} {loading && tenants.length === 0 ? (
Loading tenants…
) : filtered.length === 0 ? (
{query ? "No tenants match that search." : "No tenants yet."}
) : ( Name Slug Status Plan Created {filtered.map((t) => ( ))}
)}
) } function TenantRow({ tenant, onChange }: { tenant: Tenant; onChange: () => void | Promise }) { const arcadia = useArcadiaClient() const [busy, setBusy] = useState(false) const act = async (fn: () => Promise) => { setBusy(true) try { await fn() await onChange() } finally { setBusy(false) } } return ( {tenant.name} {tenant.slug} {tenant.plan?.name ?? "—"} {formatDate(tenant.inserted_at)} {tenant.status === "active" ? ( act(() => suspendTenant(arcadia, tenant.id))}> Suspend ) : ( act(() => activateTenant(arcadia, tenant.id))}> Activate )} act(() => deactivateTenant(arcadia, tenant.id))}> Deactivate ) } function StatusBadge({ status }: { status: TenantStatus }) { const variant: "default" | "secondary" | "destructive" | "outline" = status === "active" ? "default" : status === "suspended" ? "secondary" : "outline" return ( {status} ) } function formatDate(iso: string): string { try { return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }) } catch { return iso } }