(props: {
readonly user: AdminUserRow;
readonly catalog: readonly AdminCatalogRow[];
readonly icons: IconLookup;
readonly orgSlug: string | undefined;
})
| 363 | // ── Detail sheet ──────────────────────────────────────────────────────────── |
| 364 | |
| 365 | function UserDetail(props: { |
| 366 | readonly user: AdminUserRow; |
| 367 | readonly catalog: readonly AdminCatalogRow[]; |
| 368 | readonly icons: IconLookup; |
| 369 | readonly orgSlug: string | undefined; |
| 370 | }) { |
| 371 | const result = useAtomValue(adminUserConnectionsAtom(props.user.externalId)); |
| 372 | const refresh = useAtomRefresh(adminUserConnectionsAtom(props.user.externalId)); |
| 373 | // The connect link targets the recipient's own session, so it is built for |
| 374 | // this deployment's origin and copied out — never navigated to from here. It |
| 375 | // carries THIS admin's org so a multi-org recipient connects in the workspace |
| 376 | // the link came from, not whichever one their session defaults to. |
| 377 | const origin = globalThis.window?.location?.origin ?? ""; |
| 378 | |
| 379 | if (isAsyncResultLoading(result)) { |
| 380 | return ( |
| 381 | <div className="flex flex-col gap-2 p-6"> |
| 382 | {[0, 1, 2].map((row) => ( |
| 383 | <Skeleton key={row} className="h-12" /> |
| 384 | ))} |
| 385 | </div> |
| 386 | ); |
| 387 | } |
| 388 | |
| 389 | return AsyncResult.match(result, { |
| 390 | onInitial: () => ( |
| 391 | <div className="flex flex-col gap-2 p-6"> |
| 392 | {[0, 1, 2].map((row) => ( |
| 393 | <Skeleton key={row} className="h-12" /> |
| 394 | ))} |
| 395 | </div> |
| 396 | ), |
| 397 | onFailure: (failure) => |
| 398 | isAccessDenied(failure.cause) ? ( |
| 399 | <div className="p-6"> |
| 400 | <AccessDenied /> |
| 401 | </div> |
| 402 | ) : ( |
| 403 | <div className="p-6"> |
| 404 | <ErrorState message="Couldn't load this user's connections" onRetry={refresh} /> |
| 405 | </div> |
| 406 | ), |
| 407 | onSuccess: ({ value }) => { |
| 408 | const states = integrationConnectionStates(props.catalog, value.connections); |
| 409 | const connected = states.filter((state) => state.connected); |
| 410 | const available = states.filter((state) => !state.connected); |
| 411 | |
| 412 | return ( |
| 413 | <div className="flex flex-col gap-8 overflow-y-auto p-6"> |
| 414 | <section> |
| 415 | <h3 className="font-mono text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground"> |
| 416 | Connected · {connected.length} |
| 417 | </h3> |
| 418 | {connected.length === 0 ? ( |
| 419 | <p className="mt-3 text-sm leading-6 text-muted-foreground"> |
| 420 | This user hasn't connected anything yet. Send them a connect link below to get |
| 421 | started. |
| 422 | </p> |
nothing calls this directly
no test coverage detected