@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.
- package/dist/api/hooks/useApiClient.d.ts +54 -0
- package/dist/api/hooks/useApiClient.d.ts.map +1 -0
- package/dist/api/hooks/useApiClient.js +185 -0
- package/dist/api/index.d.ts +49 -11
- package/dist/api/index.js +3 -2
- package/dist/auth/index.d.ts +88 -2
- package/dist/auth/index.js +5 -1
- package/dist/{chunk-5UWFGBFM.js → chunk-4KAG5U7A.js} +18 -4
- package/dist/chunk-4VGWQ5AN.js +91 -0
- package/dist/{chunk-JKERRYVS.js → chunk-BLO4SISK.js} +7 -3
- package/dist/chunk-BWCC6ZJC.js +202 -0
- package/dist/{chunk-WNWKOCGJ.js → chunk-BZTA7IIL.js} +1 -1
- package/dist/chunk-DD3CCMCZ.js +15 -0
- package/dist/{chunk-GEFB5YIR.js → chunk-EZMRFWZQ.js} +1 -1
- package/dist/chunk-FDCVFCOQ.js +105 -0
- package/dist/chunk-FLJXZ7YC.js +150 -0
- package/dist/{chunk-7AI5ZYJ4.js → chunk-JVAZHVNV.js} +2 -94
- package/dist/{chunk-ZGHDPDTF.js → chunk-JYSYHVLU.js} +3 -3
- package/dist/{chunk-J3FALDQE.js → chunk-NXHL23JW.js} +7 -13
- package/dist/{chunk-OUHGHTE7.js → chunk-O3PY6B6E.js} +3 -2
- package/dist/{chunk-ZQVPUAGR.js → chunk-OLD3NQLI.js} +33 -31
- package/dist/{chunk-B64YDSAY.js → chunk-PCBXNHKY.js} +53 -97
- package/dist/chunk-QQOLC46E.js +75 -0
- package/dist/chunk-RNP5R5I3.js +1 -0
- package/dist/{chunk-YULUKCS6.js → chunk-SITSZUFW.js} +1 -1
- package/dist/chunk-TIRMFDM4.js +33 -0
- package/dist/{chunk-PYL4XW6H.js → chunk-TMFCNFLW.js} +1 -1
- package/dist/{chunk-S66I2PYB.js → chunk-TN3PU2WK.js} +1 -1
- package/dist/components/command-queue/index.js +6 -4
- package/dist/components/index.js +9 -7
- package/dist/components/notifications/index.js +4 -3
- package/dist/display/index.js +3 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +5 -4
- package/dist/hooks/published.d.ts +1 -1
- package/dist/hooks/published.js +4 -3
- package/dist/index.d.ts +447 -117
- package/dist/index.js +22 -16
- package/dist/initialization/index.d.ts +49 -1
- package/dist/initialization/index.js +5 -2
- package/dist/organization/index.d.ts +61 -2
- package/dist/organization/index.js +5 -2
- package/dist/profile/index.d.ts +30 -2
- package/dist/profile/index.js +2 -1
- package/dist/provider/index.d.ts +112 -27
- package/dist/provider/index.js +11 -6
- package/dist/provider/published.d.ts +85 -13
- package/dist/provider/published.js +10 -4
- package/dist/utils/index.js +2 -1
- package/package.json +17 -4
- package/dist/chunk-GDV44UWF.js +0 -138
- package/dist/chunk-HBRMWW6V.js +0 -43
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { APIClientError } from './chunk-4VGWQ5AN.js';
|
|
2
|
+
import { createContext, useContext, useMemo, useCallback } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var ApiClientContext = createContext(null);
|
|
6
|
+
function useApiClientContext() {
|
|
7
|
+
const context = useContext(ApiClientContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error("useApiClient must be used within ApiClientProvider");
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
13
|
+
function ApiClientProvider({
|
|
14
|
+
children,
|
|
15
|
+
getAccessToken,
|
|
16
|
+
organizationId = null,
|
|
17
|
+
getOrganizationId: getOrganizationIdProp,
|
|
18
|
+
isOrganizationReady,
|
|
19
|
+
onError
|
|
20
|
+
}) {
|
|
21
|
+
const getOrganizationId = useMemo(
|
|
22
|
+
() => getOrganizationIdProp ?? (() => organizationId ?? null),
|
|
23
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
24
|
+
[getOrganizationIdProp, organizationId]
|
|
25
|
+
);
|
|
26
|
+
const value = useMemo(
|
|
27
|
+
() => ({
|
|
28
|
+
getAccessToken,
|
|
29
|
+
organizationId: organizationId ?? null,
|
|
30
|
+
isOrganizationReady,
|
|
31
|
+
getOrganizationId,
|
|
32
|
+
onError
|
|
33
|
+
}),
|
|
34
|
+
[getAccessToken, organizationId, isOrganizationReady, getOrganizationId, onError]
|
|
35
|
+
);
|
|
36
|
+
return /* @__PURE__ */ jsx(ApiClientContext.Provider, { value, children });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ../core/src/platform/constants/http.ts
|
|
40
|
+
var HTTP_HEADERS = {
|
|
41
|
+
/**
|
|
42
|
+
* Organization context header
|
|
43
|
+
* Note: Node.js normalizes all headers to lowercase, so we use lowercase consistently
|
|
44
|
+
*/
|
|
45
|
+
WORKOS_ORGANIZATION_ID: "workos-organization-id",
|
|
46
|
+
AGENT_ORGANIZATION: "x-agent-organization",
|
|
47
|
+
AGENT_MODE: "x-agent-mode"
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/api/hooks/useApiClient.ts
|
|
51
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
52
|
+
function buildApiRequest(getAccessToken, getOrganizationId, apiUrl, handleResponseError) {
|
|
53
|
+
return async function apiRequest(endpoint, options = {}) {
|
|
54
|
+
try {
|
|
55
|
+
const token = await getAccessToken();
|
|
56
|
+
const organizationId = getOrganizationId();
|
|
57
|
+
if (organizationId) console.log("[apiRequest]", endpoint, "org:", organizationId);
|
|
58
|
+
const headers = {
|
|
59
|
+
...options.body ? { "Content-Type": "application/json" } : {},
|
|
60
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
61
|
+
...organizationId && { [HTTP_HEADERS.WORKOS_ORGANIZATION_ID]: organizationId },
|
|
62
|
+
...options.headers
|
|
63
|
+
};
|
|
64
|
+
const timeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
|
|
65
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
|
|
66
|
+
const response = await fetch(`${apiUrl}/api${endpoint}`, {
|
|
67
|
+
...options,
|
|
68
|
+
headers,
|
|
69
|
+
signal
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
await handleResponseError(endpoint, response, options);
|
|
73
|
+
}
|
|
74
|
+
if (response.status === 204) {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
return response.json();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof Error && error.message.includes("No access token")) {
|
|
80
|
+
const headers = {
|
|
81
|
+
...options.body ? { "Content-Type": "application/json" } : {},
|
|
82
|
+
...options.headers
|
|
83
|
+
};
|
|
84
|
+
const fallbackTimeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
|
|
85
|
+
const fallbackSignal = options.signal ? AbortSignal.any([options.signal, fallbackTimeoutSignal]) : fallbackTimeoutSignal;
|
|
86
|
+
const response = await fetch(`${apiUrl}/api${endpoint}`, {
|
|
87
|
+
...options,
|
|
88
|
+
headers,
|
|
89
|
+
signal: fallbackSignal
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
await handleResponseError(endpoint, response, options);
|
|
93
|
+
}
|
|
94
|
+
if (response.status === 204) {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
return response.json();
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function useApiClient(apiUrl) {
|
|
104
|
+
const { getAccessToken, getOrganizationId, isOrganizationReady, onError } = useApiClientContext();
|
|
105
|
+
const handleResponseError = useCallback(
|
|
106
|
+
async (endpoint, response, options) => {
|
|
107
|
+
const errorData = await response.json().catch(() => ({
|
|
108
|
+
error: `HTTP ${response.status}`,
|
|
109
|
+
code: "INTERNAL_SERVER_ERROR"
|
|
110
|
+
}));
|
|
111
|
+
if (response.status >= 500 && onError) {
|
|
112
|
+
onError(endpoint, new Error(errorData.error), {
|
|
113
|
+
method: options.method || "GET",
|
|
114
|
+
statusCode: response.status,
|
|
115
|
+
requestId: errorData.requestId
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
throw new APIClientError(
|
|
119
|
+
errorData.error,
|
|
120
|
+
errorData.code,
|
|
121
|
+
response.status,
|
|
122
|
+
errorData.requestId,
|
|
123
|
+
errorData.fields,
|
|
124
|
+
errorData.retryAfter
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
[onError]
|
|
128
|
+
);
|
|
129
|
+
const apiRequest = useCallback(
|
|
130
|
+
(endpoint, options = {}) => buildApiRequest(getAccessToken, getOrganizationId, apiUrl, handleResponseError)(endpoint, options),
|
|
131
|
+
[getAccessToken, getOrganizationId, apiUrl, handleResponseError]
|
|
132
|
+
);
|
|
133
|
+
const deferredApiRequest = useCallback(
|
|
134
|
+
async (endpoint, options = {}) => {
|
|
135
|
+
if (!isOrganizationReady) {
|
|
136
|
+
throw new Error("No organization selected. Please select an organization.");
|
|
137
|
+
}
|
|
138
|
+
return apiRequest(endpoint, options);
|
|
139
|
+
},
|
|
140
|
+
[apiRequest, isOrganizationReady]
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
apiRequest,
|
|
144
|
+
deferredApiRequest,
|
|
145
|
+
isOrganizationReady,
|
|
146
|
+
isInitializing: false
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function createUseApiClient(useOrganizations, apiUrl) {
|
|
150
|
+
return function useApiClientCompat() {
|
|
151
|
+
const { getAccessToken, getOrganizationId, isOrganizationReady, onError } = useApiClientContext();
|
|
152
|
+
const { isInitializing, isOrgRefreshing } = useOrganizations();
|
|
153
|
+
const handleResponseError = useCallback(
|
|
154
|
+
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(
|
|
167
|
+
errorData.error,
|
|
168
|
+
errorData.code,
|
|
169
|
+
response.status,
|
|
170
|
+
errorData.requestId,
|
|
171
|
+
errorData.fields,
|
|
172
|
+
errorData.retryAfter
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
[onError]
|
|
176
|
+
);
|
|
177
|
+
const apiRequest = useCallback(
|
|
178
|
+
(endpoint, options = {}) => buildApiRequest(getAccessToken, getOrganizationId, apiUrl, handleResponseError)(endpoint, options),
|
|
179
|
+
[getAccessToken, getOrganizationId, handleResponseError]
|
|
180
|
+
);
|
|
181
|
+
const deferredApiRequest = useCallback(
|
|
182
|
+
async (endpoint, options = {}) => {
|
|
183
|
+
if (isInitializing || isOrgRefreshing) {
|
|
184
|
+
throw new Error("Organization context is still initializing. Please wait.");
|
|
185
|
+
}
|
|
186
|
+
if (!isOrganizationReady) {
|
|
187
|
+
throw new Error("No organization selected. Please select an organization.");
|
|
188
|
+
}
|
|
189
|
+
return apiRequest(endpoint, options);
|
|
190
|
+
},
|
|
191
|
+
[apiRequest, isInitializing, isOrgRefreshing, isOrganizationReady]
|
|
192
|
+
);
|
|
193
|
+
return {
|
|
194
|
+
apiRequest,
|
|
195
|
+
deferredApiRequest,
|
|
196
|
+
isOrganizationReady,
|
|
197
|
+
isInitializing: isInitializing || isOrgRefreshing
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export { ApiClientProvider, createUseApiClient, useApiClient, useApiClientContext };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UuidSchema, ResourceTypeSchema, NonEmptyStringSchema, OriginResourceTypeSchema } from './chunk-
|
|
1
|
+
import { UuidSchema, ResourceTypeSchema, NonEmptyStringSchema, OriginResourceTypeSchema } from './chunk-BLO4SISK.js';
|
|
2
2
|
import { useElevasisServices } from './chunk-KA7LO7U5.js';
|
|
3
3
|
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/organization/context/OrganizationContext.tsx
|
|
4
|
+
var OrganizationContext = createContext(null);
|
|
5
|
+
function useOrganization() {
|
|
6
|
+
const ctx = useContext(OrganizationContext);
|
|
7
|
+
if (!ctx) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"useOrganization must be used within an OrganizationProvider. Wrap your app (or the relevant subtree) with <OrganizationProvider>."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { OrganizationContext, useOrganization };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useUserProfile } from './chunk-
|
|
1
|
+
import { useUserProfile } from './chunk-4KAG5U7A.js';
|
|
2
2
|
import { useAuthContext } from './chunk-7PLEQFHO.js';
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { Button, Loader, Menu, Text, Badge } from '@mantine/core';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useInitialization } from './chunk-QQOLC46E.js';
|
|
2
|
+
import { useAuthContext } from './chunk-7PLEQFHO.js';
|
|
3
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
4
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
5
|
+
import { useNavigate, useLocation } from '@tanstack/react-router';
|
|
6
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function useStableAccessToken() {
|
|
9
|
+
const { getAccessToken } = useAuthContext();
|
|
10
|
+
const getAccessTokenRef = useRef(getAccessToken);
|
|
11
|
+
getAccessTokenRef.current = getAccessToken;
|
|
12
|
+
return useCallback(() => {
|
|
13
|
+
return getAccessTokenRef.current();
|
|
14
|
+
}, []);
|
|
15
|
+
}
|
|
16
|
+
function useSessionCheck() {
|
|
17
|
+
const { user } = useAuthContext();
|
|
18
|
+
const queryClient = useQueryClient();
|
|
19
|
+
const isCheckingRef = useRef(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const handleVisibilityChange = async () => {
|
|
22
|
+
if (isCheckingRef.current || document.visibilityState !== "visible") {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
isCheckingRef.current = true;
|
|
26
|
+
try {
|
|
27
|
+
if (!user) {
|
|
28
|
+
window.location.replace("/login");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await queryClient.cancelQueries();
|
|
32
|
+
queryClient.invalidateQueries();
|
|
33
|
+
} finally {
|
|
34
|
+
isCheckingRef.current = false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
38
|
+
window.addEventListener("focus", handleVisibilityChange);
|
|
39
|
+
return () => {
|
|
40
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
41
|
+
window.removeEventListener("focus", handleVisibilityChange);
|
|
42
|
+
};
|
|
43
|
+
}, [user, queryClient]);
|
|
44
|
+
}
|
|
45
|
+
function ProtectedRoute({
|
|
46
|
+
children,
|
|
47
|
+
redirectTo = "/login",
|
|
48
|
+
fallback = null,
|
|
49
|
+
errorFallback,
|
|
50
|
+
waitForOrganization = true
|
|
51
|
+
}) {
|
|
52
|
+
const { userReady, allReady, isInitializing, error, retry } = useInitialization();
|
|
53
|
+
const navigate = useNavigate();
|
|
54
|
+
const location = useLocation();
|
|
55
|
+
const hasRedirected = useRef(false);
|
|
56
|
+
const unauthenticated = !isInitializing && !userReady && error?.layer !== "profile";
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (unauthenticated && !hasRedirected.current) {
|
|
59
|
+
hasRedirected.current = true;
|
|
60
|
+
const searchStr = location.searchStr ?? "";
|
|
61
|
+
const returnTo = location.pathname + searchStr;
|
|
62
|
+
navigate({
|
|
63
|
+
to: redirectTo,
|
|
64
|
+
search: returnTo !== "/" ? { returnTo } : {}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}, [unauthenticated, navigate, redirectTo, location]);
|
|
68
|
+
if (isInitializing) {
|
|
69
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
70
|
+
}
|
|
71
|
+
if (error && error.layer !== "organization") {
|
|
72
|
+
if (errorFallback) {
|
|
73
|
+
return /* @__PURE__ */ jsx(Fragment, { children: errorFallback(error, retry) });
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (unauthenticated) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (waitForOrganization && !allReady && error?.layer !== "organization") {
|
|
81
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
82
|
+
}
|
|
83
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
84
|
+
}
|
|
85
|
+
function AdminGuard({ children, redirectTo = "/", fallback = null }) {
|
|
86
|
+
const { userReady, profile } = useInitialization();
|
|
87
|
+
const navigate = useNavigate();
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!userReady || !profile) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!profile.is_platform_admin) {
|
|
93
|
+
navigate({ to: redirectTo });
|
|
94
|
+
}
|
|
95
|
+
}, [userReady, profile, navigate, redirectTo]);
|
|
96
|
+
if (!userReady) {
|
|
97
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
98
|
+
}
|
|
99
|
+
if (!profile?.is_platform_admin) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { AdminGuard, ProtectedRoute, useSessionCheck, useStableAccessToken };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { OrganizationContext } from './chunk-DD3CCMCZ.js';
|
|
2
|
+
import { useProfile } from './chunk-4KAG5U7A.js';
|
|
3
|
+
import { useElevasisServices } from './chunk-KA7LO7U5.js';
|
|
4
|
+
import { useAuthContext } from './chunk-7PLEQFHO.js';
|
|
5
|
+
import { useState, useRef, useEffect, useCallback, createElement } from 'react';
|
|
6
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
|
|
8
|
+
function OrganizationProvider({ children }) {
|
|
9
|
+
const { user, organizationId: workosOrgId } = useAuthContext();
|
|
10
|
+
const { apiRequest } = useElevasisServices();
|
|
11
|
+
const { profile } = useProfile();
|
|
12
|
+
const queryClient = useQueryClient();
|
|
13
|
+
const [memberships, setMemberships] = useState([]);
|
|
14
|
+
const [currentWorkOSOrganizationId, setCurrentWorkOSOrganizationId] = useState(null);
|
|
15
|
+
const [currentSupabaseOrganizationId, setCurrentSupabaseOrganizationId] = useState(null);
|
|
16
|
+
const [currentMembership, setCurrentMembership] = useState(null);
|
|
17
|
+
const [isInitializing, setIsInitializing] = useState(true);
|
|
18
|
+
const [isOrgRefreshing, setIsOrgRefreshing] = useState(false);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const hasInitializedRef = useRef(false);
|
|
21
|
+
const [profileLoaded, setProfileLoaded] = useState(false);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (profile !== void 0 && profile !== null) {
|
|
24
|
+
setProfileLoaded(true);
|
|
25
|
+
}
|
|
26
|
+
}, [profile]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!user) {
|
|
29
|
+
setMemberships([]);
|
|
30
|
+
setCurrentWorkOSOrganizationId(null);
|
|
31
|
+
setCurrentSupabaseOrganizationId(null);
|
|
32
|
+
setCurrentMembership(null);
|
|
33
|
+
setIsInitializing(false);
|
|
34
|
+
setIsOrgRefreshing(false);
|
|
35
|
+
setError(null);
|
|
36
|
+
hasInitializedRef.current = false;
|
|
37
|
+
setProfileLoaded(false);
|
|
38
|
+
}
|
|
39
|
+
}, [user]);
|
|
40
|
+
const applyMembership = useCallback((membership) => {
|
|
41
|
+
setCurrentMembership(membership);
|
|
42
|
+
setCurrentSupabaseOrganizationId(membership?.organization?.id ?? null);
|
|
43
|
+
setCurrentWorkOSOrganizationId(membership?.organization?.workos_org_id ?? null);
|
|
44
|
+
}, []);
|
|
45
|
+
const selectOrganization = useCallback(
|
|
46
|
+
(data) => {
|
|
47
|
+
let selected = null;
|
|
48
|
+
if (profile?.last_visited_org) {
|
|
49
|
+
selected = data.find((m) => m.organizationId === profile.last_visited_org) ?? null;
|
|
50
|
+
}
|
|
51
|
+
if (!selected && workosOrgId) {
|
|
52
|
+
selected = data.find((m) => m.organization?.workos_org_id === workosOrgId) ?? null;
|
|
53
|
+
}
|
|
54
|
+
if (!selected && data.length > 0) {
|
|
55
|
+
selected = data[0];
|
|
56
|
+
}
|
|
57
|
+
console.log("[OrgProvider] selected:", selected?.organization?.workos_org_id ?? "none");
|
|
58
|
+
if (selected) {
|
|
59
|
+
applyMembership(selected);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
[profile?.last_visited_org, workosOrgId, applyMembership]
|
|
63
|
+
);
|
|
64
|
+
const fetchAndInitialize = useCallback(async () => {
|
|
65
|
+
if (!user?.id || !profileLoaded) return;
|
|
66
|
+
setError(null);
|
|
67
|
+
if (memberships.length === 0) {
|
|
68
|
+
setIsInitializing(true);
|
|
69
|
+
} else {
|
|
70
|
+
setIsOrgRefreshing(true);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const data = await apiRequest("/memberships/my-memberships");
|
|
74
|
+
if (!Array.isArray(data)) {
|
|
75
|
+
throw new Error("Invalid memberships response");
|
|
76
|
+
}
|
|
77
|
+
setMemberships(data);
|
|
78
|
+
if (data.length === 0) {
|
|
79
|
+
setError("No organization memberships found. Please contact your administrator or try refreshing the page.");
|
|
80
|
+
hasInitializedRef.current = true;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!currentWorkOSOrganizationId) {
|
|
84
|
+
selectOrganization(data);
|
|
85
|
+
} else {
|
|
86
|
+
const stillPresent = data.find((m) => m.organization?.workos_org_id === currentWorkOSOrganizationId);
|
|
87
|
+
if (!stillPresent) {
|
|
88
|
+
applyMembership(data[0] ?? null);
|
|
89
|
+
} else if (stillPresent.id !== currentMembership?.id) {
|
|
90
|
+
applyMembership(stillPresent);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
hasInitializedRef.current = true;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
setError(err instanceof Error ? err.message : "Failed to load organizations");
|
|
96
|
+
} finally {
|
|
97
|
+
setIsInitializing(false);
|
|
98
|
+
setIsOrgRefreshing(false);
|
|
99
|
+
}
|
|
100
|
+
}, [
|
|
101
|
+
user?.id,
|
|
102
|
+
profileLoaded,
|
|
103
|
+
memberships.length,
|
|
104
|
+
apiRequest,
|
|
105
|
+
currentWorkOSOrganizationId,
|
|
106
|
+
currentMembership?.id,
|
|
107
|
+
selectOrganization,
|
|
108
|
+
applyMembership
|
|
109
|
+
]);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
console.log("[OrgProvider] gate:", { userId: !!user?.id, profileLoaded, initialized: hasInitializedRef.current });
|
|
112
|
+
if (!user?.id || !profileLoaded || hasInitializedRef.current) return;
|
|
113
|
+
fetchAndInitialize();
|
|
114
|
+
}, [user?.id, profileLoaded, fetchAndInitialize]);
|
|
115
|
+
const switchOrganization = useCallback(
|
|
116
|
+
(workosOrgId2) => {
|
|
117
|
+
const target = memberships.find((m) => m.organization?.workos_org_id === workosOrgId2);
|
|
118
|
+
if (!target) return;
|
|
119
|
+
applyMembership(target);
|
|
120
|
+
void queryClient.invalidateQueries();
|
|
121
|
+
void apiRequest("/users/me", {
|
|
122
|
+
method: "PATCH",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({ last_visited_org: target.organizationId })
|
|
125
|
+
}).catch((err) => {
|
|
126
|
+
console.warn("Failed to persist last_visited_org preference:", err);
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
[memberships, applyMembership, queryClient, apiRequest]
|
|
130
|
+
);
|
|
131
|
+
const retry = useCallback(async () => {
|
|
132
|
+
hasInitializedRef.current = false;
|
|
133
|
+
setError(null);
|
|
134
|
+
await fetchAndInitialize();
|
|
135
|
+
}, [fetchAndInitialize]);
|
|
136
|
+
const value = {
|
|
137
|
+
currentWorkOSOrganizationId,
|
|
138
|
+
currentSupabaseOrganizationId,
|
|
139
|
+
currentMembership,
|
|
140
|
+
memberships,
|
|
141
|
+
isInitializing,
|
|
142
|
+
isOrgRefreshing,
|
|
143
|
+
error,
|
|
144
|
+
switchOrganization,
|
|
145
|
+
retry
|
|
146
|
+
};
|
|
147
|
+
return createElement(OrganizationContext.Provider, { value }, children);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { OrganizationProvider };
|
|
@@ -1,99 +1,7 @@
|
|
|
1
|
+
import { getErrorInfo, formatErrorMessage, getErrorTitle } from './chunk-4VGWQ5AN.js';
|
|
1
2
|
import { notifications } from '@mantine/notifications';
|
|
2
3
|
import { IconUser, IconExternalLink, IconPlug, IconBolt, IconGitBranch, IconBrain } from '@tabler/icons-react';
|
|
3
4
|
|
|
4
|
-
// src/utils/notify.tsx
|
|
5
|
-
|
|
6
|
-
// src/utils/error-utils.ts
|
|
7
|
-
var APIClientError = class extends Error {
|
|
8
|
-
statusCode;
|
|
9
|
-
code;
|
|
10
|
-
requestId;
|
|
11
|
-
fields;
|
|
12
|
-
retryAfter;
|
|
13
|
-
constructor(message, code, statusCode, requestId, fields, retryAfter) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.name = "APIClientError";
|
|
16
|
-
this.code = code;
|
|
17
|
-
this.statusCode = statusCode;
|
|
18
|
-
this.requestId = requestId;
|
|
19
|
-
this.fields = fields;
|
|
20
|
-
this.retryAfter = retryAfter;
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
function isAPIClientError(error) {
|
|
24
|
-
return error instanceof APIClientError;
|
|
25
|
-
}
|
|
26
|
-
function getErrorInfo(error) {
|
|
27
|
-
if (isAPIClientError(error)) {
|
|
28
|
-
return {
|
|
29
|
-
message: error.message,
|
|
30
|
-
code: error.code,
|
|
31
|
-
requestId: error.requestId,
|
|
32
|
-
statusCode: error.statusCode,
|
|
33
|
-
fields: error.fields,
|
|
34
|
-
retryAfter: error.retryAfter
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
if (error instanceof Error) {
|
|
38
|
-
return {
|
|
39
|
-
message: error.message
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
message: String(error)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function getErrorTitle(code) {
|
|
47
|
-
if (!code) return "Error";
|
|
48
|
-
switch (code) {
|
|
49
|
-
case "VALIDATION_ERROR":
|
|
50
|
-
return "Validation Error";
|
|
51
|
-
case "AUTHENTICATION_FAILED":
|
|
52
|
-
return "Authentication Required";
|
|
53
|
-
case "FORBIDDEN":
|
|
54
|
-
return "Access Denied";
|
|
55
|
-
case "NOT_FOUND":
|
|
56
|
-
return "Not Found";
|
|
57
|
-
case "CONFLICT":
|
|
58
|
-
return "Conflict";
|
|
59
|
-
case "RATE_LIMIT_EXCEEDED":
|
|
60
|
-
return "Rate Limit Exceeded";
|
|
61
|
-
case "INTERNAL_SERVER_ERROR":
|
|
62
|
-
return "Server Error";
|
|
63
|
-
case "SERVICE_UNAVAILABLE":
|
|
64
|
-
return "Service Temporarily Unavailable";
|
|
65
|
-
default:
|
|
66
|
-
return "Error";
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function formatErrorMessage(message, requestId, fields, retryAfter) {
|
|
70
|
-
let formatted = message;
|
|
71
|
-
if (fields && Object.keys(fields).length > 0) {
|
|
72
|
-
const fieldErrors = [];
|
|
73
|
-
for (const [field, errors] of Object.entries(fields)) {
|
|
74
|
-
const fieldName = field === "root" || field === "_root" ? "Request" : field;
|
|
75
|
-
errors.forEach((error) => {
|
|
76
|
-
fieldErrors.push(`\u2022 ${fieldName}: ${error}`);
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
if (fieldErrors.length > 0) {
|
|
80
|
-
formatted = fieldErrors.join("\n");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (retryAfter) {
|
|
84
|
-
formatted += `
|
|
85
|
-
|
|
86
|
-
Please wait ${retryAfter} seconds before retrying.`;
|
|
87
|
-
}
|
|
88
|
-
if (requestId) {
|
|
89
|
-
formatted += `
|
|
90
|
-
|
|
91
|
-
Request ID: ${requestId}`;
|
|
92
|
-
}
|
|
93
|
-
return formatted;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// src/utils/notify.tsx
|
|
97
5
|
var showInfoNotification = (message) => {
|
|
98
6
|
notifications.show({
|
|
99
7
|
title: "Info",
|
|
@@ -199,4 +107,4 @@ function getResourceIcon(type) {
|
|
|
199
107
|
return iconNameToComponent[iconName];
|
|
200
108
|
}
|
|
201
109
|
|
|
202
|
-
export {
|
|
110
|
+
export { formatDate, getResourceColor, getResourceIcon, showApiErrorNotification, showErrorNotification, showInfoNotification, showSuccessNotification, showWarningNotification, validateEmail };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useSubmitAction, useDeleteTask } from './chunk-
|
|
1
|
+
import { useSubmitAction, useDeleteTask } from './chunk-NXHL23JW.js';
|
|
2
2
|
import { FormFieldRenderer } from './chunk-PSLKGOBZ.js';
|
|
3
|
-
import { ContextViewer, JsonViewer } from './chunk-
|
|
4
|
-
import { getErrorInfo, formatErrorMessage } from './chunk-
|
|
3
|
+
import { ContextViewer, JsonViewer } from './chunk-O3PY6B6E.js';
|
|
4
|
+
import { getErrorInfo, formatErrorMessage } from './chunk-4VGWQ5AN.js';
|
|
5
5
|
import { Modal, Stack, Title, Text, Textarea, Alert, Group, Button, Card, ThemeIcon, Badge, Loader, Menu, ActionIcon, Accordion } from '@mantine/core';
|
|
6
6
|
import { IconMail, IconSend, IconFileText, IconClock, IconArrowUp, IconMessageCircle, IconRocket, IconEye, IconEdit, IconAlertTriangle, IconRefresh, IconX, IconCheck, IconAlertCircle, IconRobot, IconGitBranch, IconDotsVertical, IconTrash, IconPlayerPlay, IconExternalLink } from '@tabler/icons-react';
|
|
7
7
|
import { useState } from 'react';
|
|
@@ -1,7 +1,6 @@
|
|
|
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 { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
4
|
-
import { notifications } from '@mantine/notifications';
|
|
5
4
|
|
|
6
5
|
function useCommandQueue({
|
|
7
6
|
status,
|
|
@@ -82,6 +81,7 @@ function useSubmitAction() {
|
|
|
82
81
|
function useDeleteTask() {
|
|
83
82
|
const { apiRequest } = useElevasisServices();
|
|
84
83
|
const queryClient = useQueryClient();
|
|
84
|
+
const notify = useNotificationAdapter();
|
|
85
85
|
return useMutation({
|
|
86
86
|
mutationFn: async (taskId) => {
|
|
87
87
|
await apiRequest(`/command-queue/${taskId}`, {
|
|
@@ -95,20 +95,13 @@ function useDeleteTask() {
|
|
|
95
95
|
for (const [queryKey, data] of queries) {
|
|
96
96
|
previousData.set(queryKey, data);
|
|
97
97
|
if (data) {
|
|
98
|
-
queryClient.setQueryData(
|
|
99
|
-
queryKey,
|
|
100
|
-
(old) => old?.filter((task) => task.id !== taskId)
|
|
101
|
-
);
|
|
98
|
+
queryClient.setQueryData(queryKey, (old) => old?.filter((task) => task.id !== taskId));
|
|
102
99
|
}
|
|
103
100
|
}
|
|
104
101
|
return { previousData };
|
|
105
102
|
},
|
|
106
103
|
onSuccess: () => {
|
|
107
|
-
|
|
108
|
-
title: "Task Deleted",
|
|
109
|
-
message: "Task has been removed",
|
|
110
|
-
color: "green"
|
|
111
|
-
});
|
|
104
|
+
notify.success("Task Deleted", "Task has been removed");
|
|
112
105
|
queryClient.invalidateQueries({ queryKey: ["command-queue"] });
|
|
113
106
|
},
|
|
114
107
|
onError: (error, _taskId, context) => {
|
|
@@ -117,7 +110,7 @@ function useDeleteTask() {
|
|
|
117
110
|
queryClient.setQueryData(queryKey, data);
|
|
118
111
|
}
|
|
119
112
|
}
|
|
120
|
-
|
|
113
|
+
notify.apiError(error);
|
|
121
114
|
}
|
|
122
115
|
});
|
|
123
116
|
}
|
|
@@ -146,6 +139,7 @@ function useCommandQueueTotals({
|
|
|
146
139
|
function usePatchTask() {
|
|
147
140
|
const { apiRequest } = useElevasisServices();
|
|
148
141
|
const queryClient = useQueryClient();
|
|
142
|
+
const notify = useNotificationAdapter();
|
|
149
143
|
return useMutation({
|
|
150
144
|
mutationFn: async ({ taskId, params }) => {
|
|
151
145
|
const response = await apiRequest(`/command-queue/${taskId}`, {
|
|
@@ -168,7 +162,7 @@ function usePatchTask() {
|
|
|
168
162
|
);
|
|
169
163
|
},
|
|
170
164
|
onError: (error) => {
|
|
171
|
-
|
|
165
|
+
notify.apiError(error);
|
|
172
166
|
}
|
|
173
167
|
});
|
|
174
168
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StyledMarkdown } from './chunk-3KMDHCAR.js';
|
|
2
2
|
import { ResourceStatusColors } from './chunk-YZ6GTZXL.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getResourceIcon } from './chunk-JVAZHVNV.js';
|
|
4
|
+
import { getErrorInfo, getErrorTitle } from './chunk-4VGWQ5AN.js';
|
|
4
5
|
import { useAuthContext } from './chunk-7PLEQFHO.js';
|
|
5
6
|
import { useRouterContext } from './chunk-Q7DJKLEN.js';
|
|
6
7
|
import { Alert, Text, Code, Group, Badge, Collapse, ScrollArea, Center, Stack, Title, Button, UnstyledButton, Box, ThemeIcon, Card, Skeleton, Paper, Loader, Select, Grid, Space } from '@mantine/core';
|
|
@@ -101,7 +102,7 @@ function CollapsibleSection({
|
|
|
101
102
|
function EmptyState({ icon: Icon, title, description, action, py = "xl" }) {
|
|
102
103
|
return /* @__PURE__ */ jsx(Center, { py, children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "xs", children: [
|
|
103
104
|
/* @__PURE__ */ jsx(Icon, { size: 48, style: { opacity: 0.5 } }),
|
|
104
|
-
/* @__PURE__ */ jsx(Title, { order:
|
|
105
|
+
/* @__PURE__ */ jsx(Title, { order: 4, children: title }),
|
|
105
106
|
description && /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", ta: "center", children: description }),
|
|
106
107
|
action && /* @__PURE__ */ jsx(Button, { variant: "light", onClick: action.onClick, leftSection: action.icon, mt: "sm", children: action.label })
|
|
107
108
|
] }) });
|