()
| 524 | // ── Page ──────────────────────────────────────────────────────────────────── |
| 525 | |
| 526 | export function AdminUsersPage() { |
| 527 | useExecutorDocumentTitle("Users"); |
| 528 | const [offset, setOffset] = useState(0); |
| 529 | const [selected, setSelected] = useState<AdminUserRow | null>(null); |
| 530 | |
| 531 | const page = { limit: ADMIN_USERS_PAGE_SIZE, offset }; |
| 532 | const result = useAtomValue(adminUsersWithConnectionsAtom(page)); |
| 533 | const refresh = useAtomRefresh(adminUsersWithConnectionsAtom(page)); |
| 534 | const catalog = useCatalogRows(); |
| 535 | const icons = useIntegrationIcons(); |
| 536 | // The route is `/{-$orgSlug}/users`: the segment is OPTIONAL, so this reads |
| 537 | // `undefined` on any host that renders no segment and the link falls back to |
| 538 | // the bare form. Both cloud and self-host do canonicalize onto a slug (self- |
| 539 | // host onto `default`), so in practice the prefixed form is what ships — but |
| 540 | // the optional param is the contract, not an assumption about the host. |
| 541 | const { orgSlug } = useParams({ strict: false }) as { orgSlug?: string }; |
| 542 | |
| 543 | const header = ( |
| 544 | <PageHeader |
| 545 | title="Users" |
| 546 | description="Everyone who has used this workspace, and what each of them has connected." |
| 547 | /> |
| 548 | ); |
| 549 | |
| 550 | const loading = ( |
| 551 | <div className="flex flex-col gap-2"> |
| 552 | {[0, 1, 2, 3].map((row) => ( |
| 553 | <Skeleton key={row} className="h-14" /> |
| 554 | ))} |
| 555 | </div> |
| 556 | ); |
| 557 | |
| 558 | return ( |
| 559 | <PageContainer> |
| 560 | {header} |
| 561 | |
| 562 | {isAsyncResultLoading(result) |
| 563 | ? loading |
| 564 | : AsyncResult.match(result, { |
| 565 | onInitial: () => loading, |
| 566 | onFailure: (failure) => |
| 567 | isAccessDenied(failure.cause) ? ( |
| 568 | <AccessDenied /> |
| 569 | ) : ( |
| 570 | <ErrorState message="Couldn't load users" onRetry={refresh} /> |
| 571 | ), |
| 572 | onSuccess: ({ value }) => { |
| 573 | const { rows, hasNext } = splitPage(value.users, ADMIN_USERS_PAGE_SIZE); |
| 574 | |
| 575 | if (rows.length === 0) { |
| 576 | return ( |
| 577 | <div className="rounded-lg border border-dashed border-border bg-card p-8"> |
| 578 | <h2 className="text-base font-semibold text-foreground"> |
| 579 | {offset === 0 ? "No users yet" : "No users on this page"} |
| 580 | </h2> |
| 581 | <p className="mt-2 max-w-xl text-sm leading-6 text-muted-foreground"> |
| 582 | {offset === 0 |
| 583 | ? "A user appears here the first time they reach this workspace or connect an account." |
nothing calls this directly
no test coverage detected