105 lines
3.0 KiB
TypeScript
105 lines
3.0 KiB
TypeScript
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router';
|
|
import {
|
|
AlertDialog,
|
|
Badge,
|
|
Card,
|
|
DataTable,
|
|
EmptyState,
|
|
Spinner,
|
|
} from '@cameleer/design-system';
|
|
import type { Column } from '@cameleer/design-system';
|
|
import { useAllTenants } from '../api/hooks';
|
|
import { useOrgStore } from '../auth/useOrganization';
|
|
import type { TenantResponse } from '../types/api';
|
|
import styles from '../styles/platform.module.css';
|
|
|
|
const columns: Column<TenantResponse>[] = [
|
|
{ key: 'name', header: 'Name' },
|
|
{ key: 'slug', header: 'Slug' },
|
|
{
|
|
key: 'tier',
|
|
header: 'Tier',
|
|
render: (_v: unknown, row: TenantResponse) => <Badge label={row.tier} color="primary" />,
|
|
},
|
|
{
|
|
key: 'status',
|
|
header: 'Status',
|
|
render: (_v: unknown, row: TenantResponse) => (
|
|
<Badge
|
|
label={row.status}
|
|
color={row.status === 'ACTIVE' ? 'success' : 'warning'}
|
|
/>
|
|
),
|
|
},
|
|
{ key: 'createdAt', header: 'Created', render: (_: unknown, row: TenantResponse) => new Date(row.createdAt).toLocaleDateString() },
|
|
];
|
|
|
|
export function AdminTenantsPage() {
|
|
const navigate = useNavigate();
|
|
const { data: tenants, isLoading, isError } = useAllTenants();
|
|
const { setCurrentOrg } = useOrgStore();
|
|
const [switchTarget, setSwitchTarget] = useState<TenantResponse | null>(null);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center h-64">
|
|
<Spinner />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<div className="p-6">
|
|
<EmptyState
|
|
title="Unable to load tenants"
|
|
description="You may not have admin permissions, or the server is unavailable."
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const handleRowClick = (tenant: TenantResponse) => {
|
|
setSwitchTarget(tenant);
|
|
};
|
|
|
|
const confirmSwitch = () => {
|
|
if (!switchTarget) return;
|
|
const orgs = useOrgStore.getState().organizations;
|
|
const match = orgs.find((o) => o.name === switchTarget.name || o.slug === switchTarget.slug);
|
|
if (match) {
|
|
setCurrentOrg(match.id);
|
|
navigate('/');
|
|
}
|
|
setSwitchTarget(null);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6 p-6">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className={styles.heading}>All Tenants</h1>
|
|
<Badge label="Platform Admin" color="warning" />
|
|
</div>
|
|
|
|
<Card title={`${tenants?.length ?? 0} Tenants`}>
|
|
{(!tenants || tenants.length === 0) ? (
|
|
<EmptyState title="No tenants" description="No tenants have been created yet." />
|
|
) : (
|
|
<DataTable columns={columns} data={tenants} onRowClick={handleRowClick} />
|
|
)}
|
|
</Card>
|
|
|
|
<AlertDialog
|
|
open={!!switchTarget}
|
|
onClose={() => setSwitchTarget(null)}
|
|
onConfirm={confirmSwitch}
|
|
title="Switch tenant?"
|
|
description={`Switch to tenant "${switchTarget?.name}"? Your dashboard context will change.`}
|
|
confirmLabel="Switch"
|
|
variant="warning"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|