@b1-road/react 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +300 -0
  3. package/dist/__tests__/a11y.test.d.ts +1 -0
  4. package/dist/__tests__/contract-replay.test.d.ts +1 -0
  5. package/dist/__tests__/create-role-dialog.test.d.ts +1 -0
  6. package/dist/__tests__/form-errors.test.d.ts +1 -0
  7. package/dist/__tests__/pending-invitations.test.d.ts +1 -0
  8. package/dist/__tests__/setup.d.ts +0 -0
  9. package/dist/api/client.d.ts +253 -0
  10. package/dist/api/client.test.d.ts +1 -0
  11. package/dist/api/cookie-mode.test.d.ts +1 -0
  12. package/dist/api/errors.d.ts +107 -0
  13. package/dist/api/errors.test.d.ts +1 -0
  14. package/dist/api/hooks.d.ts +126 -0
  15. package/dist/api/hooks.test.d.ts +1 -0
  16. package/dist/api/mock-client.d.ts +121 -0
  17. package/dist/api/mock-client.test.d.ts +1 -0
  18. package/dist/api/types.d.ts +7 -0
  19. package/dist/appearance/appearance.d.ts +19 -0
  20. package/dist/components/BusinessUnitSwitcher.d.ts +15 -0
  21. package/dist/components/BusinessUnitsMgmt.d.ts +35 -0
  22. package/dist/components/business-units/BusinessUnitDetail.d.ts +6 -0
  23. package/dist/components/business-units/BusinessUnitList.d.ts +5 -0
  24. package/dist/components/business-units/BusinessUnitRow.d.ts +7 -0
  25. package/dist/components/business-units/BusinessUnitSettings.d.ts +5 -0
  26. package/dist/components/business-units/BusinessUnitsTab.d.ts +5 -0
  27. package/dist/components/business-units/CreateBusinessUnitForm.d.ts +6 -0
  28. package/dist/components/business-units/MembersList.d.ts +5 -0
  29. package/dist/components/business-units/PendingInvitations.d.ts +15 -0
  30. package/dist/components/invitations/InvitationsList.d.ts +7 -0
  31. package/dist/components/invitations/InvitationsTab.d.ts +5 -0
  32. package/dist/components/invitations/InviteForm.d.ts +7 -0
  33. package/dist/components/roles/BUSelector.d.ts +8 -0
  34. package/dist/components/roles/CreateRoleDialog.d.ts +8 -0
  35. package/dist/components/roles/PermissionPicker.d.ts +10 -0
  36. package/dist/components/roles/RoleEditor.d.ts +7 -0
  37. package/dist/components/roles/RoleRow.d.ts +7 -0
  38. package/dist/components/roles/RolesList.d.ts +7 -0
  39. package/dist/components/roles/RolesTab.d.ts +1 -0
  40. package/dist/components/shared/Avatar.d.ts +7 -0
  41. package/dist/components/shared/EmptyState.d.ts +10 -0
  42. package/dist/components/shared/LoadMoreFooter.d.ts +18 -0
  43. package/dist/i18n/context.d.ts +21 -0
  44. package/dist/i18n/en.d.ts +2 -0
  45. package/dist/i18n/pt-BR.d.ts +2 -0
  46. package/dist/i18n/types.d.ts +227 -0
  47. package/dist/index.cjs +56 -0
  48. package/dist/index.cjs.map +1 -0
  49. package/dist/index.d.ts +25 -0
  50. package/dist/index.js +16566 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/lib/cn.d.ts +2 -0
  53. package/dist/lib/use-form-errors.d.ts +34 -0
  54. package/dist/provider/RoadProvider.d.ts +93 -0
  55. package/dist/provider/context.d.ts +12 -0
  56. package/dist/provider/cookie-mode-integration.test.d.ts +1 -0
  57. package/dist/provider/current-business-unit.d.ts +37 -0
  58. package/dist/provider/current-business-unit.test.d.ts +1 -0
  59. package/dist/provider/strict-mode-checks.test.d.ts +1 -0
  60. package/dist/style.css +1 -0
  61. package/dist/ui/alert-dialog.d.ts +8 -0
  62. package/dist/ui/badge.d.ts +9 -0
  63. package/dist/ui/button.d.ts +11 -0
  64. package/dist/ui/checkbox.d.ts +2 -0
  65. package/dist/ui/dialog.d.ts +17 -0
  66. package/dist/ui/dropdown-menu.d.ts +10 -0
  67. package/dist/ui/field.d.ts +26 -0
  68. package/dist/ui/input.d.ts +4 -0
  69. package/dist/ui/label.d.ts +2 -0
  70. package/dist/ui/select.d.ts +6 -0
  71. package/dist/ui/skeleton.d.ts +2 -0
  72. package/dist/ui/tabs.d.ts +5 -0
  73. package/dist/ui/textarea.d.ts +2 -0
  74. package/dist/ui/tooltip.d.ts +5 -0
  75. package/package.json +126 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Road API error taxonomy. Every non-OK HTTP response from the Road API is
