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