@axcode/axcloud-sdk 0.1.7-dev.87ea138 → 0.1.7-dev.cc5cc69
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 +265 -144
- package/dist/index.d.mts +291 -92
- package/dist/index.d.ts +291 -92
- package/dist/index.js +808 -171
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +799 -174
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var AxCloudError = class _AxCloudError extends Error {
|
|
|
20
20
|
}
|
|
21
21
|
static fromResponse(status, body) {
|
|
22
22
|
const details = typeof body === "object" ? body : void 0;
|
|
23
|
-
const message = details?.message;
|
|
23
|
+
const message = (typeof details?.message === "string" ? details.message : void 0) || (typeof details?.error === "string" ? details.error : void 0);
|
|
24
24
|
switch (status) {
|
|
25
25
|
case 401:
|
|
26
26
|
return new _AxCloudError(
|
|
@@ -30,19 +30,9 @@ var AxCloudError = class _AxCloudError extends Error {
|
|
|
30
30
|
details
|
|
31
31
|
);
|
|
32
32
|
case 403:
|
|
33
|
-
return new _AxCloudError(
|
|
34
|
-
message || "Access denied",
|
|
35
|
-
"FORBIDDEN",
|
|
36
|
-
status,
|
|
37
|
-
details
|
|
38
|
-
);
|
|
33
|
+
return new _AxCloudError(message || "Access denied", "FORBIDDEN", status, details);
|
|
39
34
|
case 404:
|
|
40
|
-
return new _AxCloudError(
|
|
41
|
-
message || "Resource not found",
|
|
42
|
-
"NOT_FOUND",
|
|
43
|
-
status,
|
|
44
|
-
details
|
|
45
|
-
);
|
|
35
|
+
return new _AxCloudError(message || "Resource not found", "NOT_FOUND", status, details);
|
|
46
36
|
case 422:
|
|
47
37
|
return new _AxCloudError(
|
|
48
38
|
message || "Validation failed",
|
|
@@ -52,19 +42,9 @@ var AxCloudError = class _AxCloudError extends Error {
|
|
|
52
42
|
);
|
|
53
43
|
default:
|
|
54
44
|
if (status >= 500) {
|
|
55
|
-
return new _AxCloudError(
|
|
56
|
-
message || "Server error",
|
|
57
|
-
"SERVER_ERROR",
|
|
58
|
-
status,
|
|
59
|
-
details
|
|
60
|
-
);
|
|
45
|
+
return new _AxCloudError(message || "Server error", "SERVER_ERROR", status, details);
|
|
61
46
|
}
|
|
62
|
-
return new _AxCloudError(
|
|
63
|
-
message || "An error occurred",
|
|
64
|
-
"UNKNOWN_ERROR",
|
|
65
|
-
status,
|
|
66
|
-
details
|
|
67
|
-
);
|
|
47
|
+
return new _AxCloudError(message || "An error occurred", "UNKNOWN_ERROR", status, details);
|
|
68
48
|
}
|
|
69
49
|
}
|
|
70
50
|
static networkError(originalError) {
|
|
@@ -78,12 +58,9 @@ var AxCloudError = class _AxCloudError extends Error {
|
|
|
78
58
|
);
|
|
79
59
|
}
|
|
80
60
|
static timeoutError(timeoutMs) {
|
|
81
|
-
return new _AxCloudError(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
0,
|
|
85
|
-
{ timeout: timeoutMs }
|
|
86
|
-
);
|
|
61
|
+
return new _AxCloudError(`Request timed out after ${timeoutMs}ms`, "TIMEOUT_ERROR", 0, {
|
|
62
|
+
timeout: timeoutMs
|
|
63
|
+
});
|
|
87
64
|
}
|
|
88
65
|
};
|
|
89
66
|
function isAxCloudError(error) {
|
|
@@ -238,6 +215,130 @@ function getClient(key) {
|
|
|
238
215
|
throw new Error("AxCloud client not found. Make sure AxCloudProvider is mounted and use useAxCloudContext() to get the client.");
|
|
239
216
|
}
|
|
240
217
|
|
|
218
|
+
// src/core/analytics.ts
|
|
219
|
+
var EVENT_NAME_PATTERN = /^[a-z][a-z0-9_]{1,63}$/;
|
|
220
|
+
var PROPERTY_KEY_PATTERN = /^[a-z][a-z0-9_]{0,47}$/;
|
|
221
|
+
var SENSITIVE_PROPERTY_PATTERN = /(^|_)(auth0|email|name|password|query|search|secret|token|trace|user|userid)($|_)/;
|
|
222
|
+
var EMAIL_VALUE_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
223
|
+
var OPAQUE_SEGMENT_PATTERNS = [
|
|
224
|
+
/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i,
|
|
225
|
+
/^(org|user|section|document|content|poll|invitation|invite|trace|chan)[_-][a-z0-9_-]+$/i,
|
|
226
|
+
/^[a-f0-9]{20,}$/i,
|
|
227
|
+
/^[a-z0-9_-]{32,}$/i
|
|
228
|
+
];
|
|
229
|
+
var scriptLoads = /* @__PURE__ */ new Map();
|
|
230
|
+
function isLocalAnalyticsHostname(hostname) {
|
|
231
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
232
|
+
}
|
|
233
|
+
function sanitizeAnalyticsPath(pathname, customNormalizer) {
|
|
234
|
+
const rawPath = pathname.split(/[?#]/, 1)[0] || "/";
|
|
235
|
+
const normalizedInput = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
236
|
+
const custom = customNormalizer?.(normalizedInput);
|
|
237
|
+
if (custom === null) return null;
|
|
238
|
+
const path = custom ?? normalizedInput;
|
|
239
|
+
const cleanPath = path.split(/[?#]/, 1)[0] || "/";
|
|
240
|
+
const segments = cleanPath.split("/").map((segment) => {
|
|
241
|
+
if (!segment) return segment;
|
|
242
|
+
let decoded = segment;
|
|
243
|
+
try {
|
|
244
|
+
decoded = decodeURIComponent(segment);
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
return OPAQUE_SEGMENT_PATTERNS.some((pattern) => pattern.test(decoded)) ? ":id" : segment;
|
|
248
|
+
});
|
|
249
|
+
return segments.join("/") || "/";
|
|
250
|
+
}
|
|
251
|
+
function sanitizeAnalyticsEventData(data) {
|
|
252
|
+
if (!data) return void 0;
|
|
253
|
+
const sanitized = {};
|
|
254
|
+
for (const [rawKey, rawValue] of Object.entries(data).slice(0, 20)) {
|
|
255
|
+
const key = rawKey.trim().toLowerCase();
|
|
256
|
+
if (!PROPERTY_KEY_PATTERN.test(key) || SENSITIVE_PROPERTY_PATTERN.test(key)) continue;
|
|
257
|
+
if (typeof rawValue === "string") {
|
|
258
|
+
const value = rawValue.trim();
|
|
259
|
+
if (!value || value.length > 100 || EMAIL_VALUE_PATTERN.test(value)) continue;
|
|
260
|
+
sanitized[key] = value;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (typeof rawValue === "number" && Number.isFinite(rawValue)) {
|
|
264
|
+
sanitized[key] = rawValue;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (typeof rawValue === "boolean") sanitized[key] = rawValue;
|
|
268
|
+
}
|
|
269
|
+
return Object.keys(sanitized).length > 0 ? sanitized : void 0;
|
|
270
|
+
}
|
|
271
|
+
function isAllowedAnalyticsEvent(eventName, allowedEvents) {
|
|
272
|
+
return EVENT_NAME_PATTERN.test(eventName) && Boolean(allowedEvents?.includes(eventName));
|
|
273
|
+
}
|
|
274
|
+
async function fetchAnalyticsPublicConfig(input) {
|
|
275
|
+
const baseUrl = input.apiUrl.replace(/\/$/, "");
|
|
276
|
+
const url = new URL(`${baseUrl}/analytics/public/config`);
|
|
277
|
+
url.searchParams.set("orgId", input.orgId);
|
|
278
|
+
url.searchParams.set("siteKey", input.siteKey);
|
|
279
|
+
url.searchParams.set("stage", input.stage);
|
|
280
|
+
url.searchParams.set("hostname", input.hostname);
|
|
281
|
+
const response = await fetch(url, {
|
|
282
|
+
method: "GET",
|
|
283
|
+
headers: { Accept: "application/json" },
|
|
284
|
+
signal: input.signal
|
|
285
|
+
});
|
|
286
|
+
if (!response.ok) throw new Error(`Analytics configuration returned ${response.status}`);
|
|
287
|
+
return response.json();
|
|
288
|
+
}
|
|
289
|
+
function loadAnalyticsScript(config, doc = document) {
|
|
290
|
+
if (!config.enabled || !config.websiteId || !config.scriptUrl) {
|
|
291
|
+
return Promise.reject(new Error("Analytics tracker configuration is incomplete"));
|
|
292
|
+
}
|
|
293
|
+
const key = `${config.scriptUrl}|${config.websiteId}`;
|
|
294
|
+
const existingPromise = scriptLoads.get(key);
|
|
295
|
+
if (existingPromise) return existingPromise;
|
|
296
|
+
const existing = doc.querySelector(
|
|
297
|
+
`script[data-axcloud-analytics-site="${config.websiteId}"]`
|
|
298
|
+
);
|
|
299
|
+
if (existing && doc.defaultView?.umami) {
|
|
300
|
+
return Promise.resolve();
|
|
301
|
+
}
|
|
302
|
+
const promise = new Promise((resolve, reject) => {
|
|
303
|
+
const script = existing || doc.createElement("script");
|
|
304
|
+
const onLoad = () => resolve();
|
|
305
|
+
const onError = () => reject(new Error("Analytics tracker failed to load"));
|
|
306
|
+
script.addEventListener("load", onLoad, { once: true });
|
|
307
|
+
script.addEventListener("error", onError, { once: true });
|
|
308
|
+
if (!existing) {
|
|
309
|
+
script.src = config.scriptUrl;
|
|
310
|
+
script.defer = true;
|
|
311
|
+
script.dataset.websiteId = config.websiteId;
|
|
312
|
+
script.dataset.axcloudAnalyticsSite = config.websiteId;
|
|
313
|
+
script.dataset.autoTrack = String(config.autoTrack === true);
|
|
314
|
+
if (config.hostUrl) script.dataset.hostUrl = config.hostUrl;
|
|
315
|
+
if (config.domains?.length) script.dataset.domains = config.domains.join(",");
|
|
316
|
+
if (config.respectDoNotTrack !== false) script.dataset.doNotTrack = "true";
|
|
317
|
+
if (config.excludeSearch !== false) script.dataset.excludeSearch = "true";
|
|
318
|
+
if (config.excludeHash !== false) script.dataset.excludeHash = "true";
|
|
319
|
+
doc.head.appendChild(script);
|
|
320
|
+
}
|
|
321
|
+
}).catch((error) => {
|
|
322
|
+
scriptLoads.delete(key);
|
|
323
|
+
throw error;
|
|
324
|
+
});
|
|
325
|
+
scriptLoads.set(key, promise);
|
|
326
|
+
return promise;
|
|
327
|
+
}
|
|
328
|
+
function sendAnalyticsPageView(pathname, options, targetWindow = window) {
|
|
329
|
+
const path = sanitizeAnalyticsPath(pathname, options.normalizePath);
|
|
330
|
+
const umami = targetWindow.umami;
|
|
331
|
+
if (!path || !umami) return false;
|
|
332
|
+
umami.track({ url: path, title: targetWindow.document.title });
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
function sendAnalyticsEvent(eventName, data, allowedEvents, targetWindow = window) {
|
|
336
|
+
const umami = targetWindow.umami;
|
|
337
|
+
if (!umami || !isAllowedAnalyticsEvent(eventName, allowedEvents)) return false;
|
|
338
|
+
umami.track(eventName, sanitizeAnalyticsEventData(data));
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
|
|
241
342
|
// src/core/contentSlots.ts
|
|
242
343
|
var SLOT_ATTRIBUTE_KEYS = ["slot", "field", "key", "name"];
|
|
243
344
|
function getContentSlotMap(content) {
|
|
@@ -245,7 +346,7 @@ function getContentSlotMap(content) {
|
|
|
245
346
|
for (const block of content?.blocks || []) {
|
|
246
347
|
const slot = getBlockSlot(block);
|
|
247
348
|
if (!slot) continue;
|
|
248
|
-
fields[slot] = blockToSlotValue(block);
|
|
349
|
+
if (!(slot in fields)) fields[slot] = blockToSlotValue(block);
|
|
249
350
|
}
|
|
250
351
|
return fields;
|
|
251
352
|
}
|
|
@@ -254,11 +355,13 @@ function createContentSlotReader(content) {
|
|
|
254
355
|
const value = (key, fallback) => {
|
|
255
356
|
return key in fields ? fields[key] : fallback;
|
|
256
357
|
};
|
|
358
|
+
const unsafeHtml = (key, fallback = "") => stringifySlotValue(value(key, fallback));
|
|
257
359
|
return {
|
|
258
360
|
fields,
|
|
259
361
|
value,
|
|
260
362
|
text: (key, fallback = "") => stringifySlotValue(value(key, fallback)),
|
|
261
|
-
html:
|
|
363
|
+
html: unsafeHtml,
|
|
364
|
+
unsafeHtml,
|
|
262
365
|
list: (key, fallback = []) => slotValueToList(value(key, fallback)),
|
|
263
366
|
image: (key, fallback) => slotValueToImage(value(key, fallback))
|
|
264
367
|
};
|
|
@@ -533,24 +636,195 @@ function useAxCloudContext() {
|
|
|
533
636
|
}
|
|
534
637
|
return context;
|
|
535
638
|
}
|
|
536
|
-
var
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
639
|
+
var NAVIGATION_EVENT = "axcloud-sdk:navigation";
|
|
640
|
+
var historySubscriberCount = 0;
|
|
641
|
+
var originalPushState = null;
|
|
642
|
+
var originalReplaceState = null;
|
|
643
|
+
function subscribeToNavigation(callback) {
|
|
644
|
+
if (typeof window === "undefined") return () => void 0;
|
|
645
|
+
if (historySubscriberCount === 0) {
|
|
646
|
+
originalPushState = window.history.pushState.bind(window.history);
|
|
647
|
+
originalReplaceState = window.history.replaceState.bind(window.history);
|
|
648
|
+
window.history.pushState = ((...args) => {
|
|
649
|
+
originalPushState?.(...args);
|
|
650
|
+
window.dispatchEvent(new Event(NAVIGATION_EVENT));
|
|
651
|
+
});
|
|
652
|
+
window.history.replaceState = ((...args) => {
|
|
653
|
+
originalReplaceState?.(...args);
|
|
654
|
+
window.dispatchEvent(new Event(NAVIGATION_EVENT));
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
historySubscriberCount += 1;
|
|
658
|
+
window.addEventListener("popstate", callback);
|
|
659
|
+
window.addEventListener(NAVIGATION_EVENT, callback);
|
|
660
|
+
return () => {
|
|
661
|
+
window.removeEventListener("popstate", callback);
|
|
662
|
+
window.removeEventListener(NAVIGATION_EVENT, callback);
|
|
663
|
+
historySubscriberCount = Math.max(0, historySubscriberCount - 1);
|
|
664
|
+
if (historySubscriberCount === 0) {
|
|
665
|
+
if (originalPushState) window.history.pushState = originalPushState;
|
|
666
|
+
if (originalReplaceState) window.history.replaceState = originalReplaceState;
|
|
667
|
+
originalPushState = null;
|
|
668
|
+
originalReplaceState = null;
|
|
545
669
|
}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function doNotTrackEnabled(config) {
|
|
673
|
+
if (config.respectDoNotTrack === false || typeof navigator === "undefined") return false;
|
|
674
|
+
return navigator.doNotTrack === "1";
|
|
675
|
+
}
|
|
676
|
+
function useAnalyticsRuntime(config) {
|
|
677
|
+
const [enabled, setEnabled] = react.useState(false);
|
|
678
|
+
const [ready, setReady] = react.useState(false);
|
|
679
|
+
const publicConfigRef = react.useRef(null);
|
|
680
|
+
const optionsRef = react.useRef(config.analytics);
|
|
681
|
+
optionsRef.current = config.analytics;
|
|
682
|
+
const trackPageView = react.useCallback(
|
|
683
|
+
(pathname) => {
|
|
684
|
+
if (!ready || !optionsRef.current || typeof window === "undefined") return false;
|
|
685
|
+
return sendAnalyticsPageView(pathname || window.location.pathname, optionsRef.current);
|
|
686
|
+
},
|
|
687
|
+
[ready]
|
|
688
|
+
);
|
|
689
|
+
const track = react.useCallback(
|
|
690
|
+
(eventName, data) => {
|
|
691
|
+
if (!ready || typeof window === "undefined") return false;
|
|
692
|
+
return sendAnalyticsEvent(eventName, data, publicConfigRef.current?.allowedEvents);
|
|
693
|
+
},
|
|
694
|
+
[ready]
|
|
695
|
+
);
|
|
696
|
+
react.useEffect(() => {
|
|
697
|
+
const options = config.analytics;
|
|
698
|
+
setEnabled(false);
|
|
699
|
+
setReady(false);
|
|
700
|
+
publicConfigRef.current = null;
|
|
701
|
+
if (!options?.enabled || !options.siteKey || typeof window === "undefined") return void 0;
|
|
702
|
+
if (isLocalAnalyticsHostname(window.location.hostname) && !options.trackLocalhost)
|
|
703
|
+
return void 0;
|
|
704
|
+
const controller = new AbortController();
|
|
705
|
+
let cancelled = false;
|
|
706
|
+
let unsubscribe = () => void 0;
|
|
707
|
+
const initialize = async () => {
|
|
708
|
+
try {
|
|
709
|
+
const publicConfig = await fetchAnalyticsPublicConfig({
|
|
710
|
+
apiUrl: config.apiUrl,
|
|
711
|
+
orgId: config.orgId,
|
|
712
|
+
siteKey: options.siteKey,
|
|
713
|
+
stage: config.stage,
|
|
714
|
+
hostname: window.location.hostname,
|
|
715
|
+
signal: controller.signal
|
|
716
|
+
});
|
|
717
|
+
if (cancelled || !publicConfig.enabled || doNotTrackEnabled(publicConfig)) return;
|
|
718
|
+
publicConfigRef.current = publicConfig;
|
|
719
|
+
setEnabled(true);
|
|
720
|
+
await loadAnalyticsScript(publicConfig);
|
|
721
|
+
if (cancelled) return;
|
|
722
|
+
setReady(true);
|
|
723
|
+
if (!publicConfig.autoTrack) {
|
|
724
|
+
const trackCurrentPage = () => {
|
|
725
|
+
sendAnalyticsPageView(window.location.pathname, options);
|
|
726
|
+
};
|
|
727
|
+
trackCurrentPage();
|
|
728
|
+
unsubscribe = subscribeToNavigation(trackCurrentPage);
|
|
729
|
+
}
|
|
730
|
+
} catch (error) {
|
|
731
|
+
if (!cancelled && !(error instanceof Error && error.name === "AbortError")) {
|
|
732
|
+
setEnabled(false);
|
|
733
|
+
setReady(false);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
void initialize();
|
|
738
|
+
return () => {
|
|
739
|
+
cancelled = true;
|
|
740
|
+
controller.abort();
|
|
741
|
+
unsubscribe();
|
|
742
|
+
};
|
|
743
|
+
}, [
|
|
744
|
+
config.analytics?.enabled,
|
|
745
|
+
config.analytics?.siteKey,
|
|
746
|
+
config.analytics?.trackLocalhost,
|
|
747
|
+
config.analytics?.normalizePath,
|
|
748
|
+
config.apiUrl,
|
|
749
|
+
config.orgId,
|
|
750
|
+
config.stage
|
|
751
|
+
]);
|
|
752
|
+
return react.useMemo(
|
|
753
|
+
() => ({ enabled, ready, track, trackPageView }),
|
|
754
|
+
[enabled, ready, track, trackPageView]
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
function useAnalytics() {
|
|
758
|
+
return useAxCloudContext().analytics;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/react/auth/auth0Invitation.ts
|
|
762
|
+
var AUTH0_INVITATION_PATTERN = /^[A-Za-z0-9_-]{1,2048}$/;
|
|
763
|
+
function readAuth0InvitationAuthorizationParams(configuredOrganization, search = typeof window === "undefined" ? "" : window.location.search) {
|
|
764
|
+
if (!configuredOrganization || !search) return {};
|
|
765
|
+
const params = new URLSearchParams(search);
|
|
766
|
+
const invitation = params.get("invitation")?.trim();
|
|
767
|
+
const organization = params.get("organization")?.trim();
|
|
768
|
+
if (organization !== configuredOrganization || !invitation || !AUTH0_INVITATION_PATTERN.test(invitation)) {
|
|
769
|
+
return {};
|
|
546
770
|
}
|
|
547
|
-
}
|
|
771
|
+
return { invitation };
|
|
772
|
+
}
|
|
773
|
+
function createDefaultQueryClient() {
|
|
774
|
+
return new reactQuery.QueryClient({
|
|
775
|
+
defaultOptions: {
|
|
776
|
+
queries: {
|
|
777
|
+
staleTime: 1e3 * 60,
|
|
778
|
+
gcTime: 1e3 * 60 * 30,
|
|
779
|
+
retry: (failureCount, error) => {
|
|
780
|
+
if (isAxCloudError(error) && error.status >= 400 && error.status < 500) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
return failureCount < 2;
|
|
784
|
+
},
|
|
785
|
+
refetchOnWindowFocus: true
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
var queryClientAxCloudOwners = /* @__PURE__ */ new WeakMap();
|
|
791
|
+
function createQueryClientOwnerSignature(config, subject) {
|
|
792
|
+
return JSON.stringify([
|
|
793
|
+
subject === null ? "public" : "authenticated",
|
|
794
|
+
subject,
|
|
795
|
+
config.orgId,
|
|
796
|
+
config.stage,
|
|
797
|
+
config.apiUrl,
|
|
798
|
+
config.apiKey ?? null,
|
|
799
|
+
config.auth0.domain,
|
|
800
|
+
config.auth0.clientId,
|
|
801
|
+
config.auth0.audience ?? null,
|
|
802
|
+
config.auth0.organization ?? null,
|
|
803
|
+
config.auth0.connection ?? null
|
|
804
|
+
]);
|
|
805
|
+
}
|
|
548
806
|
function AxCloudInnerProvider({
|
|
549
807
|
children,
|
|
550
808
|
config,
|
|
809
|
+
queryClient,
|
|
551
810
|
timeout
|
|
552
811
|
}) {
|
|
553
|
-
const { getAccessTokenSilently, isAuthenticated } = auth0React.useAuth0();
|
|
812
|
+
const { getAccessTokenSilently, isAuthenticated, isLoading, user } = auth0React.useAuth0();
|
|
813
|
+
const analytics = useAnalyticsRuntime(config);
|
|
814
|
+
const [, setOwnerGeneration] = react.useState(0);
|
|
815
|
+
const currentSubject = isLoading ? void 0 : isAuthenticated ? user?.sub : null;
|
|
816
|
+
const currentOwnerSignature = currentSubject === void 0 ? void 0 : createQueryClientOwnerSignature(config, currentSubject);
|
|
817
|
+
const hasKnownOwner = queryClientAxCloudOwners.has(queryClient);
|
|
818
|
+
const ownerNeedsActivation = currentOwnerSignature !== void 0 && (!hasKnownOwner || queryClientAxCloudOwners.get(queryClient) !== currentOwnerSignature);
|
|
819
|
+
const clearContentCache = react.useCallback(() => {
|
|
820
|
+
queryClient.removeQueries({ queryKey: ["axcloud"] });
|
|
821
|
+
}, [queryClient]);
|
|
822
|
+
react.useLayoutEffect(() => {
|
|
823
|
+
if (currentOwnerSignature === void 0 || !ownerNeedsActivation) return;
|
|
824
|
+
clearContentCache();
|
|
825
|
+
queryClientAxCloudOwners.set(queryClient, currentOwnerSignature);
|
|
826
|
+
setOwnerGeneration((generation) => generation + 1);
|
|
827
|
+
}, [clearContentCache, currentOwnerSignature, ownerNeedsActivation, queryClient]);
|
|
554
828
|
const tokenGetterRef = react.useRef(async () => void 0);
|
|
555
829
|
tokenGetterRef.current = isAuthenticated ? async () => {
|
|
556
830
|
try {
|
|
@@ -580,11 +854,17 @@ function AxCloudInnerProvider({
|
|
|
580
854
|
client,
|
|
581
855
|
orgId: config.orgId,
|
|
582
856
|
stage: config.stage,
|
|
583
|
-
isPublicMode:
|
|
857
|
+
isPublicMode: !isAuthenticated,
|
|
858
|
+
isAuthLoading: isLoading,
|
|
859
|
+
isAuthenticated,
|
|
860
|
+
authSubject: isAuthenticated ? user?.sub : void 0,
|
|
861
|
+
clearContentCache,
|
|
862
|
+
analytics
|
|
584
863
|
}),
|
|
585
|
-
[config, client, isAuthenticated]
|
|
864
|
+
[config, client, isAuthenticated, isLoading, user?.sub, clearContentCache, analytics]
|
|
586
865
|
);
|
|
587
|
-
|
|
866
|
+
const identityReady = currentOwnerSignature !== void 0 && !ownerNeedsActivation;
|
|
867
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AxCloudContext.Provider, { value: contextValue, children: identityReady ? children : null });
|
|
588
868
|
}
|
|
589
869
|
function AxCloudProvider({
|
|
590
870
|
children,
|
|
@@ -592,19 +872,24 @@ function AxCloudProvider({
|
|
|
592
872
|
apiUrl,
|
|
593
873
|
stage = "dev",
|
|
594
874
|
apiKey,
|
|
875
|
+
analytics,
|
|
595
876
|
auth0,
|
|
596
|
-
queryClient
|
|
877
|
+
queryClient: providedQueryClient,
|
|
597
878
|
timeout
|
|
598
879
|
}) {
|
|
880
|
+
const [ownedQueryClient] = react.useState(createDefaultQueryClient);
|
|
881
|
+
const queryClient = providedQueryClient || ownedQueryClient;
|
|
882
|
+
const invitationAuthorizationParams = readAuth0InvitationAuthorizationParams(auth0.organization);
|
|
599
883
|
const config = react.useMemo(
|
|
600
884
|
() => ({
|
|
601
885
|
orgId,
|
|
602
886
|
apiUrl,
|
|
603
887
|
stage,
|
|
604
888
|
apiKey,
|
|
889
|
+
analytics,
|
|
605
890
|
auth0
|
|
606
891
|
}),
|
|
607
|
-
[orgId, apiUrl, stage, apiKey, auth0]
|
|
892
|
+
[orgId, apiUrl, stage, apiKey, analytics, auth0]
|
|
608
893
|
);
|
|
609
894
|
return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
610
895
|
auth0React.Auth0Provider,
|
|
@@ -613,14 +898,16 @@ function AxCloudProvider({
|
|
|
613
898
|
clientId: auth0.clientId,
|
|
614
899
|
authorizationParams: {
|
|
615
900
|
redirect_uri: auth0.redirectUri,
|
|
616
|
-
audience: auth0.audience
|
|
901
|
+
audience: auth0.audience,
|
|
902
|
+
...auth0.organization ? { organization: auth0.organization } : {},
|
|
903
|
+
...invitationAuthorizationParams
|
|
617
904
|
},
|
|
618
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AxCloudInnerProvider, { config, timeout, children })
|
|
905
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AxCloudInnerProvider, { config, queryClient, timeout, children })
|
|
619
906
|
}
|
|
620
907
|
) });
|
|
621
908
|
}
|
|
622
909
|
function useAuth() {
|
|
623
|
-
const { config } = useAxCloudContext();
|
|
910
|
+
const { config, client, clearContentCache } = useAxCloudContext();
|
|
624
911
|
const {
|
|
625
912
|
isAuthenticated,
|
|
626
913
|
isLoading,
|
|
@@ -633,23 +920,33 @@ function useAuth() {
|
|
|
633
920
|
} = auth0React.useAuth0();
|
|
634
921
|
const login = react.useCallback(
|
|
635
922
|
(options) => {
|
|
923
|
+
const invitationAuthorizationParams = readAuth0InvitationAuthorizationParams(
|
|
924
|
+
config.auth0.organization
|
|
925
|
+
);
|
|
636
926
|
return loginWithRedirect({
|
|
637
927
|
appState: {
|
|
638
928
|
returnTo: options?.returnTo || window.location.pathname
|
|
639
|
-
}
|
|
929
|
+
},
|
|
930
|
+
...config.auth0.organization ? {
|
|
931
|
+
authorizationParams: {
|
|
932
|
+
organization: config.auth0.organization,
|
|
933
|
+
...invitationAuthorizationParams
|
|
934
|
+
}
|
|
935
|
+
} : {}
|
|
640
936
|
});
|
|
641
937
|
},
|
|
642
|
-
[loginWithRedirect]
|
|
938
|
+
[config.auth0.organization, loginWithRedirect]
|
|
643
939
|
);
|
|
644
940
|
const logout = react.useCallback(
|
|
645
941
|
(options) => {
|
|
942
|
+
clearContentCache?.();
|
|
646
943
|
return auth0Logout({
|
|
647
944
|
logoutParams: {
|
|
648
945
|
returnTo: options?.returnTo || window.location.origin
|
|
649
946
|
}
|
|
650
947
|
});
|
|
651
948
|
},
|
|
652
|
-
[auth0Logout]
|
|
949
|
+
[auth0Logout, clearContentCache]
|
|
653
950
|
);
|
|
654
951
|
const getAccessToken = react.useCallback(async () => {
|
|
655
952
|
try {
|
|
@@ -658,6 +955,60 @@ function useAuth() {
|
|
|
658
955
|
return void 0;
|
|
659
956
|
}
|
|
660
957
|
}, [getAccessTokenSilently]);
|
|
958
|
+
const refreshUser = react.useCallback(async () => {
|
|
959
|
+
await getAccessTokenSilently({ cacheMode: "off" });
|
|
960
|
+
const claims = await getIdTokenClaims();
|
|
961
|
+
return claims?.email_verified === true;
|
|
962
|
+
}, [getAccessTokenSilently, getIdTokenClaims]);
|
|
963
|
+
const requestEmailVerification = react.useCallback(
|
|
964
|
+
async (options) => {
|
|
965
|
+
if (!isAuthenticated || !auth0User) {
|
|
966
|
+
return {
|
|
967
|
+
success: false,
|
|
968
|
+
status: "failed",
|
|
969
|
+
message: "You must be signed in to verify your email."
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
if (auth0User.email_verified === true) {
|
|
973
|
+
return {
|
|
974
|
+
success: true,
|
|
975
|
+
status: "already_verified",
|
|
976
|
+
message: "Your email address is already verified."
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
const response = await client.post("/email/identity/verification-email", {
|
|
981
|
+
return_to: options?.returnTo || window.location.href
|
|
982
|
+
});
|
|
983
|
+
const data = response.data || {};
|
|
984
|
+
return {
|
|
985
|
+
success: true,
|
|
986
|
+
status: data.status || "sent",
|
|
987
|
+
message: data.message || "A verification email has been sent.",
|
|
988
|
+
...data.retry_after_seconds !== void 0 ? { retryAfterSeconds: data.retry_after_seconds } : {}
|
|
989
|
+
};
|
|
990
|
+
} catch (requestError) {
|
|
991
|
+
if (isAxCloudError(requestError) && requestError.status === 429) {
|
|
992
|
+
const responseDetails = requestError.details?.details;
|
|
993
|
+
const retryAfterSeconds = Number(
|
|
994
|
+
responseDetails && typeof responseDetails === "object" ? responseDetails.retry_after_seconds : void 0
|
|
995
|
+
);
|
|
996
|
+
return {
|
|
997
|
+
success: false,
|
|
998
|
+
status: "rate_limited",
|
|
999
|
+
message: "Please wait before requesting another verification email.",
|
|
1000
|
+
...Number.isFinite(retryAfterSeconds) ? { retryAfterSeconds } : {}
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
success: false,
|
|
1005
|
+
status: "failed",
|
|
1006
|
+
message: "Failed to send the verification email. Please try again."
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
[auth0User, client, isAuthenticated]
|
|
1011
|
+
);
|
|
661
1012
|
const getAuth0Client = react.useCallback(() => {
|
|
662
1013
|
return {
|
|
663
1014
|
loginWithRedirect,
|
|
@@ -731,35 +1082,182 @@ function useAuth() {
|
|
|
731
1082
|
id: auth0User.sub || "",
|
|
732
1083
|
email: auth0User.email || "",
|
|
733
1084
|
name: auth0User.name || "",
|
|
734
|
-
avatar: auth0User.picture
|
|
1085
|
+
avatar: auth0User.picture,
|
|
1086
|
+
...typeof auth0User.email_verified === "boolean" ? { emailVerified: auth0User.email_verified } : {}
|
|
735
1087
|
} : null,
|
|
1088
|
+
needsEmailVerification: isAuthenticated && auth0User?.email_verified === false,
|
|
736
1089
|
login,
|
|
737
1090
|
logout,
|
|
738
1091
|
// Escape hatch for advanced use cases
|
|
739
1092
|
getAccessToken,
|
|
740
1093
|
getAuth0Client,
|
|
1094
|
+
refreshUser,
|
|
1095
|
+
requestEmailVerification,
|
|
741
1096
|
// Password management
|
|
742
1097
|
requestPasswordReset
|
|
743
1098
|
};
|
|
744
1099
|
}
|
|
1100
|
+
var currentUserQueryKey = (stage, orgId, subject) => ["axcloud", "user", "me", stage, orgId, subject];
|
|
1101
|
+
function asRecord(value) {
|
|
1102
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1103
|
+
}
|
|
1104
|
+
function asString(value) {
|
|
1105
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
1106
|
+
}
|
|
1107
|
+
function asStringArray(value) {
|
|
1108
|
+
if (!Array.isArray(value)) return [];
|
|
1109
|
+
return Array.from(new Set(value.map(asString).filter((item) => item !== null)));
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeMembershipSections(rawMembership) {
|
|
1112
|
+
const sections = /* @__PURE__ */ new Map();
|
|
1113
|
+
const addSection = (idValue, nameValue, roleValue) => {
|
|
1114
|
+
const id = asString(idValue);
|
|
1115
|
+
if (!id) return;
|
|
1116
|
+
const existing = sections.get(id);
|
|
1117
|
+
sections.set(id, {
|
|
1118
|
+
id,
|
|
1119
|
+
name: asString(nameValue) || existing?.name || id,
|
|
1120
|
+
role: asString(roleValue) || existing?.role || "member"
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1123
|
+
const directSections = rawMembership.sections;
|
|
1124
|
+
if (Array.isArray(directSections)) {
|
|
1125
|
+
for (const rawSection of directSections) {
|
|
1126
|
+
if (typeof rawSection === "string") {
|
|
1127
|
+
addSection(rawSection);
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
const section = asRecord(rawSection);
|
|
1131
|
+
addSection(
|
|
1132
|
+
section.id ?? section.sectionId ?? section.section_id,
|
|
1133
|
+
section.name ?? section.sectionName ?? section.section_name,
|
|
1134
|
+
section.role
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const sectionMemberships = asRecord(
|
|
1139
|
+
rawMembership.sectionMemberships ?? rawMembership.section_memberships
|
|
1140
|
+
);
|
|
1141
|
+
for (const [sectionId, rawSectionMembership] of Object.entries(sectionMemberships)) {
|
|
1142
|
+
const sectionMembership = asRecord(rawSectionMembership);
|
|
1143
|
+
addSection(
|
|
1144
|
+
sectionId,
|
|
1145
|
+
sectionMembership.name ?? sectionMembership.sectionName ?? sectionMembership.section_name,
|
|
1146
|
+
sectionMembership.role
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
for (const sectionId of asStringArray(rawMembership.sectionIds ?? rawMembership.section_ids)) {
|
|
1150
|
+
addSection(sectionId);
|
|
1151
|
+
}
|
|
1152
|
+
const primarySectionId = asString(rawMembership.sectionId ?? rawMembership.section_id);
|
|
1153
|
+
if (primarySectionId) addSection(primarySectionId);
|
|
1154
|
+
const normalized = Array.from(sections.values());
|
|
1155
|
+
if (!primarySectionId) return normalized;
|
|
1156
|
+
return normalized.sort((left, right) => {
|
|
1157
|
+
if (left.id === primarySectionId) return -1;
|
|
1158
|
+
if (right.id === primarySectionId) return 1;
|
|
1159
|
+
return 0;
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
function normalizeMembership(rawValue) {
|
|
1163
|
+
const raw = asRecord(rawValue);
|
|
1164
|
+
const role = asString(raw.role) || "member";
|
|
1165
|
+
const roles = asStringArray(raw.roles);
|
|
1166
|
+
const sections = normalizeMembershipSections(raw);
|
|
1167
|
+
const sectionId = asString(raw.sectionId ?? raw.section_id) || sections[0]?.id || null;
|
|
1168
|
+
const joinedAt = asString(raw.joinedAt ?? raw.joined_at);
|
|
1169
|
+
return {
|
|
1170
|
+
role,
|
|
1171
|
+
roles: roles.length > 0 ? roles : [role],
|
|
1172
|
+
title: asString(raw.title),
|
|
1173
|
+
status: asString(raw.status),
|
|
1174
|
+
joinedAt,
|
|
1175
|
+
memberSince: asString(raw.memberSince ?? raw.member_since) || joinedAt,
|
|
1176
|
+
sectionId,
|
|
1177
|
+
sectionIds: sections.map((section) => section.id),
|
|
1178
|
+
sections
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
function normalizeCurrentUserResponse(raw, configuredOrgId) {
|
|
1182
|
+
const rawMemberships = asRecord(
|
|
1183
|
+
raw.organizationMemberships ?? raw.organization_memberships
|
|
1184
|
+
);
|
|
1185
|
+
const organizationMemberships = Object.fromEntries(
|
|
1186
|
+
Object.entries(rawMemberships).map(([organizationId2, membership]) => [
|
|
1187
|
+
organizationId2,
|
|
1188
|
+
normalizeMembership(membership)
|
|
1189
|
+
])
|
|
1190
|
+
);
|
|
1191
|
+
const allOrgIds = Array.from(
|
|
1192
|
+
/* @__PURE__ */ new Set([
|
|
1193
|
+
...asStringArray(raw.allOrgIds ?? raw.all_org_ids),
|
|
1194
|
+
...Object.keys(organizationMemberships)
|
|
1195
|
+
])
|
|
1196
|
+
);
|
|
1197
|
+
const effectiveOrganizationId = asString(
|
|
1198
|
+
raw.effectiveOrganizationId ?? raw.effective_org_id
|
|
1199
|
+
);
|
|
1200
|
+
const primaryOrganizationId = asString(
|
|
1201
|
+
raw.organizationId ?? raw.primary_org_id ?? raw.org_id
|
|
1202
|
+
);
|
|
1203
|
+
const organizationId = effectiveOrganizationId || primaryOrganizationId || (organizationMemberships[configuredOrgId] ? configuredOrgId : null) || allOrgIds[0] || configuredOrgId;
|
|
1204
|
+
const configuredMembership = organizationMemberships[configuredOrgId];
|
|
1205
|
+
const firstName = asString(raw.firstName ?? raw.first_name ?? raw.given_name) || "";
|
|
1206
|
+
const lastName = asString(raw.lastName ?? raw.last_name ?? raw.family_name) || "";
|
|
1207
|
+
const email = asString(raw.email) || "";
|
|
1208
|
+
const createdAt = asString(raw.createdAt ?? raw.created_at) || "";
|
|
1209
|
+
const updatedAt = asString(raw.updatedAt ?? raw.updated_at) || createdAt;
|
|
1210
|
+
const role = configuredMembership?.role || asString(raw.role) || "member";
|
|
1211
|
+
const sectionId = configuredMembership?.sectionId || asString(raw.sectionId ?? raw.section_id);
|
|
1212
|
+
const permissions = asStringArray(raw.permissions ?? raw.effective_permissions);
|
|
1213
|
+
const metadata = asRecord(raw.metadata);
|
|
1214
|
+
return {
|
|
1215
|
+
id: asString(raw.id ?? raw.userId ?? raw.user_id ?? raw.sub) || "",
|
|
1216
|
+
email,
|
|
1217
|
+
name: asString(raw.name) || [firstName, lastName].filter(Boolean).join(" ") || email,
|
|
1218
|
+
firstName,
|
|
1219
|
+
lastName,
|
|
1220
|
+
title: configuredMembership?.title ?? asString(raw.title),
|
|
1221
|
+
phone: asString(raw.phone ?? raw.phoneNumber ?? raw.phone_number),
|
|
1222
|
+
address: asString(raw.address ?? raw.location),
|
|
1223
|
+
avatar: asString(raw.avatar ?? raw.avatarUrl ?? raw.avatar_url ?? raw.picture) || void 0,
|
|
1224
|
+
organizationId,
|
|
1225
|
+
...sectionId ? { sectionId } : {},
|
|
1226
|
+
role,
|
|
1227
|
+
permissions,
|
|
1228
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {},
|
|
1229
|
+
createdAt,
|
|
1230
|
+
updatedAt,
|
|
1231
|
+
allOrgIds,
|
|
1232
|
+
organizationMemberships,
|
|
1233
|
+
effectiveOrganizationId,
|
|
1234
|
+
isSuperAdmin: raw.isSuperAdmin === true || raw.is_super_admin === true || asString(raw.global_role) === "super_admin" || role === "super_admin"
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
745
1237
|
function useUser() {
|
|
746
|
-
const { isAuthenticated, isLoading: isAuthLoading } = auth0React.useAuth0();
|
|
1238
|
+
const { isAuthenticated, isLoading: isAuthLoading, user: auth0User } = auth0React.useAuth0();
|
|
747
1239
|
const { client, stage, orgId } = useAxCloudContext();
|
|
1240
|
+
const queryKey = currentUserQueryKey(stage, orgId, auth0User?.sub);
|
|
748
1241
|
const query = reactQuery.useQuery({
|
|
749
|
-
queryKey
|
|
1242
|
+
queryKey,
|
|
750
1243
|
queryFn: async () => {
|
|
751
|
-
|
|
1244
|
+
const response = await client.get("/api/users/me");
|
|
1245
|
+
return normalizeCurrentUserResponse(response, orgId);
|
|
752
1246
|
},
|
|
753
1247
|
enabled: isAuthenticated && !isAuthLoading,
|
|
754
1248
|
staleTime: 1e3 * 60 * 5
|
|
755
1249
|
// 5 minutes
|
|
756
1250
|
});
|
|
757
1251
|
const userData = query.data;
|
|
758
|
-
const userOrgIds = userData?.
|
|
1252
|
+
const userOrgIds = userData?.allOrgIds ?? [];
|
|
759
1253
|
const isSuperAdmin = userData?.isSuperAdmin === true;
|
|
760
|
-
const
|
|
761
|
-
const
|
|
1254
|
+
const orgMembership = userData?.organizationMemberships?.[orgId];
|
|
1255
|
+
const hasOrgAccess = userOrgIds.includes(orgId) && orgMembership?.status?.trim().toLowerCase() === "active";
|
|
762
1256
|
const roleInOrg = orgMembership?.role ?? null;
|
|
1257
|
+
const sectionId = orgMembership?.sectionId ?? userData?.sectionId ?? null;
|
|
1258
|
+
const sections = orgMembership?.sections?.length ? orgMembership.sections : sectionId ? [{ id: sectionId, name: sectionId, role: roleInOrg ?? "member" }] : [];
|
|
1259
|
+
const section = sections.find((item) => item.id === sectionId) ?? sections[0] ?? null;
|
|
1260
|
+
const memberSince = orgMembership?.memberSince ?? null;
|
|
763
1261
|
return {
|
|
764
1262
|
user: query.data ?? null,
|
|
765
1263
|
isLoading: isAuthLoading || query.isLoading,
|
|
@@ -769,15 +1267,35 @@ function useUser() {
|
|
|
769
1267
|
// Organization access
|
|
770
1268
|
hasOrgAccess,
|
|
771
1269
|
roleInOrg,
|
|
1270
|
+
organizationMembership: orgMembership ?? null,
|
|
1271
|
+
title: orgMembership?.title ?? query.data?.title ?? null,
|
|
1272
|
+
section,
|
|
1273
|
+
sections,
|
|
1274
|
+
sectionIds: orgMembership?.sectionIds?.length ? orgMembership.sectionIds : sectionId ? [sectionId] : [],
|
|
1275
|
+
joinedAt: orgMembership?.joinedAt ?? null,
|
|
1276
|
+
memberSince,
|
|
772
1277
|
// Convenience properties
|
|
773
1278
|
isAuthenticated,
|
|
774
|
-
role: query.data?.role ?? null,
|
|
1279
|
+
role: roleInOrg ?? query.data?.role ?? null,
|
|
775
1280
|
organizationId: query.data?.organizationId ?? null,
|
|
776
|
-
sectionId
|
|
1281
|
+
sectionId,
|
|
777
1282
|
permissions: query.data?.permissions ?? [],
|
|
778
1283
|
isSuperAdmin
|
|
779
1284
|
};
|
|
780
1285
|
}
|
|
1286
|
+
function useUpdateMyProfile() {
|
|
1287
|
+
const { client, stage } = useAxCloudContext();
|
|
1288
|
+
const queryClient = reactQuery.useQueryClient();
|
|
1289
|
+
return reactQuery.useMutation({
|
|
1290
|
+
mutationFn: (updates) => client.patch("/api/users/me/profile", updates),
|
|
1291
|
+
onSuccess: async () => {
|
|
1292
|
+
await queryClient.invalidateQueries({
|
|
1293
|
+
queryKey: ["axcloud", "user", "me", stage],
|
|
1294
|
+
exact: false
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
781
1299
|
function useOrg() {
|
|
782
1300
|
const { client, orgId, stage, isPublicMode } = useAxCloudContext();
|
|
783
1301
|
const usePublicEndpoint = isPublicMode;
|
|
@@ -824,152 +1342,169 @@ function useOrg() {
|
|
|
824
1342
|
modules
|
|
825
1343
|
};
|
|
826
1344
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
"
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
refetch: query.refetch
|
|
883
|
-
};
|
|
1345
|
+
|
|
1346
|
+
// src/react/contentQueryPolicy.ts
|
|
1347
|
+
var CONTENT_STALE_TIME_MS = 6e4;
|
|
1348
|
+
function shouldRetryContentRequest(failureCount, error) {
|
|
1349
|
+
if (isAxCloudError(error) && error.status >= 400 && error.status < 500) {
|
|
1350
|
+
return false;
|
|
1351
|
+
}
|
|
1352
|
+
return failureCount < 2;
|
|
1353
|
+
}
|
|
1354
|
+
function normalizeContentCollectionType(contentType) {
|
|
1355
|
+
switch (contentType.toLowerCase()) {
|
|
1356
|
+
case "news":
|
|
1357
|
+
return "news";
|
|
1358
|
+
case "page":
|
|
1359
|
+
case "pages":
|
|
1360
|
+
return "pages";
|
|
1361
|
+
case "event":
|
|
1362
|
+
case "events":
|
|
1363
|
+
return "events";
|
|
1364
|
+
default:
|
|
1365
|
+
throw new Error(`Unsupported AxCloud content collection type: ${contentType}`);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function normalizeContentDetailType(contentType) {
|
|
1369
|
+
switch (contentType.toLowerCase()) {
|
|
1370
|
+
case "news":
|
|
1371
|
+
return "news";
|
|
1372
|
+
case "page":
|
|
1373
|
+
case "pages":
|
|
1374
|
+
return "page";
|
|
1375
|
+
case "event":
|
|
1376
|
+
case "events":
|
|
1377
|
+
return "event";
|
|
1378
|
+
default:
|
|
1379
|
+
throw new Error(`Unsupported AxCloud content detail type: ${contentType}`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
function getContentAccessState(context, publicOnly) {
|
|
1383
|
+
if (publicOnly) {
|
|
1384
|
+
return { mode: "public", enabled: true, subject: "anonymous" };
|
|
1385
|
+
}
|
|
1386
|
+
const isLoading = context.isAuthLoading ?? false;
|
|
1387
|
+
if (isLoading) {
|
|
1388
|
+
return { mode: "pending", enabled: false, subject: "auth-loading" };
|
|
1389
|
+
}
|
|
1390
|
+
const hasExplicitAuthState = typeof context.isAuthenticated === "boolean";
|
|
1391
|
+
const isAuthenticated = context.isAuthenticated ?? !context.isPublicMode;
|
|
1392
|
+
if (!isAuthenticated) {
|
|
1393
|
+
return { mode: "public", enabled: true, subject: "anonymous" };
|
|
1394
|
+
}
|
|
1395
|
+
if (hasExplicitAuthState && !context.authSubject) {
|
|
1396
|
+
return { mode: "pending", enabled: false, subject: "auth-subject-pending" };
|
|
1397
|
+
}
|
|
1398
|
+
const subject = context.authSubject || "legacy-authenticated-session";
|
|
1399
|
+
return { mode: "member", enabled: Boolean(subject), subject };
|
|
884
1400
|
}
|
|
885
|
-
|
|
886
|
-
|
|
1401
|
+
|
|
1402
|
+
// src/react/hooks/useContent.ts
|
|
887
1403
|
function useContent(contentType, options = {}) {
|
|
888
|
-
const
|
|
1404
|
+
const context = useAxCloudContext();
|
|
1405
|
+
const { client, config, orgId, stage } = context;
|
|
1406
|
+
const collectionType = normalizeContentCollectionType(contentType);
|
|
889
1407
|
const {
|
|
890
1408
|
publicOnly = false,
|
|
891
1409
|
page = 1,
|
|
892
1410
|
pageSize = 20,
|
|
893
|
-
status = "published",
|
|
894
|
-
visibility,
|
|
895
|
-
sectionId,
|
|
896
1411
|
search,
|
|
897
|
-
sortBy = "createdAt",
|
|
1412
|
+
sortBy = collectionType === "news" ? "publishedAt" : "createdAt",
|
|
898
1413
|
sortOrder = "desc",
|
|
1414
|
+
tags,
|
|
899
1415
|
lang
|
|
900
1416
|
} = options;
|
|
1417
|
+
const access = getContentAccessState(context, publicOnly);
|
|
1418
|
+
const deliveryScope = access.mode === "member" ? "members" : "public";
|
|
1419
|
+
const params = {
|
|
1420
|
+
page,
|
|
1421
|
+
pageSize,
|
|
1422
|
+
search,
|
|
1423
|
+
sort: sortBy,
|
|
1424
|
+
order: sortOrder,
|
|
1425
|
+
tags: tags?.length ? tags.join(",") : void 0,
|
|
1426
|
+
lang
|
|
1427
|
+
};
|
|
901
1428
|
const query = reactQuery.useQuery({
|
|
902
1429
|
queryKey: [
|
|
903
1430
|
"axcloud",
|
|
904
1431
|
"content",
|
|
1432
|
+
"collection",
|
|
1433
|
+
stage,
|
|
1434
|
+
config.apiUrl,
|
|
905
1435
|
orgId,
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
pageSize,
|
|
911
|
-
status,
|
|
912
|
-
visibility,
|
|
913
|
-
sectionId,
|
|
914
|
-
search,
|
|
915
|
-
sortBy,
|
|
916
|
-
sortOrder,
|
|
917
|
-
lang
|
|
1436
|
+
access.mode,
|
|
1437
|
+
access.subject,
|
|
1438
|
+
collectionType,
|
|
1439
|
+
params
|
|
918
1440
|
],
|
|
919
|
-
queryFn:
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
sectionId,
|
|
928
|
-
search,
|
|
929
|
-
sortBy,
|
|
930
|
-
sortOrder,
|
|
931
|
-
lang
|
|
932
|
-
});
|
|
933
|
-
},
|
|
934
|
-
staleTime: 1e3 * 60 * 5
|
|
935
|
-
// 5 minutes
|
|
1441
|
+
queryFn: () => client.get(
|
|
1442
|
+
`/content/${deliveryScope}/${orgId}/${collectionType}`,
|
|
1443
|
+
params
|
|
1444
|
+
),
|
|
1445
|
+
enabled: access.enabled,
|
|
1446
|
+
staleTime: CONTENT_STALE_TIME_MS,
|
|
1447
|
+
refetchOnWindowFocus: true,
|
|
1448
|
+
retry: shouldRetryContentRequest
|
|
936
1449
|
});
|
|
1450
|
+
const items = query.data?.items ?? [];
|
|
937
1451
|
return {
|
|
938
|
-
data:
|
|
939
|
-
|
|
1452
|
+
data: items,
|
|
1453
|
+
count: query.data?.count ?? items.length,
|
|
1454
|
+
total: query.data?.total ?? items.length,
|
|
1455
|
+
page: query.data?.page ?? page,
|
|
1456
|
+
pageSize: query.data?.pageSize ?? pageSize,
|
|
940
1457
|
hasMore: query.data?.hasMore ?? false,
|
|
941
|
-
|
|
1458
|
+
nextCursor: query.data?.nextCursor,
|
|
1459
|
+
isLoading: access.mode === "pending" || query.isLoading,
|
|
1460
|
+
isFetching: query.isFetching,
|
|
942
1461
|
isError: query.isError,
|
|
943
1462
|
error: query.error,
|
|
944
1463
|
refetch: query.refetch
|
|
945
1464
|
};
|
|
946
1465
|
}
|
|
947
1466
|
function useContentItem(contentType, idOrSlug, options = {}) {
|
|
948
|
-
const
|
|
1467
|
+
const context = useAxCloudContext();
|
|
1468
|
+
const { client, config, orgId, stage } = context;
|
|
1469
|
+
const detailType = normalizeContentDetailType(contentType);
|
|
949
1470
|
const { publicOnly = false, lang } = options;
|
|
950
|
-
const
|
|
1471
|
+
const access = getContentAccessState(context, publicOnly);
|
|
1472
|
+
const deliveryScope = access.mode === "member" ? "members" : "public";
|
|
951
1473
|
const query = reactQuery.useQuery({
|
|
952
|
-
queryKey: [
|
|
953
|
-
|
|
954
|
-
|
|
1474
|
+
queryKey: [
|
|
1475
|
+
"axcloud",
|
|
1476
|
+
"content",
|
|
1477
|
+
"item",
|
|
1478
|
+
stage,
|
|
1479
|
+
config.apiUrl,
|
|
1480
|
+
orgId,
|
|
1481
|
+
access.mode,
|
|
1482
|
+
access.subject,
|
|
1483
|
+
detailType,
|
|
1484
|
+
idOrSlug,
|
|
1485
|
+
lang
|
|
1486
|
+
],
|
|
1487
|
+
queryFn: () => {
|
|
955
1488
|
const encodedIdOrSlug = encodeURIComponent(idOrSlug);
|
|
956
|
-
const endpoint =
|
|
1489
|
+
const endpoint = `/content/${deliveryScope}/${orgId}/items/${detailType}/${encodedIdOrSlug}`;
|
|
957
1490
|
return lang ? client.get(endpoint, { lang }) : client.get(endpoint);
|
|
958
1491
|
},
|
|
959
|
-
enabled:
|
|
960
|
-
staleTime:
|
|
961
|
-
|
|
1492
|
+
enabled: access.enabled && Boolean(idOrSlug),
|
|
1493
|
+
staleTime: CONTENT_STALE_TIME_MS,
|
|
1494
|
+
refetchOnWindowFocus: true,
|
|
1495
|
+
retry: shouldRetryContentRequest
|
|
962
1496
|
});
|
|
963
1497
|
return {
|
|
964
1498
|
data: query.data ?? null,
|
|
965
|
-
isLoading: query.isLoading,
|
|
1499
|
+
isLoading: access.mode === "pending" || query.isLoading,
|
|
1500
|
+
isFetching: query.isFetching,
|
|
966
1501
|
isError: query.isError,
|
|
967
1502
|
error: query.error,
|
|
968
1503
|
refetch: query.refetch
|
|
969
1504
|
};
|
|
970
1505
|
}
|
|
971
1506
|
function usePageContent(slug, options = {}) {
|
|
972
|
-
const state = useContentItem("
|
|
1507
|
+
const state = useContentItem("page", slug, options);
|
|
973
1508
|
const reader = react.useMemo(() => createContentSlotReader(state.data), [state.data]);
|
|
974
1509
|
return {
|
|
975
1510
|
...state,
|
|
@@ -977,6 +1512,11 @@ function usePageContent(slug, options = {}) {
|
|
|
977
1512
|
...reader
|
|
978
1513
|
};
|
|
979
1514
|
}
|
|
1515
|
+
|
|
1516
|
+
// src/react/hooks/useNews.ts
|
|
1517
|
+
function useNews(options = {}) {
|
|
1518
|
+
return useContent("news", options);
|
|
1519
|
+
}
|
|
980
1520
|
function useMySection(options = {}) {
|
|
981
1521
|
const { includeMembers = true } = options;
|
|
982
1522
|
const { isAuthenticated } = auth0React.useAuth0();
|
|
@@ -1375,6 +1915,73 @@ function useDocumentDownload(documentId) {
|
|
|
1375
1915
|
refetch: query.refetch
|
|
1376
1916
|
};
|
|
1377
1917
|
}
|
|
1918
|
+
function useMemberDocuments(options) {
|
|
1919
|
+
const { client, orgId } = useAxCloudContext();
|
|
1920
|
+
const params = buildDocumentParams(options);
|
|
1921
|
+
const folderId = options?.folderId;
|
|
1922
|
+
const query = reactQuery.useQuery({
|
|
1923
|
+
queryKey: ["axcloud", "documents", "member", "list", orgId, options],
|
|
1924
|
+
queryFn: async () => {
|
|
1925
|
+
return client.get(`/documents/members/${orgId}/documents`, params);
|
|
1926
|
+
},
|
|
1927
|
+
staleTime: 1e3 * 60 * 5
|
|
1928
|
+
});
|
|
1929
|
+
const allItems = query.data?.items ?? [];
|
|
1930
|
+
const items = folderId ? allItems.filter((doc) => doc.folder_ids?.includes(folderId)) : allItems;
|
|
1931
|
+
return {
|
|
1932
|
+
documents: items,
|
|
1933
|
+
items,
|
|
1934
|
+
count: folderId ? items.length : query.data?.count ?? 0,
|
|
1935
|
+
isLoading: query.isLoading,
|
|
1936
|
+
isError: query.isError,
|
|
1937
|
+
error: query.error,
|
|
1938
|
+
refetch: query.refetch
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
function useMemberDocumentFolders() {
|
|
1942
|
+
const { client, orgId } = useAxCloudContext();
|
|
1943
|
+
const query = reactQuery.useQuery({
|
|
1944
|
+
queryKey: ["axcloud", "documents", "member", "folders", orgId],
|
|
1945
|
+
queryFn: async () => {
|
|
1946
|
+
return client.get(
|
|
1947
|
+
`/documents/members/${orgId}/document-folders`
|
|
1948
|
+
);
|
|
1949
|
+
},
|
|
1950
|
+
staleTime: 1e3 * 60 * 5
|
|
1951
|
+
});
|
|
1952
|
+
return {
|
|
1953
|
+
folders: query.data?.folders ?? [],
|
|
1954
|
+
isLoading: query.isLoading,
|
|
1955
|
+
isError: query.isError,
|
|
1956
|
+
error: query.error,
|
|
1957
|
+
refetch: query.refetch
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function useMemberDocumentDownload(documentId) {
|
|
1961
|
+
const { client, orgId } = useAxCloudContext();
|
|
1962
|
+
const query = reactQuery.useQuery({
|
|
1963
|
+
queryKey: ["axcloud", "documents", "member", "download", orgId, documentId],
|
|
1964
|
+
queryFn: async () => {
|
|
1965
|
+
return client.get(
|
|
1966
|
+
`/documents/members/${orgId}/documents/${encodeURIComponent(documentId || "")}/download`
|
|
1967
|
+
);
|
|
1968
|
+
},
|
|
1969
|
+
enabled: !!documentId,
|
|
1970
|
+
staleTime: 1e3 * 60 * 10,
|
|
1971
|
+
gcTime: 1e3 * 60 * 14
|
|
1972
|
+
});
|
|
1973
|
+
return {
|
|
1974
|
+
url: query.data?.url ?? null,
|
|
1975
|
+
documentUrl: query.data?.url ?? null,
|
|
1976
|
+
filename: query.data?.filename ?? null,
|
|
1977
|
+
mimeType: query.data?.mimeType ?? null,
|
|
1978
|
+
expiresIn: query.data?.expiresIn ?? null,
|
|
1979
|
+
isLoading: query.isLoading,
|
|
1980
|
+
isError: query.isError,
|
|
1981
|
+
error: query.error,
|
|
1982
|
+
refetch: query.refetch
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1378
1985
|
function useBarnsportMunicipalities() {
|
|
1379
1986
|
const { client, orgId } = useAxCloudContext();
|
|
1380
1987
|
const query = reactQuery.useQuery({
|
|
@@ -1411,6 +2018,24 @@ function useBarnsportSports() {
|
|
|
1411
2018
|
refetch: query.refetch
|
|
1412
2019
|
};
|
|
1413
2020
|
}
|
|
2021
|
+
function useBarnsportSport(slug) {
|
|
2022
|
+
const { client, orgId } = useAxCloudContext();
|
|
2023
|
+
const query = reactQuery.useQuery({
|
|
2024
|
+
queryKey: ["axcloud", "barnsport", "sport", orgId, slug],
|
|
2025
|
+
queryFn: () => client.get(
|
|
2026
|
+
`/barnsport/public/${orgId}/sports/${slug || ""}`
|
|
2027
|
+
),
|
|
2028
|
+
enabled: Boolean(slug),
|
|
2029
|
+
staleTime: 1e3 * 60 * 15
|
|
2030
|
+
});
|
|
2031
|
+
return {
|
|
2032
|
+
sport: query.data?.sport ?? null,
|
|
2033
|
+
isLoading: query.isLoading,
|
|
2034
|
+
isError: query.isError,
|
|
2035
|
+
error: query.error,
|
|
2036
|
+
refetch: query.refetch
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
1414
2039
|
function useBarnsportActivities(filters = {}) {
|
|
1415
2040
|
const { client, orgId } = useAxCloudContext();
|
|
1416
2041
|
const params = { ...filters };
|
|
@@ -1735,12 +2360,19 @@ exports.checkVotingCode = checkVotingCode;
|
|
|
1735
2360
|
exports.createClient = createClient;
|
|
1736
2361
|
exports.createContentSlotReader = createContentSlotReader;
|
|
1737
2362
|
exports.createFormHelper = createFormHelper;
|
|
2363
|
+
exports.fetchAnalyticsPublicConfig = fetchAnalyticsPublicConfig;
|
|
1738
2364
|
exports.getBallot = getBallot;
|
|
1739
2365
|
exports.getClient = getClient;
|
|
1740
2366
|
exports.getContentSlotMap = getContentSlotMap;
|
|
1741
2367
|
exports.getVotingReceipt = getVotingReceipt;
|
|
2368
|
+
exports.isAllowedAnalyticsEvent = isAllowedAnalyticsEvent;
|
|
1742
2369
|
exports.isAxCloudError = isAxCloudError;
|
|
2370
|
+
exports.isLocalAnalyticsHostname = isLocalAnalyticsHostname;
|
|
2371
|
+
exports.sanitizeAnalyticsEventData = sanitizeAnalyticsEventData;
|
|
2372
|
+
exports.sanitizeAnalyticsPath = sanitizeAnalyticsPath;
|
|
1743
2373
|
exports.submitForm = submitForm;
|
|
2374
|
+
exports.useAnalytics = useAnalytics;
|
|
2375
|
+
exports.useAnalyticsRuntime = useAnalyticsRuntime;
|
|
1744
2376
|
exports.useAuth = useAuth;
|
|
1745
2377
|
exports.useAvatar = useAvatar;
|
|
1746
2378
|
exports.useAxCloudContext = useAxCloudContext;
|
|
@@ -1748,6 +2380,7 @@ exports.useBarnsportActivities = useBarnsportActivities;
|
|
|
1748
2380
|
exports.useBarnsportActivity = useBarnsportActivity;
|
|
1749
2381
|
exports.useBarnsportMunicipalities = useBarnsportMunicipalities;
|
|
1750
2382
|
exports.useBarnsportSitemap = useBarnsportSitemap;
|
|
2383
|
+
exports.useBarnsportSport = useBarnsportSport;
|
|
1751
2384
|
exports.useBarnsportSports = useBarnsportSports;
|
|
1752
2385
|
exports.useBranding = useBranding;
|
|
1753
2386
|
exports.useCodeBallot = useCodeBallot;
|
|
@@ -1761,6 +2394,9 @@ exports.useLinkedAccounts = useLinkedAccounts;
|
|
|
1761
2394
|
exports.useLogo = useLogo;
|
|
1762
2395
|
exports.useMedia = useMedia;
|
|
1763
2396
|
exports.useMediaList = useMediaList;
|
|
2397
|
+
exports.useMemberDocumentDownload = useMemberDocumentDownload;
|
|
2398
|
+
exports.useMemberDocumentFolders = useMemberDocumentFolders;
|
|
2399
|
+
exports.useMemberDocuments = useMemberDocuments;
|
|
1764
2400
|
exports.useModuleAccess = useModuleAccess;
|
|
1765
2401
|
exports.useMySection = useMySection;
|
|
1766
2402
|
exports.useNews = useNews;
|
|
@@ -1769,6 +2405,7 @@ exports.usePageContent = usePageContent;
|
|
|
1769
2405
|
exports.useSections = useSections;
|
|
1770
2406
|
exports.useSubmitBarnsportInterest = useSubmitBarnsportInterest;
|
|
1771
2407
|
exports.useUpdateFormSubmission = useUpdateFormSubmission;
|
|
2408
|
+
exports.useUpdateMyProfile = useUpdateMyProfile;
|
|
1772
2409
|
exports.useUser = useUser;
|
|
1773
2410
|
exports.userHasAllPermissions = userHasAllPermissions;
|
|
1774
2411
|
exports.userHasAnyPermission = userHasAnyPermission;
|