@elevasis/ui 1.2.1 → 1.3.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 (52) hide show
  1. package/dist/api/hooks/useApiClient.d.ts +54 -0
  2. package/dist/api/hooks/useApiClient.d.ts.map +1 -0
  3. package/dist/api/hooks/useApiClient.js +185 -0
  4. package/dist/api/index.d.ts +49 -11
  5. package/dist/api/index.js +3 -2
  6. package/dist/auth/index.d.ts +88 -2
  7. package/dist/auth/index.js +5 -1
  8. package/dist/{chunk-5UWFGBFM.js → chunk-4KAG5U7A.js} +18 -4
  9. package/dist/chunk-4VGWQ5AN.js +91 -0
  10. package/dist/{chunk-JKERRYVS.js → chunk-BLO4SISK.js} +7 -3
  11. package/dist/chunk-BWCC6ZJC.js +202 -0
  12. package/dist/{chunk-WNWKOCGJ.js → chunk-BZTA7IIL.js} +1 -1
  13. package/dist/chunk-DD3CCMCZ.js +15 -0
  14. package/dist/{chunk-GEFB5YIR.js → chunk-EZMRFWZQ.js} +1 -1
  15. package/dist/chunk-FDCVFCOQ.js +105 -0
  16. package/dist/chunk-FLJXZ7YC.js +150 -0
  17. package/dist/{chunk-7AI5ZYJ4.js → chunk-JVAZHVNV.js} +2 -94
  18. package/dist/{chunk-ZGHDPDTF.js → chunk-JYSYHVLU.js} +3 -3
  19. package/dist/{chunk-J3FALDQE.js → chunk-NXHL23JW.js} +7 -13
  20. package/dist/{chunk-OUHGHTE7.js → chunk-O3PY6B6E.js} +3 -2
  21. package/dist/{chunk-ZQVPUAGR.js → chunk-OLD3NQLI.js} +33 -31
  22. package/dist/{chunk-B64YDSAY.js → chunk-PCBXNHKY.js} +53 -97
  23. package/dist/chunk-QQOLC46E.js +75 -0
  24. package/dist/chunk-RNP5R5I3.js +1 -0
  25. package/dist/{chunk-YULUKCS6.js → chunk-SITSZUFW.js} +1 -1
  26. package/dist/chunk-TIRMFDM4.js +33 -0
  27. package/dist/{chunk-PYL4XW6H.js → chunk-TMFCNFLW.js} +1 -1
  28. package/dist/{chunk-S66I2PYB.js → chunk-TN3PU2WK.js} +1 -1
  29. package/dist/components/command-queue/index.js +6 -4
  30. package/dist/components/index.js +9 -7
  31. package/dist/components/notifications/index.js +4 -3
  32. package/dist/display/index.js +3 -2
  33. package/dist/hooks/index.d.ts +1 -1
  34. package/dist/hooks/index.js +5 -4
  35. package/dist/hooks/published.d.ts +1 -1
  36. package/dist/hooks/published.js +4 -3
  37. package/dist/index.d.ts +447 -117
  38. package/dist/index.js +22 -16
  39. package/dist/initialization/index.d.ts +49 -1
  40. package/dist/initialization/index.js +5 -2
  41. package/dist/organization/index.d.ts +61 -2
  42. package/dist/organization/index.js +5 -2
  43. package/dist/profile/index.d.ts +30 -2
  44. package/dist/profile/index.js +2 -1
  45. package/dist/provider/index.d.ts +112 -27
  46. package/dist/provider/index.js +11 -6
  47. package/dist/provider/published.d.ts +85 -13
  48. package/dist/provider/published.js +10 -4
  49. package/dist/utils/index.js +2 -1
  50. package/package.json +17 -4
  51. package/dist/chunk-GDV44UWF.js +0 -138
  52. package/dist/chunk-HBRMWW6V.js +0 -43
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Return type of useOrganizations hook (subset needed by the factory)
3
+ */
4
+ interface UseOrganizationsReturn {
5
+ isInitializing: boolean;
6
+ isOrgRefreshing: boolean;
7
+ }
8
+ /**
9
+ * Hook that returns apiRequest and deferredApiRequest bound to the current
10
+ * ApiClientContext. apiUrl is the only parameter because org ID is resolved
11
+ * at call time via getOrganizationId() from context.
12
+ *
13
+ * Usage:
14
+ * ```typescript
15
+ * import { useApiClient } from '@repo/ui/api'
16
+ *
17
+ * const API_URL = import.meta.env.VITE_API_SERVER || 'http://localhost:5170'
18
+ *
19
+ * function useMyClient() {
20
+ * return useApiClient(API_URL)
21
+ * }
22
+ * ```
23
+ */
24
+ export declare function useApiClient(apiUrl: string): {
25
+ apiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>;
26
+ deferredApiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>;
27
+ isOrganizationReady: boolean;
28
+ isInitializing: boolean;
29
+ };
30
+ /**
31
+ * Factory function to create a useApiClient hook for your app.
32
+ *
33
+ * @deprecated Use the plain `useApiClient(apiUrl)` hook instead.
34
+ * The factory pattern is no longer needed because org ID is resolved at
35
+ * call time via getOrganizationId() from ApiClientContext.
36
+ *
37
+ * Migration:
38
+ * ```typescript
39
+ * // Before
40
+ * export const useApiClient = createUseApiClient(useOrganizations, API_URL)
41
+ *
42
+ * // After
43
+ * import { useApiClient } from '@repo/ui/api'
44
+ * // call useApiClient(API_URL) directly in your hook/component
45
+ * ```
46
+ */
47
+ export declare function createUseApiClient(useOrganizations: () => UseOrganizationsReturn, apiUrl: string): () => {
48
+ apiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>;
49
+ deferredApiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>;
50
+ isOrganizationReady: boolean;
51
+ isInitializing: boolean;
52
+ };
53
+ export {};
54
+ //# sourceMappingURL=useApiClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useApiClient.d.ts","sourceRoot":"","sources":["../../../src/api/hooks/useApiClient.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,UAAU,sBAAsB;IAC9B,cAAc,EAAE,OAAO,CAAA;IACvB,eAAe,EAAE,OAAO,CAAA;CACzB;AA+FD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;iBA+BtC,CAAC,YAAY,MAAM,YAAW,WAAW,KAAQ,OAAO,CAAC,CAAC,CAAC;yBAMrD,CAAC,YAAY,MAAM,YAAW,WAAW,KAAQ,OAAO,CAAC,CAAC,CAAC;;;EAerE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,sBAAsB,EAAE,MAAM,EAAE,MAAM;iBAqC1F,CAAC,YAAY,MAAM,YAAW,WAAW,KAAQ,OAAO,CAAC,CAAC,CAAC;yBAMrD,CAAC,YAAY,MAAM,YAAW,WAAW,KAAQ,OAAO,CAAC,CAAC,CAAC;;;EAqBvE"}
@@ -0,0 +1,185 @@
1
+ import { useCallback } from 'react';
2
+ import { HTTP_HEADERS } from '@repo/core';
3
+ import { APIClientError } from '../../utils';
4
+ import { useApiClientContext } from '../context/ApiClientProvider';
5
+ const DEFAULT_TIMEOUT_MS = 60_000;
6
+ // ---------------------------------------------------------------------------
7
+ // Shared request logic builder — used by both the plain hook and the factory.
8
+ // ---------------------------------------------------------------------------
9
+ function buildApiRequest(getAccessToken, getOrganizationId, apiUrl, handleResponseError) {
10
+ return async function apiRequest(endpoint, options = {}) {
11
+ try {
12
+ // Get the access token from context
13
+ const token = await getAccessToken();
14
+ // Read org ID at call time — reflects org switches without re-render
15
+ const organizationId = getOrganizationId();
16
+ // DEBUG
17
+ if (organizationId)
18
+ console.log('[apiRequest]', endpoint, 'org:', organizationId);
19
+ // Only set Content-Type if there's a body
20
+ const headers = {
21
+ ...(options.body ? { 'Content-Type': 'application/json' } : {}),
22
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
23
+ ...(organizationId && { [HTTP_HEADERS.WORKOS_ORGANIZATION_ID]: organizationId }),
24
+ ...options.headers
25
+ };
26
+ const timeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
27
+ const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
28
+ const response = await fetch(`${apiUrl}/api${endpoint}`, {
29
+ ...options,
30
+ headers,
31
+ signal
32
+ });
33
+ if (!response.ok) {
34
+ await handleResponseError(endpoint, response, options);
35
+ }
36
+ // Handle 204 No Content responses
37
+ if (response.status === 204) {
38
+ return undefined;
39
+ }
40
+ const data = await response.json();
41
+ // DEBUG
42
+ if (organizationId && endpoint.includes('dashboard'))
43
+ console.log('[apiResponse] dashboard:', data);
44
+ return data;
45
+ }
46
+ catch (error) {
47
+ // If getAccessToken fails, still make the request without auth
48
+ // This allows public endpoints to work
49
+ if (error instanceof Error && error.message.includes('No access token')) {
50
+ // Only set Content-Type if there's a body
51
+ const headers = {
52
+ ...(options.body ? { 'Content-Type': 'application/json' } : {}),
53
+ ...options.headers
54
+ };
55
+ const fallbackTimeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
56
+ const fallbackSignal = options.signal
57
+ ? AbortSignal.any([options.signal, fallbackTimeoutSignal])
58
+ : fallbackTimeoutSignal;
59
+ const response = await fetch(`${apiUrl}/api${endpoint}`, {
60
+ ...options,
61
+ headers,
62
+ signal: fallbackSignal
63
+ });
64
+ if (!response.ok) {
65
+ await handleResponseError(endpoint, response, options);
66
+ }
67
+ if (response.status === 204) {
68
+ return undefined;
69
+ }
70
+ return response.json();
71
+ }
72
+ throw error;
73
+ }
74
+ };
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Plain hook — reads everything from ApiClientContext directly.
78
+ // apiUrl must be provided because the context does not store it.
79
+ // ---------------------------------------------------------------------------
80
+ /**
81
+ * Hook that returns apiRequest and deferredApiRequest bound to the current
82
+ * ApiClientContext. apiUrl is the only parameter because org ID is resolved
83
+ * at call time via getOrganizationId() from context.
84
+ *
85
+ * Usage:
86
+ * ```typescript
87
+ * import { useApiClient } from '@repo/ui/api'
88
+ *
89
+ * const API_URL = import.meta.env.VITE_API_SERVER || 'http://localhost:5170'
90
+ *
91
+ * function useMyClient() {
92
+ * return useApiClient(API_URL)
93
+ * }
94
+ * ```
95
+ */
96
+ export function useApiClient(apiUrl) {
97
+ const { getAccessToken, getOrganizationId, isOrganizationReady, onError } = useApiClientContext();
98
+ const handleResponseError = useCallback(async (endpoint, response, options) => {
99
+ const errorData = await response.json().catch(() => ({
100
+ error: `HTTP ${response.status}`,
101
+ code: 'INTERNAL_SERVER_ERROR'
102
+ }));
103
+ if (response.status >= 500 && onError) {
104
+ onError(endpoint, new Error(errorData.error), {
105
+ method: options.method || 'GET',
106
+ statusCode: response.status,
107
+ requestId: errorData.requestId
108
+ });
109
+ }
110
+ throw new APIClientError(errorData.error, errorData.code, response.status, errorData.requestId, errorData.fields, errorData.retryAfter);
111
+ }, [onError]);
112
+ const apiRequest = useCallback((endpoint, options = {}) => buildApiRequest(getAccessToken, getOrganizationId, apiUrl, handleResponseError)(endpoint, options), [getAccessToken, getOrganizationId, apiUrl, handleResponseError]);
113
+ const deferredApiRequest = useCallback(async (endpoint, options = {}) => {
114
+ if (!isOrganizationReady) {
115
+ throw new Error('No organization selected. Please select an organization.');
116
+ }
117
+ return apiRequest(endpoint, options);
118
+ }, [apiRequest, isOrganizationReady]);
119
+ return {
120
+ apiRequest,
121
+ deferredApiRequest,
122
+ isOrganizationReady,
123
+ isInitializing: false
124
+ };
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Factory — kept for backwards compatibility during migration.
128
+ // ---------------------------------------------------------------------------
129
+ /**
130
+ * Factory function to create a useApiClient hook for your app.
131
+ *
132
+ * @deprecated Use the plain `useApiClient(apiUrl)` hook instead.
133
+ * The factory pattern is no longer needed because org ID is resolved at
134
+ * call time via getOrganizationId() from ApiClientContext.
135
+ *
136
+ * Migration:
137
+ * ```typescript
138
+ * // Before
139
+ * export const useApiClient = createUseApiClient(useOrganizations, API_URL)
140
+ *
141
+ * // After
142
+ * import { useApiClient } from '@repo/ui/api'
143
+ * // call useApiClient(API_URL) directly in your hook/component
144
+ * ```
145
+ */
146
+ export function createUseApiClient(useOrganizations, apiUrl) {
147
+ return function useApiClientCompat() {
148
+ const { getAccessToken, getOrganizationId, isOrganizationReady, onError } = useApiClientContext();
149
+ const { isInitializing, isOrgRefreshing } = useOrganizations();
150
+ /**
151
+ * Handle API response errors
152
+ * Logs 5xx errors via onError callback and throws APIClientError
153
+ */
154
+ const handleResponseError = useCallback(async (endpoint, response, options) => {
155
+ const errorData = await response.json().catch(() => ({
156
+ error: `HTTP ${response.status}`,
157
+ code: 'INTERNAL_SERVER_ERROR'
158
+ }));
159
+ if (response.status >= 500 && onError) {
160
+ onError(endpoint, new Error(errorData.error), {
161
+ method: options.method || 'GET',
162
+ statusCode: response.status,
163
+ requestId: errorData.requestId
164
+ });
165
+ }
166
+ throw new APIClientError(errorData.error, errorData.code, response.status, errorData.requestId, errorData.fields, errorData.retryAfter);
167
+ }, [onError]);
168
+ const apiRequest = useCallback((endpoint, options = {}) => buildApiRequest(getAccessToken, getOrganizationId, apiUrl, handleResponseError)(endpoint, options), [getAccessToken, getOrganizationId, handleResponseError]);
169
+ const deferredApiRequest = useCallback(async (endpoint, options = {}) => {
170
+ if (isInitializing || isOrgRefreshing) {
171
+ throw new Error('Organization context is still initializing. Please wait.');
172
+ }
173
+ if (!isOrganizationReady) {
174
+ throw new Error('No organization selected. Please select an organization.');
175
+ }
176
+ return apiRequest(endpoint, options);
177
+ }, [apiRequest, isInitializing, isOrgRefreshing, isOrganizationReady]);
178
+ return {
179
+ apiRequest,
180
+ deferredApiRequest,
181
+ isOrganizationReady,
182
+ isInitializing: isInitializing || isOrgRefreshing
183
+ };
184
+ };
185
+ }
@@ -5,8 +5,10 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  */
6
6
  interface ApiClientContextValue {
7
7
  getAccessToken: () => Promise<string | undefined>;
8
+ /** @deprecated Read via getOrganizationId() instead. Kept for downstream context consumers during migration. */
8
9
  organizationId: string | null;
9
10
  isOrganizationReady: boolean;
11
+ getOrganizationId: () => string | null;
10
12
  onError?: (endpoint: string, error: Error, details?: ApiErrorDetails) => void;
11
13
  }
12
14
  interface ApiErrorDetails {
@@ -17,7 +19,19 @@ interface ApiErrorDetails {
17
19
  interface ApiClientProviderProps {
18
20
  children: React.ReactNode;
19
21
  getAccessToken: () => Promise<string | undefined>;
20
- organizationId: string | null;
22
+ /**
23
+ * Static org ID. Used when org ID is stable at render time.
24
+ * Cannot reflect org switches without re-rendering the provider.
25
+ * Prefer getOrganizationId for dynamic org context.
26
+ * @deprecated Pass getOrganizationId instead for dynamic org resolution.
27
+ */
28
+ organizationId?: string | null;
29
+ /**
30
+ * Callback invoked on every request to read the current org ID.
31
+ * Takes precedence over organizationId when provided.
32
+ * Allows org switching without re-rendering the provider tree.
33
+ */
34
+ getOrganizationId?: () => string | null;
21
35
  isOrganizationReady: boolean;
22
36
  onError?: (endpoint: string, error: Error, details?: ApiErrorDetails) => void;
23
37
  }
@@ -52,28 +66,52 @@ declare function useApiClientContext(): ApiClientContextValue;
52
66
  * }
53
67
  * ```
54
68
  */
55
- declare function ApiClientProvider({ children, getAccessToken, organizationId, isOrganizationReady, onError }: ApiClientProviderProps): react_jsx_runtime.JSX.Element;
69
+ declare function ApiClientProvider({ children, getAccessToken, organizationId, getOrganizationId: getOrganizationIdProp, isOrganizationReady, onError }: ApiClientProviderProps): react_jsx_runtime.JSX.Element;
56
70
 
57
71
  /**
58
- * Return type of useOrganizations hook (subset needed by useApiClient)
72
+ * Return type of useOrganizations hook (subset needed by the factory)
59
73
  */
60
74
  interface UseOrganizationsReturn {
61
75
  isInitializing: boolean;
62
76
  isOrgRefreshing: boolean;
63
77
  }
64
78
  /**
65
- * Factory function to create a useApiClient hook for your app.
66
- *
67
- * This pattern allows the shared package to provide the hook logic
68
- * while each app provides its own useOrganizations hook and API URL.
79
+ * Hook that returns apiRequest and deferredApiRequest bound to the current
80
+ * ApiClientContext. apiUrl is the only parameter because org ID is resolved
81
+ * at call time via getOrganizationId() from context.
69
82
  *
70
- * Usage in app:
83
+ * Usage:
71
84
  * ```typescript
72
- * import { createUseApiClient } from '@repo/ui/api'
73
- * import { useOrganizations } from './organization/hooks/useOrganizations'
85
+ * import { useApiClient } from '@repo/ui/api'
74
86
  *
75
87
  * const API_URL = import.meta.env.VITE_API_SERVER || 'http://localhost:5170'
88
+ *
89
+ * function useMyClient() {
90
+ * return useApiClient(API_URL)
91
+ * }
92
+ * ```
93
+ */
94
+ declare function useApiClient(apiUrl: string): {
95
+ apiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>;
96
+ deferredApiRequest: <T>(endpoint: string, options?: RequestInit) => Promise<T>;
97
+ isOrganizationReady: boolean;
98
+ isInitializing: boolean;
99
+ };
100
+ /**
101
+ * Factory function to create a useApiClient hook for your app.
102
+ *
103
+ * @deprecated Use the plain `useApiClient(apiUrl)` hook instead.
104
+ * The factory pattern is no longer needed because org ID is resolved at
105
+ * call time via getOrganizationId() from ApiClientContext.
106
+ *
107
+ * Migration:
108
+ * ```typescript
109
+ * // Before
76
110
  * export const useApiClient = createUseApiClient(useOrganizations, API_URL)
111
+ *
112
+ * // After
113
+ * import { useApiClient } from '@repo/ui/api'
114
+ * // call useApiClient(API_URL) directly in your hook/component
77
115
  * ```
78
116
  */
79
117
  declare function createUseApiClient(useOrganizations: () => UseOrganizationsReturn, apiUrl: string): () => {
@@ -83,5 +121,5 @@ declare function createUseApiClient(useOrganizations: () => UseOrganizationsRetu
83
121
  isInitializing: boolean;
84
122
  };
85
123
 
86
- export { ApiClientProvider, createUseApiClient, useApiClientContext };
124
+ export { ApiClientProvider, createUseApiClient, useApiClient, useApiClientContext };
87
125
  export type { ApiClientContextValue, ApiClientProviderProps, ApiErrorDetails };
package/dist/api/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  import '../chunk-XCYKC6OZ.js';
2
- export { ApiClientProvider, createUseApiClient, useApiClientContext } from '../chunk-GDV44UWF.js';
3
- import '../chunk-7AI5ZYJ4.js';
2
+ export { ApiClientProvider, createUseApiClient, useApiClient, useApiClientContext } from '../chunk-BWCC6ZJC.js';
3
+ import '../chunk-JVAZHVNV.js';
4
+ import '../chunk-4VGWQ5AN.js';
@@ -83,5 +83,91 @@ declare function useStableAccessToken(): () => Promise<string>;
83
83
  */
84
84
  declare function useSessionCheck(): void;
85
85
 
86
- export { AuthProvider, WorkOSAuthBridge, useAuthContext, useOAuthContext, useSessionCheck as useRefocusSessionCheck, useSessionCheck, useStableAccessToken };
87
- export type { AuthAdapter, OAuthContextValue };
86
+ interface InitializationError {
87
+ layer: 'auth' | 'profile' | 'organization';
88
+ message: string;
89
+ originalError?: Error;
90
+ }
91
+
92
+ interface ProtectedRouteProps {
93
+ children: ReactNode;
94
+ /**
95
+ * Path to redirect to when user is unauthenticated.
96
+ * @default '/login'
97
+ */
98
+ redirectTo?: string;
99
+ /**
100
+ * Rendered while initialization is in progress.
101
+ * When not provided, nothing is rendered during initialization.
102
+ */
103
+ fallback?: ReactNode;
104
+ /**
105
+ * Rendered when initialization fails with a non-organization error.
106
+ * Receives the error and a retry callback.
107
+ * When not provided, the error is silently swallowed (children not rendered).
108
+ */
109
+ errorFallback?: (error: InitializationError, retry: () => void) => ReactNode;
110
+ /**
111
+ * When true (default), waits for both user AND organization to be ready
112
+ * before rendering children. When false, only waits for user readiness.
113
+ * @default true
114
+ */
115
+ waitForOrganization?: boolean;
116
+ }
117
+ /**
118
+ * Headless route guard for authenticated pages.
119
+ *
120
+ * Reads initialization state from the nearest InitializationProvider.
121
+ * Redirects unauthenticated users to `redirectTo` (default: '/login')
122
+ * with a `returnTo` search param preserving the current location.
123
+ *
124
+ * Organization-layer errors are allowed through — routes like /invitations
125
+ * must be accessible even when the user has no org membership.
126
+ *
127
+ * @example
128
+ * // With custom fallback (your Mantine loader):
129
+ * <ProtectedRoute fallback={<AppShellLoader />}>
130
+ * <DashboardPage />
131
+ * </ProtectedRoute>
132
+ *
133
+ * @example
134
+ * // Wait only for user, not org (e.g. /invitations route):
135
+ * <ProtectedRoute waitForOrganization={false}>
136
+ * <InvitationsPage />
137
+ * </ProtectedRoute>
138
+ */
139
+ declare function ProtectedRoute({ children, redirectTo, fallback, errorFallback, waitForOrganization }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
140
+
141
+ interface AdminGuardProps {
142
+ children: ReactNode;
143
+ /**
144
+ * Path to redirect non-admin users to.
145
+ * @default '/'
146
+ */
147
+ redirectTo?: string;
148
+ /**
149
+ * Rendered while user readiness is being determined.
150
+ * When not provided, nothing is rendered during initialization.
151
+ */
152
+ fallback?: ReactNode;
153
+ }
154
+ /**
155
+ * Headless route guard for platform admin pages.
156
+ *
157
+ * Reads `profile.is_platform_admin` from the nearest InitializationProvider.
158
+ * Non-admin users are redirected to `redirectTo` (default: '/').
159
+ *
160
+ * Must be nested inside a ProtectedRoute (or equivalent) so that
161
+ * `userReady` is guaranteed to be true when this guard runs.
162
+ *
163
+ * @example
164
+ * <ProtectedRoute>
165
+ * <AdminGuard fallback={<AppShellLoader />}>
166
+ * <AdminDashboard />
167
+ * </AdminGuard>
168
+ * </ProtectedRoute>
169
+ */
170
+ declare function AdminGuard({ children, redirectTo, fallback }: AdminGuardProps): react_jsx_runtime.JSX.Element | null;
171
+
172
+ export { AdminGuard, AuthProvider, ProtectedRoute, WorkOSAuthBridge, useAuthContext, useOAuthContext, useSessionCheck as useRefocusSessionCheck, useSessionCheck, useStableAccessToken };
173
+ export type { AdminGuardProps, AuthAdapter, OAuthContextValue, ProtectedRouteProps };
@@ -1,4 +1,8 @@
1
- export { useSessionCheck as useRefocusSessionCheck, useSessionCheck, useStableAccessToken } from '../chunk-HBRMWW6V.js';
1
+ export { AdminGuard, ProtectedRoute, useSessionCheck as useRefocusSessionCheck, useSessionCheck, useStableAccessToken } from '../chunk-FDCVFCOQ.js';
2
2
  export { WorkOSAuthBridge } from '../chunk-NIAVTSMB.js';
3
3
  export { useOAuthContext } from '../chunk-QSVZP2NU.js';
4
+ import '../chunk-QQOLC46E.js';
5
+ import '../chunk-DD3CCMCZ.js';
6
+ import '../chunk-4KAG5U7A.js';
7
+ import '../chunk-KA7LO7U5.js';
4
8
  export { AuthProvider, useAuthContext } from '../chunk-7PLEQFHO.js';
@@ -1,6 +1,6 @@
1
1
  import { useElevasisServices } from './chunk-KA7LO7U5.js';
2
2
  import { useAuthContext } from './chunk-7PLEQFHO.js';
3
- import { useState, useEffect } from 'react';
3
+ import { createContext, useState, useEffect, useContext, createElement } from 'react';
4
4
 
5
5
  // src/profile/services/UserProfileService.ts
6
6
  var UserProfileService = class {
@@ -65,8 +65,6 @@ var UserProfileService = class {
65
65
  }
66
66
  }
67
67
  };
68
-
69
- // src/profile/hooks/useUserProfile.ts
70
68
  var useUserProfile = (options) => {
71
69
  const { user, isLoading: authLoading } = useAuthContext();
72
70
  const { apiRequest } = useElevasisServices();
@@ -125,5 +123,21 @@ var useUserProfile = (options) => {
125
123
  refetch
126
124
  };
127
125
  };
126
+ var ProfileContext = createContext(null);
127
+ function useProfile() {
128
+ const ctx = useContext(ProfileContext);
129
+ if (!ctx) {
130
+ throw new Error(
131
+ "useProfile must be used within a ProfileProvider. Wrap your app (or the relevant subtree) with <ProfileProvider>."
132
+ );
133
+ }
134
+ return ctx;
135
+ }
136
+ function ProfileProvider({ children }) {
137
+ const value = useUserProfile();
138
+ if (!value.loading && value.profile) console.log("[ProfileProvider] ready, profile:", value.profile.id);
139
+ if (!value.loading && value.error) console.log("[ProfileProvider] error:", value.error.message);
140
+ return createElement(ProfileContext.Provider, { value }, children);
141
+ }
128
142
 
129
- export { UserProfileService, useUserProfile };
143
+ export { ProfileProvider, UserProfileService, useProfile, useUserProfile };
@@ -0,0 +1,91 @@
1
+ // src/utils/error-utils.ts
2
+ var APIClientError = class extends Error {
3
+ statusCode;
4
+ code;
5
+ requestId;
6
+ fields;
7
+ retryAfter;
8
+ constructor(message, code, statusCode, requestId, fields, retryAfter) {
9
+ super(message);
10
+ this.name = "APIClientError";
11
+ this.code = code;
12
+ this.statusCode = statusCode;
13
+ this.requestId = requestId;
14
+ this.fields = fields;
15
+ this.retryAfter = retryAfter;
16
+ }
17
+ };
18
+ function isAPIClientError(error) {
19
+ return error instanceof APIClientError;
20
+ }
21
+ function getErrorInfo(error) {
22
+ if (isAPIClientError(error)) {
23
+ return {
24
+ message: error.message,
25
+ code: error.code,
26
+ requestId: error.requestId,
27
+ statusCode: error.statusCode,
28
+ fields: error.fields,
29
+ retryAfter: error.retryAfter
30
+ };
31
+ }
32
+ if (error instanceof Error) {
33
+ return {
34
+ message: error.message
35
+ };
36
+ }
37
+ return {
38
+ message: String(error)
39
+ };
40
+ }
41
+ function getErrorTitle(code) {
42
+ if (!code) return "Error";
43
+ switch (code) {
44
+ case "VALIDATION_ERROR":
45
+ return "Validation Error";
46
+ case "AUTHENTICATION_FAILED":
47
+ return "Authentication Required";
48
+ case "FORBIDDEN":
49
+ return "Access Denied";
50
+ case "NOT_FOUND":
51
+ return "Not Found";
52
+ case "CONFLICT":
53
+ return "Conflict";
54
+ case "RATE_LIMIT_EXCEEDED":
55
+ return "Rate Limit Exceeded";
56
+ case "INTERNAL_SERVER_ERROR":
57
+ return "Server Error";
58
+ case "SERVICE_UNAVAILABLE":
59
+ return "Service Temporarily Unavailable";
60
+ default:
61
+ return "Error";
62
+ }
63
+ }
64
+ function formatErrorMessage(message, requestId, fields, retryAfter) {
65
+ let formatted = message;
66
+ if (fields && Object.keys(fields).length > 0) {
67
+ const fieldErrors = [];
68
+ for (const [field, errors] of Object.entries(fields)) {
69
+ const fieldName = field === "root" || field === "_root" ? "Request" : field;
70
+ errors.forEach((error) => {
71
+ fieldErrors.push(`\u2022 ${fieldName}: ${error}`);
72
+ });
73
+ }
74
+ if (fieldErrors.length > 0) {
75
+ formatted = fieldErrors.join("\n");
76
+ }
77
+ }
78
+ if (retryAfter) {
79
+ formatted += `
80
+
81
+ Please wait ${retryAfter} seconds before retrying.`;
82
+ }
83
+ if (requestId) {
84
+ formatted += `
85
+
86
+ Request ID: ${requestId}`;
87
+ }
88
+ return formatted;
89
+ }
90
+
91
+ export { APIClientError, formatErrorMessage, getErrorInfo, getErrorTitle, isAPIClientError };
@@ -1,5 +1,5 @@
1
+ import { useNotificationAdapter } from './chunk-TIRMFDM4.js';
1
2
  import { useElevasisServices } from './chunk-KA7LO7U5.js';
2
- import { showApiErrorNotification } from './chunk-7AI5ZYJ4.js';
3
3
  import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
4
4
  import { z } from 'zod';
5
5
 
@@ -26,6 +26,8 @@ z.object({
26
26
  startDate: z.string().datetime(),
27
27
  endDate: z.string().datetime()
28
28
  });
29
+
30
+ // ../core/src/operations/notifications/api-schemas.ts
29
31
  var NotificationCategorySchema = z.enum(["info", "queue", "alert", "error", "system"]);
30
32
  var GetNotificationsQuerySchema = z.object({
31
33
  limit: z.coerce.number().int().min(1).max(100).default(50),
@@ -47,6 +49,7 @@ z.object({
47
49
  function useMarkAsRead() {
48
50
  const queryClient = useQueryClient();
49
51
  const { apiRequest } = useElevasisServices();
52
+ const notify = useNotificationAdapter();
50
53
  return useMutation({
51
54
  mutationFn: async (notificationId) => {
52
55
  MarkAsReadParamsSchema.parse({ id: notificationId });
@@ -58,13 +61,14 @@ function useMarkAsRead() {
58
61
  queryClient.invalidateQueries({ queryKey: ["notifications"] });
59
62
  },
60
63
  onError: (error) => {
61
- showApiErrorNotification(error);
64
+ notify.apiError(error);
62
65
  }
63
66
  });
64
67
  }
65
68
  function useMarkAllAsRead() {
66
69
  const queryClient = useQueryClient();
67
70
  const { apiRequest } = useElevasisServices();
71
+ const notify = useNotificationAdapter();
68
72
  return useMutation({
69
73
  mutationFn: async () => {
70
74
  await apiRequest("/notifications/mark-all-read", {
@@ -75,7 +79,7 @@ function useMarkAllAsRead() {
75
79
  queryClient.invalidateQueries({ queryKey: ["notifications"] });
76
80
  },
77
81
  onError: (error) => {
78
- showApiErrorNotification(error);
82
+ notify.apiError(error);
79
83
  }
80
84
  });
81
85
  }