@axcode/axcloud-sdk 0.1.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/README.md +339 -0
- package/dist/index.d.mts +1473 -0
- package/dist/index.d.ts +1473 -0
- package/dist/index.js +1282 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1246 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles/linked-accounts.css +156 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1282 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var auth0React = require('@auth0/auth0-react');
|
|
5
|
+
var reactQuery = require('@tanstack/react-query');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// src/core/errors.ts
|
|
9
|
+
var AxCloudError = class _AxCloudError extends Error {
|
|
10
|
+
constructor(message, code, status, details) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "AxCloudError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.details = details;
|
|
16
|
+
const ErrorWithCapture = Error;
|
|
17
|
+
if (ErrorWithCapture.captureStackTrace) {
|
|
18
|
+
ErrorWithCapture.captureStackTrace(this, _AxCloudError);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
static fromResponse(status, body) {
|
|
22
|
+
const details = typeof body === "object" ? body : void 0;
|
|
23
|
+
const message = details?.message;
|
|
24
|
+
switch (status) {
|
|
25
|
+
case 401:
|
|
26
|
+
return new _AxCloudError(
|
|
27
|
+
message || "Authentication required",
|
|
28
|
+
"UNAUTHORIZED",
|
|
29
|
+
status,
|
|
30
|
+
details
|
|
31
|
+
);
|
|
32
|
+
case 403:
|
|
33
|
+
return new _AxCloudError(
|
|
34
|
+
message || "Access denied",
|
|
35
|
+
"FORBIDDEN",
|
|
36
|
+
status,
|
|
37
|
+
details
|
|
38
|
+
);
|
|
39
|
+
case 404:
|
|
40
|
+
return new _AxCloudError(
|
|
41
|
+
message || "Resource not found",
|
|
42
|
+
"NOT_FOUND",
|
|
43
|
+
status,
|
|
44
|
+
details
|
|
45
|
+
);
|
|
46
|
+
case 422:
|
|
47
|
+
return new _AxCloudError(
|
|
48
|
+
message || "Validation failed",
|
|
49
|
+
"VALIDATION_ERROR",
|
|
50
|
+
status,
|
|
51
|
+
details
|
|
52
|
+
);
|
|
53
|
+
default:
|
|
54
|
+
if (status >= 500) {
|
|
55
|
+
return new _AxCloudError(
|
|
56
|
+
message || "Server error",
|
|
57
|
+
"SERVER_ERROR",
|
|
58
|
+
status,
|
|
59
|
+
details
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return new _AxCloudError(
|
|
63
|
+
message || "An error occurred",
|
|
64
|
+
"UNKNOWN_ERROR",
|
|
65
|
+
status,
|
|
66
|
+
details
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
static networkError(originalError) {
|
|
71
|
+
const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
|
|
72
|
+
const message = isDev && originalError?.message ? originalError.message : "Network request failed";
|
|
73
|
+
return new _AxCloudError(
|
|
74
|
+
message,
|
|
75
|
+
"NETWORK_ERROR",
|
|
76
|
+
0,
|
|
77
|
+
isDev ? { originalError: originalError?.message } : void 0
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
static timeoutError(timeoutMs) {
|
|
81
|
+
return new _AxCloudError(
|
|
82
|
+
`Request timed out after ${timeoutMs}ms`,
|
|
83
|
+
"TIMEOUT_ERROR",
|
|
84
|
+
0,
|
|
85
|
+
{ timeout: timeoutMs }
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function isAxCloudError(error) {
|
|
90
|
+
return error instanceof AxCloudError;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/client.ts
|
|
94
|
+
var AxCloudClient = class {
|
|
95
|
+
constructor(config) {
|
|
96
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
97
|
+
this.getAccessToken = config.getAccessToken;
|
|
98
|
+
this.apiKey = config.apiKey;
|
|
99
|
+
this.defaultTimeout = config.timeout ?? 3e4;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Update the access token getter (used when auth state changes)
|
|
103
|
+
*/
|
|
104
|
+
setAccessTokenGetter(getter) {
|
|
105
|
+
this.getAccessToken = getter;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Build URL with query parameters
|
|
109
|
+
*/
|
|
110
|
+
buildUrl(path, params) {
|
|
111
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
112
|
+
if (params) {
|
|
113
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
114
|
+
if (value !== void 0) {
|
|
115
|
+
url.searchParams.append(key, String(value));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return url.toString();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Make an authenticated request
|
|
123
|
+
*/
|
|
124
|
+
async request(path, options = {}) {
|
|
125
|
+
const { body, params, headers: customHeaders, timeout, ...restOptions } = options;
|
|
126
|
+
const headers = {
|
|
127
|
+
"Content-Type": "application/json"
|
|
128
|
+
};
|
|
129
|
+
if (customHeaders) {
|
|
130
|
+
const customHeadersRecord = customHeaders;
|
|
131
|
+
Object.assign(headers, customHeadersRecord);
|
|
132
|
+
}
|
|
133
|
+
if (this.apiKey) {
|
|
134
|
+
headers["x-api-key"] = this.apiKey;
|
|
135
|
+
}
|
|
136
|
+
if (this.getAccessToken) {
|
|
137
|
+
try {
|
|
138
|
+
const token = await this.getAccessToken();
|
|
139
|
+
if (token) {
|
|
140
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
|
|
144
|
+
console.warn(
|
|
145
|
+
"[AxCloud SDK] Failed to fetch access token:",
|
|
146
|
+
error instanceof Error ? error.message : error
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const url = this.buildUrl(path, params);
|
|
152
|
+
const controller = new AbortController();
|
|
153
|
+
const requestTimeout = timeout ?? this.defaultTimeout;
|
|
154
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch(url, {
|
|
157
|
+
...restOptions,
|
|
158
|
+
headers,
|
|
159
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
160
|
+
signal: controller.signal
|
|
161
|
+
});
|
|
162
|
+
const contentType = response.headers.get("content-type");
|
|
163
|
+
const isJson = contentType?.includes("application/json");
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const errorBody = isJson ? await response.json().catch(() => null) : null;
|
|
166
|
+
throw AxCloudError.fromResponse(response.status, errorBody);
|
|
167
|
+
}
|
|
168
|
+
if (response.status === 204) {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
return isJson ? await response.json() : {};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof AxCloudError) {
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
177
|
+
throw AxCloudError.timeoutError(requestTimeout);
|
|
178
|
+
}
|
|
179
|
+
throw AxCloudError.networkError(error instanceof Error ? error : void 0);
|
|
180
|
+
} finally {
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* GET request
|
|
186
|
+
*/
|
|
187
|
+
get(path, params) {
|
|
188
|
+
return this.request(path, { method: "GET", params });
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* POST request
|
|
192
|
+
*/
|
|
193
|
+
post(path, body) {
|
|
194
|
+
return this.request(path, { method: "POST", body });
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* PUT request
|
|
198
|
+
*/
|
|
199
|
+
put(path, body) {
|
|
200
|
+
return this.request(path, { method: "PUT", body });
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* PATCH request
|
|
204
|
+
*/
|
|
205
|
+
patch(path, body) {
|
|
206
|
+
return this.request(path, { method: "PATCH", body });
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* DELETE request
|
|
210
|
+
*/
|
|
211
|
+
delete(path) {
|
|
212
|
+
return this.request(path, { method: "DELETE" });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var clientRegistry = /* @__PURE__ */ new WeakMap();
|
|
216
|
+
function createClient(config, key) {
|
|
217
|
+
const client = new AxCloudClient(config);
|
|
218
|
+
if (key) {
|
|
219
|
+
clientRegistry.set(key, client);
|
|
220
|
+
}
|
|
221
|
+
return client;
|
|
222
|
+
}
|
|
223
|
+
function getClient(key) {
|
|
224
|
+
if (key) {
|
|
225
|
+
const client = clientRegistry.get(key);
|
|
226
|
+
if (client) {
|
|
227
|
+
return client;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
throw new Error("AxCloud client not found. Make sure AxCloudProvider is mounted and use useAxCloudContext() to get the client.");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/core/types.ts
|
|
234
|
+
function userHasPermission(user, permission) {
|
|
235
|
+
if (user.permissions && user.permissions.length > 0) {
|
|
236
|
+
return user.permissions.includes(permission);
|
|
237
|
+
}
|
|
238
|
+
if (user.customRole?.effectivePermissions) {
|
|
239
|
+
return user.customRole.effectivePermissions.includes(permission);
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
function userHasAnyPermission(user, permissions) {
|
|
244
|
+
return permissions.some((p) => userHasPermission(user, p));
|
|
245
|
+
}
|
|
246
|
+
function userHasAllPermissions(user, permissions) {
|
|
247
|
+
return permissions.every((p) => userHasPermission(user, p));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/core/forms.ts
|
|
251
|
+
async function submitForm(apiUrl, submission) {
|
|
252
|
+
const url = `${apiUrl.replace(/\/$/, "")}/public/submit`;
|
|
253
|
+
const response = await fetch(url, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: {
|
|
256
|
+
"Content-Type": "application/json"
|
|
257
|
+
},
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
api_key: submission.apiKey,
|
|
260
|
+
site: submission.site,
|
|
261
|
+
form_type: submission.formType,
|
|
262
|
+
email: submission.email,
|
|
263
|
+
data: submission.data || {}
|
|
264
|
+
})
|
|
265
|
+
});
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
const error = await response.json().catch(() => ({ message: "Form submission failed" }));
|
|
268
|
+
throw new Error(error.message || "Form submission failed");
|
|
269
|
+
}
|
|
270
|
+
const result = await response.json();
|
|
271
|
+
return {
|
|
272
|
+
success: result.success,
|
|
273
|
+
message: result.message,
|
|
274
|
+
submissionId: result.submission_id
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function createFormHelper(config) {
|
|
278
|
+
return {
|
|
279
|
+
/**
|
|
280
|
+
* Submit a waitlist form
|
|
281
|
+
*/
|
|
282
|
+
submitWaitlist: (email, data) => submitForm(config.apiUrl, {
|
|
283
|
+
apiKey: config.apiKey,
|
|
284
|
+
site: config.site,
|
|
285
|
+
formType: "waitlist",
|
|
286
|
+
email,
|
|
287
|
+
data
|
|
288
|
+
}),
|
|
289
|
+
/**
|
|
290
|
+
* Submit a contact form
|
|
291
|
+
*/
|
|
292
|
+
submitContact: (email, data) => submitForm(config.apiUrl, {
|
|
293
|
+
apiKey: config.apiKey,
|
|
294
|
+
site: config.site,
|
|
295
|
+
formType: "contact",
|
|
296
|
+
email,
|
|
297
|
+
data
|
|
298
|
+
}),
|
|
299
|
+
/**
|
|
300
|
+
* Submit a membership application
|
|
301
|
+
*/
|
|
302
|
+
submitMembership: (email, data) => submitForm(config.apiUrl, {
|
|
303
|
+
apiKey: config.apiKey,
|
|
304
|
+
site: config.site,
|
|
305
|
+
formType: "membership",
|
|
306
|
+
email,
|
|
307
|
+
data
|
|
308
|
+
}),
|
|
309
|
+
/**
|
|
310
|
+
* Generic submit function
|
|
311
|
+
*/
|
|
312
|
+
submit: (formType, email, data) => submitForm(config.apiUrl, {
|
|
313
|
+
apiKey: config.apiKey,
|
|
314
|
+
site: config.site,
|
|
315
|
+
formType,
|
|
316
|
+
email,
|
|
317
|
+
data
|
|
318
|
+
})
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
var AxCloudContext = react.createContext(null);
|
|
322
|
+
function useAxCloudContext() {
|
|
323
|
+
const context = react.useContext(AxCloudContext);
|
|
324
|
+
if (!context) {
|
|
325
|
+
throw new Error("useAxCloudContext must be used within a AxCloudProvider");
|
|
326
|
+
}
|
|
327
|
+
return context;
|
|
328
|
+
}
|
|
329
|
+
var defaultQueryClient = new reactQuery.QueryClient({
|
|
330
|
+
defaultOptions: {
|
|
331
|
+
queries: {
|
|
332
|
+
staleTime: 1e3 * 60 * 5,
|
|
333
|
+
// 5 minutes
|
|
334
|
+
gcTime: 1e3 * 60 * 30,
|
|
335
|
+
// 30 minutes (garbage collection)
|
|
336
|
+
retry: 2,
|
|
337
|
+
refetchOnWindowFocus: false
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
function AxCloudInnerProvider({
|
|
342
|
+
children,
|
|
343
|
+
config,
|
|
344
|
+
timeout
|
|
345
|
+
}) {
|
|
346
|
+
const { getAccessTokenSilently, isAuthenticated } = auth0React.useAuth0();
|
|
347
|
+
const tokenGetterRef = react.useRef(async () => void 0);
|
|
348
|
+
tokenGetterRef.current = isAuthenticated ? async () => {
|
|
349
|
+
try {
|
|
350
|
+
return await getAccessTokenSilently();
|
|
351
|
+
} catch {
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
} : async () => void 0;
|
|
355
|
+
const client = react.useMemo(() => {
|
|
356
|
+
return new AxCloudClient({
|
|
357
|
+
baseUrl: config.apiUrl,
|
|
358
|
+
apiKey: config.apiKey,
|
|
359
|
+
timeout,
|
|
360
|
+
// Use a wrapper function that delegates to the ref
|
|
361
|
+
// This ensures the client always uses the current auth state
|
|
362
|
+
getAccessToken: async () => {
|
|
363
|
+
if (tokenGetterRef.current) {
|
|
364
|
+
return tokenGetterRef.current();
|
|
365
|
+
}
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}, [config.apiUrl, config.apiKey, timeout]);
|
|
370
|
+
const contextValue = react.useMemo(
|
|
371
|
+
() => ({
|
|
372
|
+
config,
|
|
373
|
+
client,
|
|
374
|
+
orgId: config.orgId,
|
|
375
|
+
stage: config.stage,
|
|
376
|
+
isPublicMode: Boolean(config.apiKey) && !isAuthenticated
|
|
377
|
+
}),
|
|
378
|
+
[config, client, isAuthenticated]
|
|
379
|
+
);
|
|
380
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AxCloudContext.Provider, { value: contextValue, children });
|
|
381
|
+
}
|
|
382
|
+
function AxCloudProvider({
|
|
383
|
+
children,
|
|
384
|
+
orgId,
|
|
385
|
+
apiUrl,
|
|
386
|
+
stage = "dev",
|
|
387
|
+
apiKey,
|
|
388
|
+
auth0,
|
|
389
|
+
queryClient = defaultQueryClient,
|
|
390
|
+
timeout
|
|
391
|
+
}) {
|
|
392
|
+
const config = react.useMemo(
|
|
393
|
+
() => ({
|
|
394
|
+
orgId,
|
|
395
|
+
apiUrl,
|
|
396
|
+
stage,
|
|
397
|
+
apiKey,
|
|
398
|
+
auth0
|
|
399
|
+
}),
|
|
400
|
+
[orgId, apiUrl, stage, apiKey, auth0]
|
|
401
|
+
);
|
|
402
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
403
|
+
auth0React.Auth0Provider,
|
|
404
|
+
{
|
|
405
|
+
domain: auth0.domain,
|
|
406
|
+
clientId: auth0.clientId,
|
|
407
|
+
authorizationParams: {
|
|
408
|
+
redirect_uri: auth0.redirectUri,
|
|
409
|
+
audience: auth0.audience
|
|
410
|
+
},
|
|
411
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AxCloudInnerProvider, { config, timeout, children })
|
|
412
|
+
}
|
|
413
|
+
) });
|
|
414
|
+
}
|
|
415
|
+
function useAuth() {
|
|
416
|
+
const { config } = useAxCloudContext();
|
|
417
|
+
const {
|
|
418
|
+
isAuthenticated,
|
|
419
|
+
isLoading,
|
|
420
|
+
user: auth0User,
|
|
421
|
+
error,
|
|
422
|
+
loginWithRedirect,
|
|
423
|
+
logout: auth0Logout,
|
|
424
|
+
getAccessTokenSilently,
|
|
425
|
+
getIdTokenClaims
|
|
426
|
+
} = auth0React.useAuth0();
|
|
427
|
+
const login = react.useCallback(
|
|
428
|
+
(options) => {
|
|
429
|
+
return loginWithRedirect({
|
|
430
|
+
appState: {
|
|
431
|
+
returnTo: options?.returnTo || window.location.pathname
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
[loginWithRedirect]
|
|
436
|
+
);
|
|
437
|
+
const logout = react.useCallback(
|
|
438
|
+
(options) => {
|
|
439
|
+
return auth0Logout({
|
|
440
|
+
logoutParams: {
|
|
441
|
+
returnTo: options?.returnTo || window.location.origin
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
[auth0Logout]
|
|
446
|
+
);
|
|
447
|
+
const getAccessToken = react.useCallback(async () => {
|
|
448
|
+
try {
|
|
449
|
+
return await getAccessTokenSilently();
|
|
450
|
+
} catch {
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
}, [getAccessTokenSilently]);
|
|
454
|
+
const getAuth0Client = react.useCallback(() => {
|
|
455
|
+
return {
|
|
456
|
+
loginWithRedirect,
|
|
457
|
+
logout: auth0Logout,
|
|
458
|
+
getAccessTokenSilently,
|
|
459
|
+
getIdTokenClaims,
|
|
460
|
+
user: auth0User,
|
|
461
|
+
isAuthenticated,
|
|
462
|
+
isLoading,
|
|
463
|
+
error
|
|
464
|
+
};
|
|
465
|
+
}, [
|
|
466
|
+
loginWithRedirect,
|
|
467
|
+
auth0Logout,
|
|
468
|
+
getAccessTokenSilently,
|
|
469
|
+
getIdTokenClaims,
|
|
470
|
+
auth0User,
|
|
471
|
+
isAuthenticated,
|
|
472
|
+
isLoading,
|
|
473
|
+
error
|
|
474
|
+
]);
|
|
475
|
+
const requestPasswordReset = react.useCallback(
|
|
476
|
+
async (email) => {
|
|
477
|
+
const targetEmail = email || auth0User?.email;
|
|
478
|
+
if (!targetEmail) {
|
|
479
|
+
return {
|
|
480
|
+
success: false,
|
|
481
|
+
message: "Email address is required"
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const auth0Domain = config.auth0.domain;
|
|
486
|
+
const clientId = config.auth0.clientId;
|
|
487
|
+
const connection = config.auth0.connection || "Username-Password-Authentication";
|
|
488
|
+
const response = await fetch(`https://${auth0Domain}/dbconnections/change_password`, {
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: {
|
|
491
|
+
"Content-Type": "application/json"
|
|
492
|
+
},
|
|
493
|
+
body: JSON.stringify({
|
|
494
|
+
client_id: clientId,
|
|
495
|
+
email: targetEmail.toLowerCase(),
|
|
496
|
+
connection
|
|
497
|
+
})
|
|
498
|
+
});
|
|
499
|
+
if (response.ok) {
|
|
500
|
+
return {
|
|
501
|
+
success: true,
|
|
502
|
+
message: "If an account exists with this email, a password reset link has been sent."
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
success: true,
|
|
507
|
+
message: "If an account exists with this email, a password reset link has been sent."
|
|
508
|
+
};
|
|
509
|
+
} catch {
|
|
510
|
+
return {
|
|
511
|
+
success: false,
|
|
512
|
+
message: "Failed to send password reset email. Please try again."
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
[auth0User?.email, config.auth0.domain, config.auth0.clientId]
|
|
517
|
+
);
|
|
518
|
+
return {
|
|
519
|
+
// Simple, abstracted API
|
|
520
|
+
isAuthenticated,
|
|
521
|
+
isLoading,
|
|
522
|
+
error,
|
|
523
|
+
user: auth0User ? {
|
|
524
|
+
id: auth0User.sub || "",
|
|
525
|
+
email: auth0User.email || "",
|
|
526
|
+
name: auth0User.name || "",
|
|
527
|
+
avatar: auth0User.picture
|
|
528
|
+
} : null,
|
|
529
|
+
login,
|
|
530
|
+
logout,
|
|
531
|
+
// Escape hatch for advanced use cases
|
|
532
|
+
getAccessToken,
|
|
533
|
+
getAuth0Client,
|
|
534
|
+
// Password management
|
|
535
|
+
requestPasswordReset
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function useUser() {
|
|
539
|
+
const { isAuthenticated, isLoading: isAuthLoading } = auth0React.useAuth0();
|
|
540
|
+
const { client, stage, orgId } = useAxCloudContext();
|
|
541
|
+
const query = reactQuery.useQuery({
|
|
542
|
+
queryKey: ["axcloud", "user", "me", stage],
|
|
543
|
+
queryFn: async () => {
|
|
544
|
+
return client.get(`/${stage}/api/users/me`);
|
|
545
|
+
},
|
|
546
|
+
enabled: isAuthenticated && !isAuthLoading,
|
|
547
|
+
staleTime: 1e3 * 60 * 5
|
|
548
|
+
// 5 minutes
|
|
549
|
+
});
|
|
550
|
+
const userData = query.data;
|
|
551
|
+
const userOrgIds = userData?.all_org_ids ?? [];
|
|
552
|
+
const isSuperAdmin = userData?.isSuperAdmin === true;
|
|
553
|
+
const hasOrgAccess = isSuperAdmin || userOrgIds.includes(orgId);
|
|
554
|
+
const orgMembership = userData?.organization_memberships?.[orgId];
|
|
555
|
+
const roleInOrg = orgMembership?.role ?? null;
|
|
556
|
+
return {
|
|
557
|
+
user: query.data ?? null,
|
|
558
|
+
isLoading: isAuthLoading || query.isLoading,
|
|
559
|
+
isError: query.isError,
|
|
560
|
+
error: query.error,
|
|
561
|
+
refetch: query.refetch,
|
|
562
|
+
// Organization access
|
|
563
|
+
hasOrgAccess,
|
|
564
|
+
roleInOrg,
|
|
565
|
+
// Convenience properties
|
|
566
|
+
isAuthenticated,
|
|
567
|
+
role: query.data?.role ?? null,
|
|
568
|
+
organizationId: query.data?.organizationId ?? null,
|
|
569
|
+
sectionId: query.data?.sectionId ?? null,
|
|
570
|
+
permissions: query.data?.permissions ?? [],
|
|
571
|
+
isSuperAdmin
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function useOrg() {
|
|
575
|
+
const { client, orgId, stage, config } = useAxCloudContext();
|
|
576
|
+
const usePublicEndpoint = Boolean(config.apiKey);
|
|
577
|
+
const query = reactQuery.useQuery({
|
|
578
|
+
queryKey: ["axcloud", "organization", orgId, stage, usePublicEndpoint],
|
|
579
|
+
queryFn: async () => {
|
|
580
|
+
if (usePublicEndpoint) {
|
|
581
|
+
return client.get(`/content/public/${orgId}/org`);
|
|
582
|
+
} else {
|
|
583
|
+
return client.get(`/${stage}/api/organizations/${orgId}`);
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
staleTime: 1e3 * 60 * 10
|
|
587
|
+
// 10 minutes (org data rarely changes)
|
|
588
|
+
});
|
|
589
|
+
const orgData = query.data;
|
|
590
|
+
const normalizedOrg = orgData ? {
|
|
591
|
+
id: orgData.id || orgData.org_id,
|
|
592
|
+
org_id: orgData.org_id || orgData.id,
|
|
593
|
+
name: orgData.name,
|
|
594
|
+
description: orgData.description,
|
|
595
|
+
branding: orgData.branding || orgData.settings?.theme,
|
|
596
|
+
modules: orgData.modules || [],
|
|
597
|
+
contact: orgData.contact,
|
|
598
|
+
social: orgData.social
|
|
599
|
+
} : null;
|
|
600
|
+
const hasModule = react.useCallback(
|
|
601
|
+
(module) => {
|
|
602
|
+
const mods = normalizedOrg?.modules || [];
|
|
603
|
+
return mods.includes(module);
|
|
604
|
+
},
|
|
605
|
+
[normalizedOrg?.modules]
|
|
606
|
+
);
|
|
607
|
+
const modules = normalizedOrg?.modules ?? [];
|
|
608
|
+
return {
|
|
609
|
+
org: normalizedOrg,
|
|
610
|
+
orgId,
|
|
611
|
+
isLoading: query.isLoading,
|
|
612
|
+
isError: query.isError,
|
|
613
|
+
error: query.error,
|
|
614
|
+
refetch: query.refetch,
|
|
615
|
+
// Module helpers
|
|
616
|
+
hasModule,
|
|
617
|
+
modules
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function useNews(options = {}) {
|
|
621
|
+
const { client, orgId, isPublicMode } = useAxCloudContext();
|
|
622
|
+
const {
|
|
623
|
+
page = 1,
|
|
624
|
+
pageSize = 20,
|
|
625
|
+
status = "published",
|
|
626
|
+
visibility,
|
|
627
|
+
sectionId,
|
|
628
|
+
search,
|
|
629
|
+
sortBy = "publishedAt",
|
|
630
|
+
sortOrder = "desc",
|
|
631
|
+
publicOnly = false
|
|
632
|
+
} = options;
|
|
633
|
+
const usePublicEndpoint = isPublicMode || publicOnly;
|
|
634
|
+
const query = reactQuery.useQuery({
|
|
635
|
+
queryKey: ["axcloud", "news", orgId, usePublicEndpoint, options],
|
|
636
|
+
queryFn: async () => {
|
|
637
|
+
const endpoint = usePublicEndpoint ? `/content/public/${orgId}/news` : `/content/members/${orgId}/news`;
|
|
638
|
+
return client.get(endpoint, {
|
|
639
|
+
page,
|
|
640
|
+
pageSize,
|
|
641
|
+
status,
|
|
642
|
+
visibility,
|
|
643
|
+
sectionId,
|
|
644
|
+
search,
|
|
645
|
+
sortBy,
|
|
646
|
+
sortOrder
|
|
647
|
+
});
|
|
648
|
+
},
|
|
649
|
+
staleTime: 1e3 * 60 * 5
|
|
650
|
+
// 5 minutes
|
|
651
|
+
});
|
|
652
|
+
return {
|
|
653
|
+
data: query.data?.items ?? [],
|
|
654
|
+
total: query.data?.total ?? 0,
|
|
655
|
+
hasMore: query.data?.hasMore ?? false,
|
|
656
|
+
isLoading: query.isLoading,
|
|
657
|
+
isError: query.isError,
|
|
658
|
+
error: query.error,
|
|
659
|
+
refetch: query.refetch
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
var MEMBER_ENDPOINT_TYPES = ["news", "pages", "events"];
|
|
663
|
+
function useContent(contentType, options = {}) {
|
|
664
|
+
const { client, orgId, isPublicMode } = useAxCloudContext();
|
|
665
|
+
const {
|
|
666
|
+
page = 1,
|
|
667
|
+
pageSize = 20,
|
|
668
|
+
status = "published",
|
|
669
|
+
visibility,
|
|
670
|
+
sectionId,
|
|
671
|
+
search,
|
|
672
|
+
sortBy = "createdAt",
|
|
673
|
+
sortOrder = "desc"
|
|
674
|
+
} = options;
|
|
675
|
+
const query = reactQuery.useQuery({
|
|
676
|
+
queryKey: ["axcloud", "content", orgId, contentType, isPublicMode, options],
|
|
677
|
+
queryFn: async () => {
|
|
678
|
+
const useMemberEndpoint = !isPublicMode && MEMBER_ENDPOINT_TYPES.includes(contentType);
|
|
679
|
+
const endpoint = useMemberEndpoint ? `/content/members/${orgId}/${contentType}` : `/content/public/${orgId}/${contentType}`;
|
|
680
|
+
return client.get(endpoint, {
|
|
681
|
+
page,
|
|
682
|
+
pageSize,
|
|
683
|
+
status,
|
|
684
|
+
visibility,
|
|
685
|
+
sectionId,
|
|
686
|
+
search,
|
|
687
|
+
sortBy,
|
|
688
|
+
sortOrder
|
|
689
|
+
});
|
|
690
|
+
},
|
|
691
|
+
staleTime: 1e3 * 60 * 5
|
|
692
|
+
// 5 minutes
|
|
693
|
+
});
|
|
694
|
+
return {
|
|
695
|
+
data: query.data?.items ?? [],
|
|
696
|
+
total: query.data?.total ?? 0,
|
|
697
|
+
hasMore: query.data?.hasMore ?? false,
|
|
698
|
+
isLoading: query.isLoading,
|
|
699
|
+
isError: query.isError,
|
|
700
|
+
error: query.error,
|
|
701
|
+
refetch: query.refetch
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function useContentItem(contentType, idOrSlug, options = {}) {
|
|
705
|
+
const { client, orgId, isPublicMode } = useAxCloudContext();
|
|
706
|
+
const { publicOnly = false } = options;
|
|
707
|
+
const useMemberEndpoint = !isPublicMode && !publicOnly && MEMBER_ENDPOINT_TYPES.includes(contentType);
|
|
708
|
+
const query = reactQuery.useQuery({
|
|
709
|
+
queryKey: ["axcloud", "content", orgId, contentType, idOrSlug, useMemberEndpoint],
|
|
710
|
+
queryFn: async () => {
|
|
711
|
+
const basePath = useMemberEndpoint ? "members" : "public";
|
|
712
|
+
return client.get(`/content/${basePath}/${orgId}/${contentType}/${idOrSlug}`);
|
|
713
|
+
},
|
|
714
|
+
enabled: !!idOrSlug,
|
|
715
|
+
staleTime: 1e3 * 60 * 5
|
|
716
|
+
// 5 minutes
|
|
717
|
+
});
|
|
718
|
+
return {
|
|
719
|
+
data: query.data ?? null,
|
|
720
|
+
isLoading: query.isLoading,
|
|
721
|
+
isError: query.isError,
|
|
722
|
+
error: query.error,
|
|
723
|
+
refetch: query.refetch
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function useMySection(options = {}) {
|
|
727
|
+
const { includeMembers = true } = options;
|
|
728
|
+
const { isAuthenticated } = auth0React.useAuth0();
|
|
729
|
+
const { client, orgId, stage } = useAxCloudContext();
|
|
730
|
+
const { user, sectionId, isLoading: isUserLoading } = useUser();
|
|
731
|
+
const query = reactQuery.useQuery({
|
|
732
|
+
queryKey: ["axcloud", "mySection", orgId, sectionId, includeMembers, stage],
|
|
733
|
+
queryFn: async () => {
|
|
734
|
+
if (!sectionId) {
|
|
735
|
+
throw new Error("User is not assigned to a section");
|
|
736
|
+
}
|
|
737
|
+
const section = await client.get(
|
|
738
|
+
`/${stage}/api/sections/${sectionId}`,
|
|
739
|
+
{ includeMembers: includeMembers ? "true" : void 0 }
|
|
740
|
+
);
|
|
741
|
+
return section;
|
|
742
|
+
},
|
|
743
|
+
enabled: isAuthenticated && !isUserLoading && !!sectionId,
|
|
744
|
+
staleTime: 1e3 * 60 * 5
|
|
745
|
+
// 5 minutes
|
|
746
|
+
});
|
|
747
|
+
return {
|
|
748
|
+
section: query.data ?? null,
|
|
749
|
+
sectionId,
|
|
750
|
+
members: query.data?.members ?? [],
|
|
751
|
+
memberCount: query.data?.memberCount ?? 0,
|
|
752
|
+
isLoading: isUserLoading || query.isLoading,
|
|
753
|
+
isError: query.isError,
|
|
754
|
+
error: query.error,
|
|
755
|
+
refetch: query.refetch,
|
|
756
|
+
// Helper: Does the user have a section?
|
|
757
|
+
hasSection: !!sectionId,
|
|
758
|
+
// The current user in context
|
|
759
|
+
user
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function useSections() {
|
|
763
|
+
const { client, orgId, stage } = useAxCloudContext();
|
|
764
|
+
const query = reactQuery.useQuery({
|
|
765
|
+
queryKey: ["axcloud", "sections", orgId, stage],
|
|
766
|
+
queryFn: async () => {
|
|
767
|
+
return client.get(`/${stage}/api/organizations/${orgId}/sections`);
|
|
768
|
+
},
|
|
769
|
+
staleTime: 1e3 * 60 * 10
|
|
770
|
+
// 10 minutes
|
|
771
|
+
});
|
|
772
|
+
return {
|
|
773
|
+
sections: query.data ?? [],
|
|
774
|
+
isLoading: query.isLoading,
|
|
775
|
+
isError: query.isError,
|
|
776
|
+
error: query.error,
|
|
777
|
+
refetch: query.refetch
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function useForms(options = {}) {
|
|
781
|
+
const { client, stage } = useAxCloudContext();
|
|
782
|
+
const { formType, status, limit = 50, cursor } = options;
|
|
783
|
+
const query = reactQuery.useQuery({
|
|
784
|
+
queryKey: ["axcloud", "forms", "submissions", stage, options],
|
|
785
|
+
queryFn: async () => {
|
|
786
|
+
const params = { limit };
|
|
787
|
+
if (formType) params.form_type = formType;
|
|
788
|
+
if (status) params.status = status;
|
|
789
|
+
if (cursor) params.cursor = cursor;
|
|
790
|
+
return client.get(`/${stage}/api/admin/forms/submissions`, params);
|
|
791
|
+
},
|
|
792
|
+
staleTime: 1e3 * 30
|
|
793
|
+
// 30 seconds
|
|
794
|
+
});
|
|
795
|
+
return {
|
|
796
|
+
data: query.data?.items ?? [],
|
|
797
|
+
total: query.data?.total ?? 0,
|
|
798
|
+
hasMore: query.data?.hasMore ?? false,
|
|
799
|
+
cursor: query.data?.cursor,
|
|
800
|
+
isLoading: query.isLoading,
|
|
801
|
+
isError: query.isError,
|
|
802
|
+
error: query.error,
|
|
803
|
+
refetch: query.refetch
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
function useFormStats(options = {}) {
|
|
807
|
+
const { client, stage } = useAxCloudContext();
|
|
808
|
+
const { days = 30 } = options;
|
|
809
|
+
const query = reactQuery.useQuery({
|
|
810
|
+
queryKey: ["axcloud", "forms", "stats", stage, days],
|
|
811
|
+
queryFn: async () => {
|
|
812
|
+
return client.get(`/${stage}/api/admin/forms/stats`, { days });
|
|
813
|
+
},
|
|
814
|
+
staleTime: 1e3 * 60 * 5
|
|
815
|
+
// 5 minutes
|
|
816
|
+
});
|
|
817
|
+
return {
|
|
818
|
+
stats: query.data?.stats,
|
|
819
|
+
summary: query.data?.summary,
|
|
820
|
+
period: query.data?.period,
|
|
821
|
+
isLoading: query.isLoading,
|
|
822
|
+
isError: query.isError,
|
|
823
|
+
error: query.error,
|
|
824
|
+
refetch: query.refetch
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function useUpdateFormSubmission() {
|
|
828
|
+
const { client, stage } = useAxCloudContext();
|
|
829
|
+
const queryClient = reactQuery.useQueryClient();
|
|
830
|
+
return reactQuery.useMutation({
|
|
831
|
+
mutationFn: async ({
|
|
832
|
+
submissionId,
|
|
833
|
+
status,
|
|
834
|
+
notes
|
|
835
|
+
}) => {
|
|
836
|
+
return client.patch(`/${stage}/api/admin/forms/submissions/${submissionId}`, { status, notes });
|
|
837
|
+
},
|
|
838
|
+
onSuccess: () => {
|
|
839
|
+
queryClient.invalidateQueries({ queryKey: ["axcloud", "forms"] });
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
var LINKED_ACCOUNTS_QUERY_KEY = ["axcloud", "linked-accounts"];
|
|
844
|
+
function useLinkedAccounts() {
|
|
845
|
+
const { isAuthenticated } = useAuth();
|
|
846
|
+
const queryClient = reactQuery.useQueryClient();
|
|
847
|
+
const query = reactQuery.useQuery({
|
|
848
|
+
queryKey: LINKED_ACCOUNTS_QUERY_KEY,
|
|
849
|
+
queryFn: async () => {
|
|
850
|
+
const client = getClient();
|
|
851
|
+
return client.get("/api/users/me/identities");
|
|
852
|
+
},
|
|
853
|
+
enabled: isAuthenticated,
|
|
854
|
+
staleTime: 1e3 * 60 * 5
|
|
855
|
+
// 5 minutes
|
|
856
|
+
});
|
|
857
|
+
const linkMutation = reactQuery.useMutation({
|
|
858
|
+
mutationFn: async (provider) => {
|
|
859
|
+
const client = getClient();
|
|
860
|
+
const response = await client.post(
|
|
861
|
+
`/api/users/me/identities/${provider}/link`,
|
|
862
|
+
{}
|
|
863
|
+
);
|
|
864
|
+
if (response.authorization_url) {
|
|
865
|
+
window.location.href = response.authorization_url;
|
|
866
|
+
}
|
|
867
|
+
return response;
|
|
868
|
+
},
|
|
869
|
+
onSuccess: () => {
|
|
870
|
+
queryClient.invalidateQueries({ queryKey: LINKED_ACCOUNTS_QUERY_KEY });
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
const unlinkMutation = reactQuery.useMutation({
|
|
874
|
+
mutationFn: async (provider) => {
|
|
875
|
+
const client = getClient();
|
|
876
|
+
return client.delete(
|
|
877
|
+
`/api/users/me/identities/${provider}`
|
|
878
|
+
);
|
|
879
|
+
},
|
|
880
|
+
onSuccess: () => {
|
|
881
|
+
queryClient.invalidateQueries({ queryKey: LINKED_ACCOUNTS_QUERY_KEY });
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
return {
|
|
885
|
+
identities: query.data?.identities ?? [],
|
|
886
|
+
availableProviders: query.data?.available_providers ?? [],
|
|
887
|
+
canUnlink: query.data?.can_unlink ?? false,
|
|
888
|
+
isLoading: query.isLoading,
|
|
889
|
+
error: query.error,
|
|
890
|
+
linkAccount: linkMutation.mutate,
|
|
891
|
+
unlinkAccount: unlinkMutation.mutate,
|
|
892
|
+
isLinking: linkMutation.isPending,
|
|
893
|
+
isUnlinking: unlinkMutation.isPending,
|
|
894
|
+
refetch: query.refetch
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function useBranding() {
|
|
898
|
+
const { client, orgId } = useAxCloudContext();
|
|
899
|
+
const query = reactQuery.useQuery({
|
|
900
|
+
queryKey: ["axcloud", "media", "branding", orgId],
|
|
901
|
+
queryFn: async () => {
|
|
902
|
+
return client.get(`/media/public/${orgId}/branding`);
|
|
903
|
+
},
|
|
904
|
+
staleTime: 1e3 * 60 * 30,
|
|
905
|
+
// 30 minutes (logo URL valid for 1 hour)
|
|
906
|
+
gcTime: 1e3 * 60 * 55
|
|
907
|
+
// Garbage collect before URL expires
|
|
908
|
+
});
|
|
909
|
+
return {
|
|
910
|
+
branding: query.data?.branding ?? null,
|
|
911
|
+
logoUrl: query.data?.branding?.logoUrl ?? null,
|
|
912
|
+
organizationName: query.data?.name ?? null,
|
|
913
|
+
isLoading: query.isLoading,
|
|
914
|
+
isError: query.isError,
|
|
915
|
+
error: query.error,
|
|
916
|
+
refetch: query.refetch
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function useLogo() {
|
|
920
|
+
const { client, orgId } = useAxCloudContext();
|
|
921
|
+
const query = reactQuery.useQuery({
|
|
922
|
+
queryKey: ["axcloud", "media", "logo", orgId],
|
|
923
|
+
queryFn: async () => {
|
|
924
|
+
return client.get(`/media/public/${orgId}/logo`);
|
|
925
|
+
},
|
|
926
|
+
staleTime: 1e3 * 60 * 30,
|
|
927
|
+
// 30 minutes
|
|
928
|
+
gcTime: 1e3 * 60 * 55
|
|
929
|
+
// Garbage collect before URL expires
|
|
930
|
+
});
|
|
931
|
+
return {
|
|
932
|
+
logoUrl: query.data?.url ?? null,
|
|
933
|
+
expiresIn: query.data?.expiresIn ?? null,
|
|
934
|
+
isLoading: query.isLoading,
|
|
935
|
+
isError: query.isError,
|
|
936
|
+
error: query.error,
|
|
937
|
+
refetch: query.refetch
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function useAvatar(userId) {
|
|
941
|
+
const { client, orgId } = useAxCloudContext();
|
|
942
|
+
const query = reactQuery.useQuery({
|
|
943
|
+
queryKey: ["axcloud", "media", "avatar", orgId, userId],
|
|
944
|
+
queryFn: async () => {
|
|
945
|
+
return client.get(`/media/public/${orgId}/avatar/${userId}`);
|
|
946
|
+
},
|
|
947
|
+
enabled: !!userId,
|
|
948
|
+
staleTime: 1e3 * 60 * 30,
|
|
949
|
+
gcTime: 1e3 * 60 * 55,
|
|
950
|
+
retry: false
|
|
951
|
+
// Don't retry if avatar doesn't exist
|
|
952
|
+
});
|
|
953
|
+
return {
|
|
954
|
+
avatarUrl: query.data?.url ?? null,
|
|
955
|
+
isLoading: query.isLoading,
|
|
956
|
+
isError: query.isError,
|
|
957
|
+
error: query.error,
|
|
958
|
+
refetch: query.refetch
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function useMediaList(type = "media", limit = 50) {
|
|
962
|
+
const { client, orgId } = useAxCloudContext();
|
|
963
|
+
const query = reactQuery.useQuery({
|
|
964
|
+
queryKey: ["axcloud", "media", "list", orgId, type, limit],
|
|
965
|
+
queryFn: async () => {
|
|
966
|
+
return client.get(`/media/public/${orgId}/list`, {
|
|
967
|
+
type,
|
|
968
|
+
limit
|
|
969
|
+
});
|
|
970
|
+
},
|
|
971
|
+
staleTime: 1e3 * 60 * 5
|
|
972
|
+
// 5 minutes
|
|
973
|
+
});
|
|
974
|
+
return {
|
|
975
|
+
items: query.data?.items ?? [],
|
|
976
|
+
count: query.data?.count ?? 0,
|
|
977
|
+
isLoading: query.isLoading,
|
|
978
|
+
isError: query.isError,
|
|
979
|
+
error: query.error,
|
|
980
|
+
refetch: query.refetch
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function useMedia(mediaId) {
|
|
984
|
+
const { client, orgId } = useAxCloudContext();
|
|
985
|
+
const query = reactQuery.useQuery({
|
|
986
|
+
queryKey: ["axcloud", "media", "file", orgId, mediaId],
|
|
987
|
+
queryFn: async () => {
|
|
988
|
+
return client.get(`/media/public/${orgId}/media/${mediaId}`);
|
|
989
|
+
},
|
|
990
|
+
enabled: !!mediaId,
|
|
991
|
+
staleTime: 1e3 * 60 * 30,
|
|
992
|
+
gcTime: 1e3 * 60 * 55
|
|
993
|
+
});
|
|
994
|
+
return {
|
|
995
|
+
mediaUrl: query.data?.url ?? null,
|
|
996
|
+
expiresIn: query.data?.expiresIn ?? null,
|
|
997
|
+
isLoading: query.isLoading,
|
|
998
|
+
isError: query.isError,
|
|
999
|
+
error: query.error,
|
|
1000
|
+
refetch: query.refetch
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function LoginButton({
|
|
1004
|
+
children = "Log in",
|
|
1005
|
+
returnTo,
|
|
1006
|
+
className,
|
|
1007
|
+
style
|
|
1008
|
+
}) {
|
|
1009
|
+
const { login, isLoading } = useAuth();
|
|
1010
|
+
const handleClick = () => {
|
|
1011
|
+
login({ returnTo });
|
|
1012
|
+
};
|
|
1013
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1014
|
+
"button",
|
|
1015
|
+
{
|
|
1016
|
+
onClick: handleClick,
|
|
1017
|
+
disabled: isLoading,
|
|
1018
|
+
className,
|
|
1019
|
+
style,
|
|
1020
|
+
type: "button",
|
|
1021
|
+
children: isLoading ? "Loading..." : children
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
function LogoutButton({
|
|
1026
|
+
children = "Log out",
|
|
1027
|
+
returnTo,
|
|
1028
|
+
className,
|
|
1029
|
+
style
|
|
1030
|
+
}) {
|
|
1031
|
+
const { logout } = useAuth();
|
|
1032
|
+
const handleClick = () => {
|
|
1033
|
+
logout({ returnTo });
|
|
1034
|
+
};
|
|
1035
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1036
|
+
"button",
|
|
1037
|
+
{
|
|
1038
|
+
onClick: handleClick,
|
|
1039
|
+
className,
|
|
1040
|
+
style,
|
|
1041
|
+
type: "button",
|
|
1042
|
+
children
|
|
1043
|
+
}
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
function ProtectedRoute({
|
|
1047
|
+
children,
|
|
1048
|
+
roles,
|
|
1049
|
+
loadingComponent,
|
|
1050
|
+
unauthenticatedComponent,
|
|
1051
|
+
unauthorizedComponent,
|
|
1052
|
+
redirectToLogin = false,
|
|
1053
|
+
returnTo
|
|
1054
|
+
}) {
|
|
1055
|
+
const { isAuthenticated, isLoading: isAuthLoading, login } = useAuth();
|
|
1056
|
+
const { user, role, isLoading: isUserLoading } = useUser();
|
|
1057
|
+
const isLoading = isAuthLoading || isAuthenticated && isUserLoading;
|
|
1058
|
+
if (isLoading) {
|
|
1059
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultLoadingComponent, {}) });
|
|
1060
|
+
}
|
|
1061
|
+
if (!isAuthenticated) {
|
|
1062
|
+
if (redirectToLogin) {
|
|
1063
|
+
login({ returnTo: returnTo || window.location.pathname });
|
|
1064
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultLoadingComponent, {}) });
|
|
1065
|
+
}
|
|
1066
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: unauthenticatedComponent ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultUnauthenticatedComponent, {}) });
|
|
1067
|
+
}
|
|
1068
|
+
if (roles && role) {
|
|
1069
|
+
const requiredRoles = Array.isArray(roles) ? roles : [roles];
|
|
1070
|
+
const hasRequiredRole = requiredRoles.includes(role);
|
|
1071
|
+
if (!hasRequiredRole) {
|
|
1072
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: unauthorizedComponent ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultUnauthorizedComponent, {}) });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1076
|
+
}
|
|
1077
|
+
function DefaultLoadingComponent() {
|
|
1078
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "20px", textAlign: "center" }, children: "Loading..." });
|
|
1079
|
+
}
|
|
1080
|
+
function DefaultUnauthenticatedComponent() {
|
|
1081
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "20px", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Please log in to view this content." }) });
|
|
1082
|
+
}
|
|
1083
|
+
function DefaultUnauthorizedComponent() {
|
|
1084
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "20px", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "You don't have permission to view this content." }) });
|
|
1085
|
+
}
|
|
1086
|
+
function ModuleGate({
|
|
1087
|
+
module,
|
|
1088
|
+
children,
|
|
1089
|
+
fallback = null,
|
|
1090
|
+
requireAll = false
|
|
1091
|
+
}) {
|
|
1092
|
+
const { hasModule, isLoading } = useOrg();
|
|
1093
|
+
if (isLoading) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
const requiredModules = Array.isArray(module) ? module : [module];
|
|
1097
|
+
const hasAccess = requireAll ? requiredModules.every((m) => hasModule(m)) : requiredModules.some((m) => hasModule(m));
|
|
1098
|
+
if (!hasAccess) {
|
|
1099
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
|
|
1100
|
+
}
|
|
1101
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
1102
|
+
}
|
|
1103
|
+
function useModuleAccess() {
|
|
1104
|
+
const { hasModule, modules, isLoading } = useOrg();
|
|
1105
|
+
return {
|
|
1106
|
+
hasModule,
|
|
1107
|
+
modules,
|
|
1108
|
+
isLoading
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
var SUPPORTED_OAUTH_PROVIDERS = ["google", "microsoft", "apple"];
|
|
1112
|
+
function isSupportedOAuthProvider(provider) {
|
|
1113
|
+
return SUPPORTED_OAUTH_PROVIDERS.includes(provider);
|
|
1114
|
+
}
|
|
1115
|
+
var ProviderIcons = {
|
|
1116
|
+
google: ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", className, fill: "currentColor", children: [
|
|
1117
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }),
|
|
1118
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }),
|
|
1119
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }),
|
|
1120
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })
|
|
1121
|
+
] }),
|
|
1122
|
+
microsoft: ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", className, children: [
|
|
1123
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#F25022", d: "M1 1h10v10H1z" }),
|
|
1124
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#00A4EF", d: "M1 13h10v10H1z" }),
|
|
1125
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#7FBA00", d: "M13 1h10v10H13z" }),
|
|
1126
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { fill: "#FFB900", d: "M13 13h10v10H13z" })
|
|
1127
|
+
] }),
|
|
1128
|
+
apple: ({ className }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className, fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" }) }),
|
|
1129
|
+
email: ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", className, fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1130
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" }),
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "22,6 12,13 2,6" })
|
|
1132
|
+
] })
|
|
1133
|
+
};
|
|
1134
|
+
function LinkedAccountsList({
|
|
1135
|
+
providers,
|
|
1136
|
+
showConnectButtons = true,
|
|
1137
|
+
className,
|
|
1138
|
+
onUnlink,
|
|
1139
|
+
onLinkInitiated,
|
|
1140
|
+
renderIdentity,
|
|
1141
|
+
renderAvailableProvider
|
|
1142
|
+
}) {
|
|
1143
|
+
const {
|
|
1144
|
+
identities,
|
|
1145
|
+
availableProviders,
|
|
1146
|
+
canUnlink,
|
|
1147
|
+
isLoading,
|
|
1148
|
+
error,
|
|
1149
|
+
linkAccount,
|
|
1150
|
+
unlinkAccount
|
|
1151
|
+
} = useLinkedAccounts();
|
|
1152
|
+
const filteredIdentities = providers ? identities.filter(
|
|
1153
|
+
(i) => providers.some(
|
|
1154
|
+
(p) => i.provider.toLowerCase().includes(p) || i.provider_icon === p
|
|
1155
|
+
)
|
|
1156
|
+
) : identities;
|
|
1157
|
+
const filteredAvailableProviders = providers ? availableProviders.filter(
|
|
1158
|
+
(p) => providers.some(
|
|
1159
|
+
(provider) => p.provider.toLowerCase().includes(provider) || p.icon === provider
|
|
1160
|
+
)
|
|
1161
|
+
) : availableProviders;
|
|
1162
|
+
const handleUnlink = (provider) => {
|
|
1163
|
+
unlinkAccount(provider);
|
|
1164
|
+
onUnlink?.(provider);
|
|
1165
|
+
};
|
|
1166
|
+
const handleLink = (provider) => {
|
|
1167
|
+
onLinkInitiated?.(provider);
|
|
1168
|
+
linkAccount(provider);
|
|
1169
|
+
};
|
|
1170
|
+
if (isLoading) {
|
|
1171
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `axcloud-linked-accounts__loading ${className || ""}`, children: "Loading linked accounts..." });
|
|
1172
|
+
}
|
|
1173
|
+
if (error) {
|
|
1174
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `axcloud-linked-accounts__error ${className || ""}`, children: "Failed to load linked accounts. Please try again." });
|
|
1175
|
+
}
|
|
1176
|
+
const getIcon = (iconName, muted = false) => {
|
|
1177
|
+
const IconComponent = ProviderIcons[iconName] || ProviderIcons.email;
|
|
1178
|
+
const iconClass = muted ? "axcloud-linked-accounts__icon axcloud-linked-accounts__icon--muted" : "axcloud-linked-accounts__icon";
|
|
1179
|
+
return /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { className: iconClass });
|
|
1180
|
+
};
|
|
1181
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `axcloud-linked-accounts ${className || ""}`, children: [
|
|
1182
|
+
filteredIdentities.map((identity) => {
|
|
1183
|
+
if (renderIdentity) {
|
|
1184
|
+
return renderIdentity(identity, () => handleUnlink(identity.provider));
|
|
1185
|
+
}
|
|
1186
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "axcloud-linked-accounts__item", children: [
|
|
1187
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "axcloud-linked-accounts__info", children: [
|
|
1188
|
+
getIcon(identity.provider_icon),
|
|
1189
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1190
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "axcloud-linked-accounts__name-row", children: [
|
|
1191
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "axcloud-linked-accounts__provider-name", children: identity.provider_display_name }),
|
|
1192
|
+
identity.is_primary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "axcloud-linked-accounts__badge", children: "Primary" })
|
|
1193
|
+
] }),
|
|
1194
|
+
identity.profile_data?.email && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "axcloud-linked-accounts__email", children: identity.profile_data.email })
|
|
1195
|
+
] })
|
|
1196
|
+
] }),
|
|
1197
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "axcloud-linked-accounts__actions", children: [
|
|
1198
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "axcloud-linked-accounts__connected", children: [
|
|
1199
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) }),
|
|
1200
|
+
"Connected"
|
|
1201
|
+
] }),
|
|
1202
|
+
canUnlink && !identity.is_primary && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1203
|
+
"button",
|
|
1204
|
+
{
|
|
1205
|
+
onClick: () => handleUnlink(identity.provider),
|
|
1206
|
+
className: "axcloud-linked-accounts__button axcloud-linked-accounts__button--disconnect",
|
|
1207
|
+
children: "Disconnect"
|
|
1208
|
+
}
|
|
1209
|
+
)
|
|
1210
|
+
] })
|
|
1211
|
+
] }, `${identity.provider}-${identity.user_id}`);
|
|
1212
|
+
}),
|
|
1213
|
+
showConnectButtons && filteredAvailableProviders.map((provider) => {
|
|
1214
|
+
if (renderAvailableProvider) {
|
|
1215
|
+
return renderAvailableProvider(provider, () => {
|
|
1216
|
+
if (isSupportedOAuthProvider(provider.provider)) {
|
|
1217
|
+
handleLink(provider.provider);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "axcloud-linked-accounts__available", children: [
|
|
1222
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "axcloud-linked-accounts__info", children: [
|
|
1223
|
+
getIcon(provider.icon, true),
|
|
1224
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1225
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "axcloud-linked-accounts__provider-name", children: provider.display_name }),
|
|
1226
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "axcloud-linked-accounts__email", children: "Not connected" })
|
|
1227
|
+
] })
|
|
1228
|
+
] }),
|
|
1229
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1230
|
+
"button",
|
|
1231
|
+
{
|
|
1232
|
+
onClick: () => {
|
|
1233
|
+
if (isSupportedOAuthProvider(provider.provider)) {
|
|
1234
|
+
handleLink(provider.provider);
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
className: "axcloud-linked-accounts__button axcloud-linked-accounts__button--connect",
|
|
1238
|
+
children: "Connect"
|
|
1239
|
+
}
|
|
1240
|
+
)
|
|
1241
|
+
] }, provider.provider);
|
|
1242
|
+
})
|
|
1243
|
+
] });
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
exports.AxCloudClient = AxCloudClient;
|
|
1247
|
+
exports.AxCloudError = AxCloudError;
|
|
1248
|
+
exports.AxCloudProvider = AxCloudProvider;
|
|
1249
|
+
exports.LinkedAccountsList = LinkedAccountsList;
|
|
1250
|
+
exports.LoginButton = LoginButton;
|
|
1251
|
+
exports.LogoutButton = LogoutButton;
|
|
1252
|
+
exports.ModuleGate = ModuleGate;
|
|
1253
|
+
exports.ProtectedRoute = ProtectedRoute;
|
|
1254
|
+
exports.createClient = createClient;
|
|
1255
|
+
exports.createFormHelper = createFormHelper;
|
|
1256
|
+
exports.getClient = getClient;
|
|
1257
|
+
exports.isAxCloudError = isAxCloudError;
|
|
1258
|
+
exports.submitForm = submitForm;
|
|
1259
|
+
exports.useAuth = useAuth;
|
|
1260
|
+
exports.useAvatar = useAvatar;
|
|
1261
|
+
exports.useAxCloudContext = useAxCloudContext;
|
|
1262
|
+
exports.useBranding = useBranding;
|
|
1263
|
+
exports.useContent = useContent;
|
|
1264
|
+
exports.useContentItem = useContentItem;
|
|
1265
|
+
exports.useFormStats = useFormStats;
|
|
1266
|
+
exports.useForms = useForms;
|
|
1267
|
+
exports.useLinkedAccounts = useLinkedAccounts;
|
|
1268
|
+
exports.useLogo = useLogo;
|
|
1269
|
+
exports.useMedia = useMedia;
|
|
1270
|
+
exports.useMediaList = useMediaList;
|
|
1271
|
+
exports.useModuleAccess = useModuleAccess;
|
|
1272
|
+
exports.useMySection = useMySection;
|
|
1273
|
+
exports.useNews = useNews;
|
|
1274
|
+
exports.useOrg = useOrg;
|
|
1275
|
+
exports.useSections = useSections;
|
|
1276
|
+
exports.useUpdateFormSubmission = useUpdateFormSubmission;
|
|
1277
|
+
exports.useUser = useUser;
|
|
1278
|
+
exports.userHasAllPermissions = userHasAllPermissions;
|
|
1279
|
+
exports.userHasAnyPermission = userHasAnyPermission;
|
|
1280
|
+
exports.userHasPermission = userHasPermission;
|
|
1281
|
+
//# sourceMappingURL=index.js.map
|
|
1282
|
+
//# sourceMappingURL=index.js.map
|