import { useState } from "react"; import { useAtomRefresh, useAtomValue } from "@effect/atom-react"; import { useParams } from "@tanstack/react-router"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import * as Cause from "effect/Cause"; import * as Option from "effect/Option"; import * as Predicate from "effect/Predicate"; import type { HealthStatus, Integration, IntegrationSlug } from "@executor-js/sdk/shared"; import { useIntegrationPlugins } from "@executor-js/sdk/client"; import { ADMIN_USERS_PAGE_SIZE, adminUserConnectionsAtom, adminUsersWithConnectionsAtom, } from "../api/admin-atoms"; import { integrationsOptimisticAtom } from "../api/atoms"; import { ownerLabel } from "../api/owner-display"; import { Button } from "../components/button"; import { CopyButton } from "../components/copy-button"; import { ErrorState } from "../components/error-state"; import { IntegrationFavicon, integrationInferredUrl, integrationPresetIconUrl, } from "../components/integration-favicon"; import { PageContainer, PageHeader } from "../components/page"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "../components/sheet"; import { Skeleton } from "../components/skeleton"; import { adminUserCopyableEmail, adminUserTitle, connectionHealthStatus, connectLinkUrl, formatAdminDate, formatLastSeen, integrationConnectionStates, lastSeenTitle, pageNumber, shortenExternalId, splitPage, type AdminCatalogRow, type AdminConnectionRow, type AdminUserIdentityRow, } from "../lib/admin-users-display"; import { HEALTH_INDICATOR_COLOR, HEALTH_STATUS_LABEL, HEALTH_TEXT_CLASS, } from "../lib/health-display"; import { isAsyncResultLoading } from "../lib/async-result"; import { useExecutorDocumentTitle } from "../lib/document-title"; // --------------------------------------------------------------------------- // Admin · Users — the tenant-wide operator view. // // The product plane answers "what can I reach"; this page answers the owner's // question: who are my users, and what has each of them connected. It reads the // admin plane only (`/admin/users*`), which is read-only by construction, so // there is nothing to mutate here — every affordance is a read or a copy. // // ACCESS: the server is the gate. A plain member's request comes back 403 (401 // with no session), and the page renders an explicit denial rather than an // empty table, so "you may not see this" never reads as "there is nobody here". // The nav item is separately hidden from non-admins (see lib/admin-access), but // that is convenience — this is the surface that actually refuses. // // The row is a button rather than a link: a user has no page of its own, and a // detail sheet keeps the list's paging position while an operator scans several // users in a row. // --------------------------------------------------------------------------- type AdminUserRow = { readonly externalId: string; readonly createdAt: number; readonly lastSeenAt: number | null; readonly status: string | null; /** Joined server-side from the host's member directory. Null whenever the * host couldn't resolve the id — see `adminUserTitle` for what that renders * as. */ readonly email: string | null; readonly displayName: string | null; readonly connections: readonly AdminConnectionRow[]; }; /** 401/403 from the admin plane. Both mean "not an operator here" and get the * same denial: the distinction (no session vs wrong role) is the server's, and * a signed-in member reading "unauthorized" would only confuse. */ const isAccessDenied = (cause: Cause.Cause): boolean => Option.match(Cause.findErrorOption(cause), { onNone: () => false, onSome: (error) => Predicate.isTagged(error, "AdminUsersForbidden") || Predicate.isTagged(error, "AdminUsersUnauthorized"), }); // ── Access denied ─────────────────────────────────────────────────────────── function AccessDenied() { return (

Admin only

You don't have access to this workspace's users

Reading every user and what they've connected requires an admin role. Ask an admin of this workspace if you need it.

); } // ── Health ────────────────────────────────────────────────────────────────── /** The shared health vocabulary, rendered from a stored verdict. * * Deliberately NOT `IntegrationHealthSummary`: that component probes upstream * on mount through the product plane, which is both the wrong plane here (an * admin is not the credential's owner) and the wrong behavior (opening an * admin view must not fire traffic at every user's provider). This reads the * `lastHealth` the admin payload already carries. */ function HealthIndicator(props: { readonly status: HealthStatus }) { const label = HEALTH_STATUS_LABEL[props.status]; return ( {label} ); } // ── Integration icons ─────────────────────────────────────────────────────── /** The catalog joined into a favicon lookup. The admin payload carries only a * slug per connection (no kind, no display URL — the platform view hands out * nothing it doesn't have to), so brand marks come from the tenant's own * integration catalog, which the admin can read on the product plane. */ type IconLookup = (integration: IntegrationSlug) => { readonly icon: string | null; readonly url: string | undefined; readonly name: string; }; const useIntegrationIcons = (): IconLookup => { const catalog = useAtomValue(integrationsOptimisticAtom); const integrationPlugins = useIntegrationPlugins(); const integrations = Option.getOrElse( AsyncResult.value(catalog), (): readonly Integration[] => [], ); return (integration) => { const slug = String(integration); const found = integrations.find((row) => String(row.slug) === slug); const name = found?.name || slug; return { icon: found ? integrationPresetIconUrl( { id: slug, kind: found.kind, name, url: found.displayUrl }, integrationPlugins, ) : null, url: found?.displayUrl ?? integrationInferredUrl({ id: slug, name }) ?? undefined, name, }; }; }; /** The catalog rows this view needs — slug plus kind, since `kind` is what says * whether an integration can be connected at all (see * `isConnectableIntegration`). Empty while the catalog loads, which degrades to * "connected only" rather than blocking the users table on a second request. */ const useCatalogRows = (): readonly AdminCatalogRow[] => { const catalog = useAtomValue(integrationsOptimisticAtom); return Option.match(AsyncResult.value(catalog), { onNone: (): readonly AdminCatalogRow[] => [], onSome: (integrations: readonly Integration[]) => integrations.map((row) => ({ slug: row.slug, kind: row.kind })), }); }; /** * The connection summary: one slot per integration in the catalog, lit when * this user has connected it and greyed when they haven't. * * Showing the not-connected slots is the point — the grid answers "what is * available and what has this person actually set up", which a list of only * their connections cannot. */ function ConnectionGrid(props: { readonly catalog: readonly AdminCatalogRow[]; readonly connections: readonly AdminConnectionRow[]; readonly icons: IconLookup; }) { const states = integrationConnectionStates(props.catalog, props.connections); if (states.length === 0) { return No integrations; } const connectedCount = states.filter((state) => state.connected).length; return (
{states.slice(0, 8).map((state) => { const { icon, url, name } = props.icons(state.integration); return ( ); })}
{connectedCount}/{states.length}
); } // ── Identity ──────────────────────────────────────────────────────────────── /** * The user cell: who this is, then the opaque id underneath. * * The host joins email/name onto each row server-side, so the headline is * something an operator recognizes and the `externalId` becomes the supporting * line — still mono, still carrying the full value in its title, still under the * same `data-slot` it always had, because the id is what identifies a row to * anything reading this table. When the host resolved no identity the cell * collapses back to exactly the single mono id line this table has always shown. * * NO copy button lives here: the row itself is a ` )} ); } return ( <>
User Created Last seen Connections
{rows.map((user: AdminUserRow) => ( // oxlint-disable-next-line react/forbid-elements ))}
{(hasNext || offset > 0) && (
Page {pageNumber(offset, ADMIN_USERS_PAGE_SIZE)}
)} ); }, })} !open && setSelected(null)}> {/* A read-only detail panel: clicking away closes it. */} {selected && ( <> Created {formatAdminDate(selected.createdAt)} · last seen{" "} {formatLastSeen(selected.lastSeenAt).toLowerCase()} )} ); }