3
+ * mapped to one of these classes so integrators can branch on
4
+ * `instanceof` rather than reading raw HTTP status codes.
5
+ *
6
+ * All errors carry the originating HTTP status, the parsed error body
7
+ * (when the response was a Problem-Details JSON document), the
8
+ * `x-request-id` response header, and the `traceparent` trace id when the
9
+ * API emits one — the values the API logs against the request, so an
10
+ * integrator opening a support ticket has something Road on-call can grep
11
+ * for.
12
+ *
13
+ * Inheritance: every specific class extends `RoadApiError`, so existing
14
+ * `catch (e) { if (e instanceof RoadApiError) … }` blocks still catch
15
+ * every Road error.
16
+ */
17
+ /**
18
+ * Shape of the API's RFC 7807 Problem Details body. Road's
19
+ * GlobalExceptionFilter emits this for every error response — see
20
+ * `apps/api/src/common/filters/http-exception.filter.ts`.
21
+ */
22
+ export interface RoadProblemDetails {
23
+ type?: string;
24
+ title?: string;
25
+ status?: number;
26
+ detail?: string;
27
+ instance?: string;
28
+ errors?: Array<{
29
+ field: string;
30
+ code: string;
31
+ message: string;
32
+ }>;
33
+ meta?: {
34
+ requestId?: string | null;
35
+ timestamp?: string;
36
+ [k: string]: unknown;
37
+ };
38
+ [k: string]: unknown;
39
+ }
40
+ export interface RoadApiErrorInit {
41
+ status: number;
42
+ statusText: string;
43
+ body: string;
44
+ problem?: RoadProblemDetails;
45
+ requestId?: string;
46
+ traceId?: string;
47
+ }
48
+ /**
49
+ * Base class for every Road SDK HTTP error. Specific status codes get
50
+ * subclasses (see below) — integrators should usually branch on those.
51
+ */
52
+ export declare class RoadApiError extends Error {
53
+ readonly status: number;
54
+ readonly statusText: string;
55
+ readonly body: string;
56
+ readonly problem?: RoadProblemDetails;
57
+ readonly requestId?: string;
58
+ readonly traceId?: string;
59
+ constructor(init: RoadApiErrorInit);
60
+ }
61
+ /** 401 — JWT missing, expired, or rejected by the API. Host should refresh and retry. */
62
+ export declare class RoadAuthError extends RoadApiError {
63
+ constructor(init: RoadApiErrorInit);
64
+ }
65
+ /** 403 — authenticated but not authorized for this resource. */
66
+ export declare class RoadForbiddenError extends RoadApiError {
67
+ constructor(init: RoadApiErrorInit);
68
+ }
69
+ /** 404 — resource doesn't exist (or caller can't see it). */
70
+ export declare class RoadNotFoundError extends RoadApiError {
71
+ constructor(init: RoadApiErrorInit);
72
+ }
73
+ /** 409 — request conflicts with current state (duplicate, version skew, …). */
74
+ export declare class RoadConflictError extends RoadApiError {
75
+ constructor(init: RoadApiErrorInit);
76
+ }
77
+ /**
78
+ * 400 / 422 — request payload failed validation. `fieldErrors` is keyed
79
+ * by the request field name; values are the list of validator messages
80
+ * for that field. Empty when the API returns a non-field-shaped error.
81
+ */
82
+ export declare class RoadValidationError extends RoadApiError {
83
+ readonly fieldErrors: Record<string, string[]>;
84
+ constructor(init: RoadApiErrorInit);
85
+ }
86
+ /**
87
+ * 429 — caller is rate-limited. `retryAfter` (seconds) is parsed from
88
+ * the `Retry-After` response header when present.
89
+ */
90
+ export declare class RoadRateLimitError extends RoadApiError {
91
+ readonly retryAfter?: number;
92
+ constructor(init: RoadApiErrorInit & {
93
+ retryAfter?: number;
94
+ });
95
+ }
96
+ /** 5xx — server-side failure. The SDK's retry loop targets this class + network errors. */
97
+ export declare class RoadServerError extends RoadApiError {
98
+ constructor(init: RoadApiErrorInit);
99
+ }
100
+ /** Network failure before/while reading the response (fetch threw). */
101
+ export declare class RoadNetworkError extends RoadApiError {
102
+ readonly cause?: unknown;
103
+ constructor(message: string, cause?: unknown);
104
+ }
105
+ export declare function mapStatusToError(init: RoadApiErrorInit & {
106
+ retryAfter?: number;
107
+ }): RoadApiError;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,126 @@
1
+ import { InfiniteData } from '@tanstack/react-query';
2
+ import { RoadClient } from './client';
3
+ import { BusinessUnitDetail, CreateBusinessUnitInput, CreateInvitationInput, CreateRoleInput, CurrentUser, Invitation, Member, MyBusinessUnits, MyPermissions, PaginatedList, Permission, RoadPermission, Role, UpdateBusinessUnitInput, UpdateRoleInput } from './types';
4
+ /**
5
+ * Action argument accepted by useCan(). Autocompletes Road's core permission
6
+ * set (read/manage × the four built-in subjects, plus "*"), and accepts any
7
+ * other string for platform-specific permissions.
8
+ */
9
+ type PermissionInput = RoadPermission | (string & {});
10
+ export declare function useCurrentUser(): import('@tanstack/react-query').UseQueryResult<NoInfer<CurrentUser>, Error>;
11
+ export declare function useMyBusinessUnits(): import('@tanstack/react-query').UseQueryResult<NoInfer<MyBusinessUnits>, Error>;
12
+ export declare function useMyPermissions(): import('@tanstack/react-query').UseQueryResult<NoInfer<MyPermissions>, Error>;
13
+ /**
14
+ * Permission check for the current user inside a business unit.
15
+ *
16
+ * Returns a callable that resolves the action against the user's effective
17
+ * permissions for the given BU. A role with `["*"]` short-circuits to true.
18
+ * While permissions are still loading, returns `false` (deny by default).
19
+ *
20
+ * Called with no arguments, reads the current BU from CurrentBusinessUnitContext
21
+ * (set by <BusinessUnitSwitcher /> or useSetCurrentBusinessUnit). Pass an
22
+ * explicit buId — or `null` to denote "no BU" — to bypass the context lookup.
23
+ */
24
+ export declare function useCan(): (action: PermissionInput) => boolean;
25
+ export declare function useCan(buId: string | null | undefined): (action: PermissionInput) => boolean;
26
+ /**
27
+ * Returns the raw RoadClient. Escape hatch for one-off calls or custom
28
+ * queries the typed hooks don't cover (Storybook, integration tests,
29
+ * extending the toolkit).
30
+ */
31
+ export declare function useRoadClient(): RoadClient;
32
+ export interface CanManyResult<P extends string> {
33
+ /** Per-permission result keyed by the input string. */
34
+ permissions: Record<P, boolean>;
35
+ /** True iff every permission in the list is granted. Empty list ⇒ true (vacuous). */
36
+ canAll: boolean;
37
+ /** True iff at least one permission in the list is granted. Empty list ⇒ false. */
38
+ canAny: boolean;
39
+ }
40
+ /**
41
+ * Batch permission check — convenient for pages that gate behavior on
42
+ * multiple actions at once. Returns a result with per-permission booleans
43
+ * plus canAll / canAny convenience flags.
44
+ *
45
+ * Calls Road's POST /iam/authorization/authorize/batch endpoint in one
46
+ * round-trip — the engine evaluates every permission with full semantics
47
+ * (manage → CRUD expansion, wildcards, scope inheritance), so this is
48
+ * strictly more accurate than useCan, which does in-memory string-match
49
+ * against the cached useMyPermissions list. React Query caches the
50
+ * result keyed by (buId, permissions), so repeated calls with the same
51
+ * list reuse the cache.
52
+ *
53
+ * Pass an `as const` array to get typed keys on the returned `permissions`
54
+ * record:
55
+ *
56
+ * const { permissions, canAll } = useCanMany([
57
+ * "manage:Role",
58
+ * "manage:Member",
59
+ * "manage:Invitation",
60
+ * ] as const);
61
+ * if (!canAll) return <ReadOnlyView />;
62
+ * permissions["manage:Role"]; // typed string literal
63
+ *
64
+ * Reads the current BU from CurrentBusinessUnitContext when buId is
65
+ * omitted. Pass an explicit buId — or `null` — to bypass the context;
66
+ * with no effective BU, every permission resolves to false (deny-by-
67
+ * default), matching useCan's behavior.
68
+ *
69
+ * While the batch query is loading, every permission also reads false —
70
+ * gated UI hides rather than flashes-visible-then-hidden, same trade-off
71
+ * useCan makes.
72
+ */
73
+ export declare function useCanMany<P extends RoadPermission | (string & {})>(actions: readonly P[]): CanManyResult<P>;
74
+ export declare function useCanMany<P extends RoadPermission | (string & {})>(actions: readonly P[], buId: string | null | undefined): CanManyResult<P>;
75
+ export declare function useBusinessUnit(buId: string | null): import('@tanstack/react-query').UseQueryResult<NoInfer<BusinessUnitDetail>, Error>;
76
+ /**
77
+ * Convenience hook that returns the first page of members for a BU
78
+ * unwrapped as `Member[]`. For BUs with thousands of members, prefer
79
+ * {@link useInfiniteMembers} and `loadMore`. With no buId the hook is
80
+ * disabled and `data` stays undefined (same as before pagination).
81
+ */
82
+ export declare function useMembers(buId: string | null): import('@tanstack/react-query').UseQueryResult<NoInfer<Member[]>, Error>;
83
+ /**
84
+ * Cursor-paginated members list. Wraps React Query's useInfiniteQuery so
85
+ * callers can flatten across pages or call `fetchNextPage()` from a
86
+ * "load more" button. `pagination.cursor` is the cursor for the next page
87
+ * — falsy when the last page has loaded.
88
+ */
89
+ export declare function useInfiniteMembers(buId: string | null, limit?: number): import('@tanstack/react-query').UseInfiniteQueryResult<InfiniteData<PaginatedList<Member>, unknown>, Error>;
90
+ /**
91
+ * Convenience hook that returns the first page of invitations for a BU
92
+ * unwrapped as `Invitation[]`. Prefer {@link useInfiniteInvitations}
93
+ * for BUs that may have many pending invitations historically.
94
+ */
95
+ export declare function useInvitations(buId: string | null): import('@tanstack/react-query').UseQueryResult<NoInfer<Invitation[]>, Error>;
96
+ /** Cursor-paginated invitations list (see {@link useInfiniteMembers}). */
97
+ export declare function useInfiniteInvitations(buId: string | null, limit?: number): import('@tanstack/react-query').UseInfiniteQueryResult<InfiniteData<PaginatedList<Invitation>, unknown>, Error>;
98
+ export declare function useCreateBusinessUnit(): import('@tanstack/react-query').UseMutationResult<BusinessUnitDetail, Error, CreateBusinessUnitInput, unknown>;
99
+ export declare function useUpdateBusinessUnit(buId: string): import('@tanstack/react-query').UseMutationResult<BusinessUnitDetail, Error, UpdateBusinessUnitInput, unknown>;
100
+ export declare function useSuspendMember(buId: string): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
101
+ export declare function useReinstateMember(buId: string): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
102
+ export declare function useRemoveMember(buId: string): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
103
+ export declare function useCreateInvitation(buId: string): import('@tanstack/react-query').UseMutationResult<Invitation, Error, CreateInvitationInput, unknown>;
104
+ export declare function useCancelInvitation(buId: string): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
105
+ export declare function useResendInvitation(buId: string): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
106
+ export declare function useAcceptInvitation(): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
107
+ export declare function useRejectInvitation(): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
108
+ /**
109
+ * First page of roles for a BU, unwrapped to `Role[]`. The role catalog
110
+ * is typically small (≤ a few dozen) so most callers don't need
111
+ * pagination — when they do, switch to {@link useInfiniteRoles}.
112
+ *
113
+ * Pass `limit` (up to 100, the API's max) to bump the first-page size
114
+ * when the caller needs all roles in one shot — eg. a picker that has
115
+ * to render every available role. For BUs with > 100 roles, use the
116
+ * infinite variant instead.
117
+ */
118
+ export declare function useRoles(buId: string | null, limit?: number): import('@tanstack/react-query').UseQueryResult<NoInfer<Role[]>, Error>;
119
+ /** Cursor-paginated roles list (see {@link useInfiniteMembers}). */
120
+ export declare function useInfiniteRoles(buId: string | null, limit?: number): import('@tanstack/react-query').UseInfiniteQueryResult<InfiniteData<PaginatedList<Role>, unknown>, Error>;
121
+ export declare function useRole(buId: string | null, roleId: string | null): import('@tanstack/react-query').UseQueryResult<NoInfer<Role>, Error>;
122
+ export declare function usePermissions(buId: string | null): import('@tanstack/react-query').UseQueryResult<NoInfer<Permission[]>, Error>;
123
+ export declare function useCreateRole(buId: string): import('@tanstack/react-query').UseMutationResult<Role, Error, CreateRoleInput, unknown>;
124
+ export declare function useUpdateRole(buId: string, roleId: string): import('@tanstack/react-query').UseMutationResult<Role, Error, UpdateRoleInput, unknown>;
125
+ export declare function useDeleteRole(buId: string): import('@tanstack/react-query').UseMutationResult<void, Error, string, unknown>;
126
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,121 @@
1
+ import { RoadClient } from './client';
2
+ import { Member } from './types';
3
+ export interface MockClientOptions {
4
+ /** Artificial network delay in ms. Default 350. */
5
+ latency?: number;
6
+ /** Force every call to fail with this error. Useful for testing error states. */
7
+ failWith?: string;
8
+ /** Seed with no BUs to demonstrate the empty state. */
9
+ empty?: boolean;
10
+ }
11
+ export declare function createMockClient(options?: MockClientOptions): RoadClient;
12
+ /** Failure scenarios that flip the mock into a known failure mode. */
13
+ export type MockScenario = "network-error" | "auth-error" | "rate-limit" | "server-error";
14
+ /**
15
+ * Fluent builder for `RoadClient` mocks. Each `with*` call mutates the
16
+ * draft state and returns `this` so chains compose. Call `.build()` to
17
+ * materialize a `RoadClient` bound to the assembled state.
18
+ *
19
+ * Defaults: starts empty (no BUs), latency 0ms, no scenario.
20
+ *
21
+ * ```ts
22
+ * const client = mockClientBuilder()
23
+ * .withLatency(50)
24
+ * .withBusinessUnit({ name: "Acme", role: "Owner" })
25
+ * .withMember("Acme", { name: "Alex", email: "alex@x.com", role: "Admin" })
26
+ * .withInvitation("Acme", { email: "pending@x.com", roleName: "Member" })
27
+ * .build();
28
+ * ```
29
+ */
30
+ export declare class MockClientBuilder {
31
+ private readonly draft;
32
+ constructor();
33
+ /** Set artificial network latency in ms. */
34
+ withLatency(ms: number): this;
35
+ /**
36
+ * Seed with the rich playground data (3 BUs with members, roles, an
37
+ * invitation, a pending cross-BU invitation). Replaces any prior
38
+ * `withBusinessUnit` calls in the chain.
39
+ */
40
+ withRichSeed(): this;
41
+ /**
42
+ * Add a business unit. `name` is required; everything else is derived
43
+ * with sensible defaults. `role` declares the current user's role
44
+ * within this BU — defaults to "Owner" with wildcard permissions.
45
+ */
46
+ withBusinessUnit(spec: {
47
+ id?: string;
48
+ name: string;
49
+ slug?: string;
50
+ role?: string;
51
+ memberLimit?: number | null;
52
+ /** Wildcard-grant the user — defaults to true for Owner / false otherwise. */
53
+ wildcard?: boolean;
54
+ }): this;
55
+ /** Add a member to an existing BU. `buName` matches `BusinessUnit.name`. */
56
+ withMember(buName: string, spec: {
57
+ name: string;
58
+ email: string;
59
+ role?: string;
60
+ status?: Member["status"];
61
+ }): this;
62
+ /** Add a custom role to a BU. The role does NOT auto-assign to anyone. */
63
+ withRole(buName: string, spec: {
64
+ name: string;
65
+ description?: string;
66
+ permissions: string[];
67
+ isSystem?: boolean;
68
+ }): this;
69
+ /** Add a pending invitation to a BU. */
70
+ withInvitation(buName: string, spec: {
71
+ email: string;
72
+ roleName: string;
73
+ expiresInDays?: number;
74
+ }): this;
75
+ /** Add a cross-BU pending invitation visible on the current user's surface. */
76
+ withPendingInvitation(spec: {
77
+ businessUnit: {
78
+ id?: string;
79
+ name: string;
80
+ slug?: string;
81
+ };
82
+ roleName: string;
83
+ expiresInDays?: number;
84
+ }): this;
85
+ /**
86
+ * Flip the mock into a failure scenario. Every call rejects with a
87
+ * shape matching the scenario (Error message keyed for assertions).
88
+ */
89
+ withScenario(scenario: MockScenario): this;
90
+ /**
91
+ * Wire an `onUnauthenticated` handler that the `auth-error`
92
+ * scenario fires (with the same 1s debounce HttpRoadClient uses).
93
+ * Lets tests assert the cookie-mode redirect path without standing
94
+ * up an HTTP fixture.
95
+ *
96
+ * ## Scope — what this does and does not do
97
+ *
98
+ * ✓ **Does**: fire `onUnauthenticated` on `withScenario("auth-error")`,
99
+ * debounced to once per 1s window, with handler-exception safety
100
+ * matching the production HttpRoadClient.
101
+ *
102
+ * ✗ **Does not** simulate cookie-mode transport behavior. The
103
+ * in-memory mock has no fetch, no `credentials: 'include'`, no
104
+ * `XSRF-TOKEN` echo, no `Authorization` header omission. Those are
105
+ * transport concerns and need an HTTP-level fixture to test
106
+ * honestly.
107
+ *
108
+ * For tests that need to assert HTTP-level cookie-mode behavior
109
+ * (CSRF echo, credentials, no Authorization), mock `globalThis.fetch`
110
+ * directly against `HttpRoadClient` — see
111
+ * `src/api/cookie-mode.test.ts` for the canonical pattern.
112
+ */
113
+ withCookieMode(opts?: {
114
+ onUnauthenticated?: () => void;
115
+ }): this;
116
+ /** Materialize the assembled state into a RoadClient. */
117
+ build(): RoadClient;
118
+ private requireBu;
119
+ }
120
+ /** Entry point for the fluent mock-client builder. See {@link MockClientBuilder}. */
121
+ export declare function mockClientBuilder(): MockClientBuilder;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type re-export shim. The canonical definitions live in @b1-road/types so
3
+ * every Road SDK reads from a single source of truth. Importing from here
4
+ * still works for backward-compat — new code should import directly from
5
+ * "@b1-road/types".
6
+ */
7
+ export * from '@b1-road/types';
@@ -0,0 +1,19 @@
1
+ import { CSSProperties } from 'react';
2
+ export interface AppearanceVariables {
3
+ colorPrimary?: string;
4
+ colorPrimaryForeground?: string;
5
+ colorBackground?: string;
6
+ colorForeground?: string;
7
+ colorMuted?: string;
8
+ colorAccent?: string;
9
+ colorDanger?: string;
10
+ colorBorder?: string;
11
+ borderRadius?: string;
12
+ fontFamily?: string;
13
+ }
14
+ export interface Appearance {
15
+ variables?: AppearanceVariables;
16
+ colorScheme?: "light" | "dark" | "auto";
17
+ }
18
+ export declare function appearanceToStyle(appearance?: Appearance): CSSProperties;
19
+ export declare function resolveColorScheme(scheme?: Appearance["colorScheme"]): "light" | "dark";
@@ -0,0 +1,15 @@
1
+ export interface BusinessUnitSwitcherProps {
2
+ /**
3
+ * Force the trigger into collapsed (true) or expanded (false) mode. When
4
+ * unset (default), the component auto-detects via ResizeObserver on its
5
+ * own container — under 180px wide it shows the compact icon-only trigger
6
+ * with a tooltip; at or above 180px it shows the expanded trigger (avatar
7
+ * + name + role + chevron). Hosts whose sidebar collapse state lives in
8
+ * app state pass the prop explicitly; hosts using a fluid container can
9
+ * leave it unset and let the switcher react to width.
10
+ */
11
+ collapsed?: boolean;
12
+ /** Additional class on the trigger button — useful for sidebar layout. */
13
+ className?: string;
14
+ }
15
+ export declare function BusinessUnitSwitcher({ collapsed, className }: BusinessUnitSwitcherProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { ReactNode } from 'react';
2
+ export type BusinessUnitsMgmtTab = "business-units" | "roles";
3
+ interface BusinessUnitsMgmtCommonProps {
4
+ /** Tab to show on each open. Defaults to "business-units". */
5
+ initialTab?: BusinessUnitsMgmtTab;
6
+ /**
7
+ * Deep-link into a specific business unit on each open — routes the Business
8
+ * Units tab to that BU's detail view instead of the list.
9
+ */
10
+ initialBusinessUnitId?: string;
11
+ }
12
+ /**
13
+ * Uncontrolled mode — component owns the open-state and renders a
14
+ * trigger (default or the one passed via `trigger`). `open` and
15
+ * `onOpenChange` MUST NOT be provided.
16
+ */
17
+ interface BusinessUnitsMgmtUncontrolledProps extends BusinessUnitsMgmtCommonProps {
18
+ trigger?: ReactNode;
19
+ open?: never;
20
+ onOpenChange?: never;
21
+ }
22
+ /**
23
+ * Controlled mode — host owns the open-state and provides BOTH `open`
24
+ * and `onOpenChange`. The discriminated union enforces this pairing at
25
+ * compile time: `{ open: true }` without `onOpenChange` is a type
26
+ * error.
27
+ */
28
+ interface BusinessUnitsMgmtControlledProps extends BusinessUnitsMgmtCommonProps {
29
+ open: boolean;
30
+ onOpenChange: (open: boolean) => void;
31
+ trigger?: never;
32
+ }
33
+ export type BusinessUnitsMgmtProps = BusinessUnitsMgmtUncontrolledProps | BusinessUnitsMgmtControlledProps;
34
+ export declare function BusinessUnitsMgmt({ trigger, open, onOpenChange, initialTab, initialBusinessUnitId, }: BusinessUnitsMgmtProps): import("react/jsx-runtime").JSX.Element;
35
+ export {};
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ buId: string;
3
+ onBack: () => void;
4
+ }
5
+ export declare function BusinessUnitDetail({ buId, onBack }: Props): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,5 @@
1
+ interface Props {
2
+ onSelect: (buId: string) => void;
3
+ }
4
+ export declare function BusinessUnitList({ onSelect }: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Membership } from '../../api/types';
2
+ interface Props {
3
+ membership: Membership;
4
+ onSelect: (buId: string) => void;
5
+ }
6
+ export declare function BusinessUnitRow({ membership, onSelect }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,5 @@
1
+ interface Props {
2
+ buId: string;
3
+ }
4
+ export declare function BusinessUnitSettings({ buId }: Props): import("react/jsx-runtime").JSX.Element | null;
5
+ export {};
@@ -0,0 +1,5 @@
1
+ export interface BusinessUnitsTabProps {
2
+ /** Initial BU to deep-link into — opens the detail view instead of the list. */
3
+ initialBuId?: string | null;
4
+ }
5
+ export declare function BusinessUnitsTab({ initialBuId }?: BusinessUnitsTabProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ onCancel: () => void;
3
+ onCreated: (buId: string) => void;
4
+ }
5
+ export declare function CreateBusinessUnitForm({ onCancel, onCreated }: Props): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,5 @@
1
+ interface Props {
2
+ buId: string;
3
+ }
4
+ export declare function MembersList({ buId }: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,15 @@
1
+ import { PendingInvitation } from '../../api/types';
2
+ /**
3
+ * The "invitation inbox" — pending BU invitations surfaced at the top of the
4
+ * business-units tab (and as the hero for a freshly-invited user who has no
5
+ * memberships yet). Each card shows the business unit, the offered role, and a
6
+ * humanized expiry, with one-click Accept / Decline.
7
+ *
8
+ * Data + actions come from {@link useAcceptInvitation} / {@link useRejectInvitation},
9
+ * which invalidate the my-business-units query on success — so an accepted BU
10
+ * slides into the list below and a declined one disappears, with no manual
11
+ * refetch wiring at the call site.
12
+ */
13
+ export declare function PendingInvitations({ invitations, }: {
14
+ invitations: PendingInvitation[];
15
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,7 @@
1
+ import { Invitation } from '../../api/types';
2
+ interface Props {
3
+ buId: string;
4
+ invitations: Invitation[];
5
+ }
6
+ export declare function InvitationsList({ buId, invitations }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,5 @@
1
+ interface Props {
2
+ buId: string;
3
+ }
4
+ export declare function InvitationsTab({ buId }: Props): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ buId: string;
3
+ onCancel: () => void;
4
+ onInvited: () => void;
5
+ }
6
+ export declare function InviteForm({ buId, onCancel, onInvited }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,8 @@
1
+ import { BusinessUnitSummary } from '../../api/types';
2
+ interface Props {
3
+ businessUnits: BusinessUnitSummary[];
4
+ value: string;
5
+ onChange: (buId: string) => void;
6
+ }
7
+ export declare function BUSelector({ businessUnits, value, onChange }: Props): import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ buId: string;
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ onCreated: (roleId: string) => void;
6
+ }
7
+ export declare function CreateRoleDialog({ buId, open, onOpenChange, onCreated }: Props): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,10 @@
1
+ import { Permission } from '../../api/types';
2
+ interface Props {
3
+ permissions: Permission[];
4
+ /** Currently selected permission codes. Use ["*"] to indicate wildcard (all). */
5
+ value: string[];
6
+ onChange: (next: string[]) => void;
7
+ disabled?: boolean;
8
+ }
9
+ export declare function PermissionPicker({ permissions, value, onChange, disabled }: Props): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ buId: string;
3
+ roleId: string;
4
+ onBack: () => void;
5
+ }
6
+ export declare function RoleEditor({ buId, roleId, onBack }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ import { Role } from '../../api/types';
2
+ interface Props {
3
+ role: Role;
4
+ onSelect: (roleId: string) => void;
5
+ }
6
+ export declare function RoleRow({ role, onSelect }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ buId: string;
3
+ onSelect: (roleId: string) => void;
4
+ onCreate: () => void;
5
+ }
6
+ export declare function RolesList({ buId, onSelect, onCreate }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1 @@
1
+ export declare function RolesTab(): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,7 @@
1
+ interface AvatarProps {
2
+ name: string;
3
+ size?: "sm" | "md" | "lg";
4
+ className?: string;
5
+ }
6
+ export declare function Avatar({ name, size, className }: AvatarProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ interface EmptyStateProps {
3
+ icon?: ReactNode;
4
+ title: string;
5
+ description?: string;
6
+ action?: ReactNode;
7
+ className?: string;
8
+ }
9
+ export declare function EmptyState({ icon, title, description, action, className }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
10
+ export {};
@@ -0,0 +1,18 @@
1
+ interface Props {
2
+ /** Number of items already rendered across all loaded pages. */
3
+ shown: number;
4
+ /** Total row count from `pagination.totalCount`. */
5
+ total: number;
6
+ hasNextPage: boolean;
7
+ isFetchingNextPage: boolean;
8
+ /** Bound to `useInfiniteQuery().fetchNextPage`. */
9
+ onLoadMore: () => void;
10
+ }
11
+ /**
12
+ * Shared footer for paginated lists. Renders nothing when total is 0,
13
+ * shows a "X of Y" count, and renders a "Load more" button when more
14
+ * pages remain. Hides the button (but keeps the count) once the last
15
+ * page has loaded.
16
+ */
17
+ export declare function LoadMoreFooter({ shown, total, hasNextPage, isFetchingNextPage, onLoadMore, }: Props): import("react/jsx-runtime").JSX.Element | null;
18
+ export {};