@axcode/axcloud-sdk 0.1.5 → 0.1.7-dev.3a3be6d

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.js CHANGED
@@ -124,7 +124,7 @@ var AxCloudClient = class {
124
124
  */
125
125
  shouldAttachApiKey(path) {
126
126
  const normalized = path.startsWith("/") ? path : `/${path}`;
127
- return normalized.startsWith("/public/") || normalized.startsWith("/api/public/") || normalized.startsWith("/content/public/");
127
+ return normalized.startsWith("/public/") || normalized.startsWith("/api/public/") || normalized.startsWith("/content/public/") || normalized.startsWith("/media/public/") || normalized.startsWith("/documents/public/") || normalized.startsWith("/barnsport/public/") || normalized.startsWith("/api/barnsport/public/");
128
128
  }
129
129
  /**
130
130
  * Make an authenticated request
@@ -238,6 +238,88 @@ function getClient(key) {
238
238
  throw new Error("AxCloud client not found. Make sure AxCloudProvider is mounted and use useAxCloudContext() to get the client.");
239
239
  }
240
240
 
241
+ // src/core/contentSlots.ts
242
+ var SLOT_ATTRIBUTE_KEYS = ["slot", "field", "key", "name"];
243
+ function getContentSlotMap(content) {
244
+ const fields = isRecord(content?.fields) ? { ...content?.fields } : {};
245
+ for (const block of content?.blocks || []) {
246
+ const slot = getBlockSlot(block);
247
+ if (!slot) continue;
248
+ fields[slot] = blockToSlotValue(block);
249
+ }
250
+ return fields;
251
+ }
252
+ function createContentSlotReader(content) {
253
+ const fields = getContentSlotMap(content);
254
+ const value = (key, fallback) => {
255
+ return key in fields ? fields[key] : fallback;
256
+ };
257
+ return {
258
+ fields,
259
+ value,
260
+ text: (key, fallback = "") => stringifySlotValue(value(key, fallback)),
261
+ html: (key, fallback = "") => stringifySlotValue(value(key, fallback)),
262
+ list: (key, fallback = []) => slotValueToList(value(key, fallback)),
263
+ image: (key, fallback) => slotValueToImage(value(key, fallback))
264
+ };
265
+ }
266
+ function getBlockSlot(block) {
267
+ const attributes = block.attributes || {};
268
+ for (const key of SLOT_ATTRIBUTE_KEYS) {
269
+ const value = attributes[key];
270
+ if (typeof value === "string" && value.trim()) return value.trim();
271
+ }
272
+ return void 0;
273
+ }
274
+ function blockToSlotValue(block) {
275
+ if (block.attributes && "value" in block.attributes) {
276
+ return block.attributes.value;
277
+ }
278
+ if (block.type === "list") {
279
+ return Array.isArray(block.items) ? block.items : [];
280
+ }
281
+ if (block.type === "image" && block.url) {
282
+ return {
283
+ url: block.url,
284
+ alt: block.alt,
285
+ caption: block.caption
286
+ };
287
+ }
288
+ return block.content ?? "";
289
+ }
290
+ function stringifySlotValue(value) {
291
+ if (value === void 0 || value === null) return "";
292
+ if (typeof value === "string") return value;
293
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
294
+ if (Array.isArray(value)) return value.map((item) => stringifySlotValue(item)).filter(Boolean).join("\n");
295
+ return "";
296
+ }
297
+ function slotValueToList(value) {
298
+ if (Array.isArray(value)) {
299
+ return value.map((item) => stringifySlotValue(item)).filter(Boolean);
300
+ }
301
+ if (typeof value === "string") {
302
+ return value.split(/\r?\n/).map((item) => item.trim()).filter(Boolean);
303
+ }
304
+ return [];
305
+ }
306
+ function slotValueToImage(value) {
307
+ if (isRecord(value) && typeof value.url === "string") {
308
+ return {
309
+ url: value.url,
310
+ alt: typeof value.alt === "string" ? value.alt : void 0,
311
+ caption: typeof value.caption === "string" ? value.caption : void 0
312
+ };
313
+ }
314
+ if (typeof value === "string" && value) {
315
+ return { url: value };
316
+ }
317
+ return void 0;
318
+ }
319
+ function isRecord(value) {
320
+ return !!value && typeof value === "object" && !Array.isArray(value);
321
+ }
322
+
241
323
  // src/core/types.ts
242
324
  function userHasPermission(user, permission) {
243
325
  if (user.permissions && user.permissions.length > 0) {
@@ -351,6 +433,98 @@ function createFormHelper(config) {
351
433
  })
352
434
  };
353
435
  }
436
+
437
+ // src/core/voting.ts
438
+ function baseOf(votingApiUrl) {
439
+ return votingApiUrl.replace(/\/$/, "");
440
+ }
441
+ async function readJson(response) {
442
+ const json = await response.json().catch(() => ({}));
443
+ if (json && typeof json === "object" && "success" in json && "data" in json) {
444
+ return json.data;
445
+ }
446
+ return json;
447
+ }
448
+ async function ensureOk(response) {
449
+ if (!response.ok) {
450
+ const body = await response.json().catch(() => ({}));
451
+ const message = body && (body.error || body.message) || `Request failed with status ${response.status}`;
452
+ throw new Error(message);
453
+ }
454
+ }
455
+ async function checkVotingCode(votingApiUrl, params) {
456
+ const response = await fetch(
457
+ `${baseOf(votingApiUrl)}/public/organizations/${params.orgId}/polls/${params.pollId}/eligibility`,
458
+ {
459
+ method: "POST",
460
+ headers: { "Content-Type": "application/json" },
461
+ body: JSON.stringify({ code: params.code })
462
+ }
463
+ );
464
+ await ensureOk(response);
465
+ return readJson(response);
466
+ }
467
+ async function castVoteWithCode(votingApiUrl, params) {
468
+ const response = await fetch(
469
+ `${baseOf(votingApiUrl)}/public/organizations/${params.orgId}/polls/${params.pollId}/vote`,
470
+ {
471
+ method: "POST",
472
+ headers: { "Content-Type": "application/json" },
473
+ body: JSON.stringify({ code: params.code, selected_options: params.selectedOptions, rankings: params.rankings })
474
+ }
475
+ );
476
+ await ensureOk(response);
477
+ return readJson(response);
478
+ }
479
+ async function getVotingReceipt(votingApiUrl, params) {
480
+ const response = await fetch(
481
+ `${baseOf(votingApiUrl)}/public/organizations/${params.orgId}/polls/${params.pollId}/receipt`,
482
+ {
483
+ method: "POST",
484
+ headers: { "Content-Type": "application/json" },
485
+ body: JSON.stringify({ code: params.code })
486
+ }
487
+ );
488
+ await ensureOk(response);
489
+ return readJson(response);
490
+ }
491
+ function authHeaders(accessToken) {
492
+ return { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}` };
493
+ }
494
+ async function getBallot(votingApiUrl, params) {
495
+ const base = `${baseOf(votingApiUrl)}/organizations/${params.orgId}/polls/${params.pollId}`;
496
+ const pollResp = await fetch(base, { headers: authHeaders(params.accessToken) });
497
+ await ensureOk(pollResp);
498
+ const poll = await readJson(pollResp);
499
+ let candidates = [];
500
+ if (poll.poll_type === "election") {
501
+ const candResp = await fetch(`${base}/candidates`, { headers: authHeaders(params.accessToken) });
502
+ await ensureOk(candResp);
503
+ const data = await readJson(candResp);
504
+ candidates = data.candidates || [];
505
+ }
506
+ return { ...poll, candidates };
507
+ }
508
+ async function checkEligibility(votingApiUrl, params) {
509
+ const response = await fetch(
510
+ `${baseOf(votingApiUrl)}/organizations/${params.orgId}/polls/${params.pollId}/eligibility`,
511
+ { headers: authHeaders(params.accessToken) }
512
+ );
513
+ await ensureOk(response);
514
+ return readJson(response);
515
+ }
516
+ async function castVote(votingApiUrl, params) {
517
+ const response = await fetch(
518
+ `${baseOf(votingApiUrl)}/organizations/${params.orgId}/polls/${params.pollId}/vote`,
519
+ {
520
+ method: "POST",
521
+ headers: authHeaders(params.accessToken),
522
+ body: JSON.stringify({ selected_options: params.selectedOptions, rankings: params.rankings })
523
+ }
524
+ );
525
+ await ensureOk(response);
526
+ return readJson(response);
527
+ }
354
528
  var AxCloudContext = react.createContext(null);
355
529
  function useAxCloudContext() {
356
530
  const context = react.useContext(AxCloudContext);
@@ -661,6 +835,7 @@ function useNews(options = {}) {
661
835
  search,
662
836
  sortBy = "publishedAt",
663
837
  sortOrder = "desc",
838
+ lang,
664
839
  publicOnly = false
665
840
  } = options;
666
841
  const usePublicEndpoint = isPublicMode || publicOnly;
@@ -677,7 +852,8 @@ function useNews(options = {}) {
677
852
  sectionId,
678
853
  search,
679
854
  sortBy,
680
- sortOrder
855
+ sortOrder,
856
+ lang
681
857
  ],
682
858
  queryFn: async () => {
683
859
  const endpoint = usePublicEndpoint ? `/content/public/${orgId}/news` : `/content/members/${orgId}/news`;
@@ -689,7 +865,8 @@ function useNews(options = {}) {
689
865
  sectionId,
690
866
  search,
691
867
  sortBy,
692
- sortOrder
868
+ sortOrder,
869
+ lang
693
870
  });
694
871
  },
695
872
  staleTime: 1e3 * 60 * 5
@@ -791,6 +968,15 @@ function useContentItem(contentType, idOrSlug, options = {}) {
791
968
  refetch: query.refetch
792
969
  };
793
970
  }
971
+ function usePageContent(slug, options = {}) {
972
+ const state = useContentItem("pages", slug, options);
973
+ const reader = react.useMemo(() => createContentSlotReader(state.data), [state.data]);
974
+ return {
975
+ ...state,
976
+ page: state.data,
977
+ ...reader
978
+ };
979
+ }
794
980
  function useMySection(options = {}) {
795
981
  const { includeMembers = true } = options;
796
982
  const { isAuthenticated } = auth0React.useAuth0();
@@ -909,6 +1095,76 @@ function useUpdateFormSubmission() {
909
1095
  }
910
1096
  });
911
1097
  }
1098
+ function useCodeBallot({ pollId, votingApiUrl }) {
1099
+ const { orgId, config } = useAxCloudContext();
1100
+ const baseUrl = votingApiUrl || `${config.apiUrl.replace(/\/$/, "")}/voting`;
1101
+ const [stage, setStage] = react.useState("enter_code");
1102
+ const [code, setCode] = react.useState("");
1103
+ const [ballot, setBallot] = react.useState(null);
1104
+ const [receipt, setReceipt] = react.useState(null);
1105
+ const [reason, setReason] = react.useState(void 0);
1106
+ const [loading, setLoading] = react.useState(false);
1107
+ const [error, setError] = react.useState(null);
1108
+ const validateCode = react.useCallback(
1109
+ async (inputCode) => {
1110
+ setLoading(true);
1111
+ setError(null);
1112
+ try {
1113
+ const result = await checkVotingCode(baseUrl, { orgId, pollId, code: inputCode });
1114
+ setCode(inputCode);
1115
+ setReason(result.reason);
1116
+ if (result.has_voted) {
1117
+ const r = await getVotingReceipt(baseUrl, { orgId, pollId, code: inputCode }).catch(() => null);
1118
+ setReceipt(r);
1119
+ setStage("already_voted");
1120
+ } else if (result.eligible && result.ballot) {
1121
+ setBallot(result.ballot);
1122
+ setStage("ballot");
1123
+ } else {
1124
+ setError(result.reason || "This code cannot be used to vote right now.");
1125
+ }
1126
+ return result;
1127
+ } catch (e) {
1128
+ setError(e instanceof Error ? e.message : "Failed to validate code");
1129
+ return null;
1130
+ } finally {
1131
+ setLoading(false);
1132
+ }
1133
+ },
1134
+ [baseUrl, orgId, pollId]
1135
+ );
1136
+ const submitVote = react.useCallback(
1137
+ async (selectedOptions, rankings) => {
1138
+ setLoading(true);
1139
+ setError(null);
1140
+ try {
1141
+ await castVoteWithCode(baseUrl, { orgId, pollId, code, selectedOptions, rankings });
1142
+ const r = await getVotingReceipt(baseUrl, { orgId, pollId, code }).catch(() => null);
1143
+ setReceipt(r);
1144
+ setStage("submitted");
1145
+ return true;
1146
+ } catch (e) {
1147
+ setError(e instanceof Error ? e.message : "Failed to submit vote");
1148
+ return false;
1149
+ } finally {
1150
+ setLoading(false);
1151
+ }
1152
+ },
1153
+ [baseUrl, orgId, pollId, code]
1154
+ );
1155
+ const reset = react.useCallback(() => {
1156
+ setStage("enter_code");
1157
+ setCode("");
1158
+ setBallot(null);
1159
+ setReceipt(null);
1160
+ setReason(void 0);
1161
+ setError(null);
1162
+ }, []);
1163
+ return react.useMemo(
1164
+ () => ({ stage, code, ballot, receipt, reason, loading, error, validateCode, submitVote, reset }),
1165
+ [stage, code, ballot, receipt, reason, loading, error, validateCode, submitVote, reset]
1166
+ );
1167
+ }
912
1168
  var LINKED_ACCOUNTS_QUERY_KEY = ["axcloud", "linked-accounts"];
913
1169
  function useLinkedAccounts() {
914
1170
  const { client, stage } = useAxCloudContext();
@@ -1067,6 +1323,160 @@ function useMedia(mediaId) {
1067
1323
  refetch: query.refetch
1068
1324
  };
1069
1325
  }
1326
+ function buildDocumentParams(options) {
1327
+ return {
1328
+ search: options?.search,
1329
+ tags: options?.tags?.join(","),
1330
+ limit: options?.limit
1331
+ };
1332
+ }
1333
+ function useDocuments(options) {
1334
+ const { client, orgId } = useAxCloudContext();
1335
+ const params = buildDocumentParams(options);
1336
+ const query = reactQuery.useQuery({
1337
+ queryKey: ["axcloud", "documents", "list", orgId, options],
1338
+ queryFn: async () => {
1339
+ return client.get(`/documents/public/${orgId}/documents`, params);
1340
+ },
1341
+ staleTime: 1e3 * 60 * 5
1342
+ });
1343
+ return {
1344
+ documents: query.data?.items ?? [],
1345
+ items: query.data?.items ?? [],
1346
+ count: query.data?.count ?? 0,
1347
+ isLoading: query.isLoading,
1348
+ isError: query.isError,
1349
+ error: query.error,
1350
+ refetch: query.refetch
1351
+ };
1352
+ }
1353
+ function useDocumentDownload(documentId) {
1354
+ const { client, orgId } = useAxCloudContext();
1355
+ const query = reactQuery.useQuery({
1356
+ queryKey: ["axcloud", "documents", "download", orgId, documentId],
1357
+ queryFn: async () => {
1358
+ return client.get(
1359
+ `/documents/public/${orgId}/documents/${encodeURIComponent(documentId || "")}/download`
1360
+ );
1361
+ },
1362
+ enabled: !!documentId,
1363
+ staleTime: 1e3 * 60 * 10,
1364
+ gcTime: 1e3 * 60 * 14
1365
+ });
1366
+ return {
1367
+ url: query.data?.url ?? null,
1368
+ documentUrl: query.data?.url ?? null,
1369
+ filename: query.data?.filename ?? null,
1370
+ mimeType: query.data?.mimeType ?? null,
1371
+ expiresIn: query.data?.expiresIn ?? null,
1372
+ isLoading: query.isLoading,
1373
+ isError: query.isError,
1374
+ error: query.error,
1375
+ refetch: query.refetch
1376
+ };
1377
+ }
1378
+ function useBarnsportMunicipalities() {
1379
+ const { client, orgId } = useAxCloudContext();
1380
+ const query = reactQuery.useQuery({
1381
+ queryKey: ["axcloud", "barnsport", "municipalities", orgId],
1382
+ queryFn: () => client.get(
1383
+ `/barnsport/public/${orgId}/municipalities`
1384
+ ),
1385
+ staleTime: 1e3 * 60 * 30
1386
+ });
1387
+ return {
1388
+ municipalities: query.data?.items ?? [],
1389
+ count: query.data?.count ?? 0,
1390
+ isLoading: query.isLoading,
1391
+ isError: query.isError,
1392
+ error: query.error,
1393
+ refetch: query.refetch
1394
+ };
1395
+ }
1396
+ function useBarnsportSports() {
1397
+ const { client, orgId } = useAxCloudContext();
1398
+ const query = reactQuery.useQuery({
1399
+ queryKey: ["axcloud", "barnsport", "sports", orgId],
1400
+ queryFn: () => client.get(
1401
+ `/barnsport/public/${orgId}/sports`
1402
+ ),
1403
+ staleTime: 1e3 * 60 * 30
1404
+ });
1405
+ return {
1406
+ sports: query.data?.items ?? [],
1407
+ count: query.data?.count ?? 0,
1408
+ isLoading: query.isLoading,
1409
+ isError: query.isError,
1410
+ error: query.error,
1411
+ refetch: query.refetch
1412
+ };
1413
+ }
1414
+ function useBarnsportActivities(filters = {}) {
1415
+ const { client, orgId } = useAxCloudContext();
1416
+ const params = { ...filters };
1417
+ const query = reactQuery.useQuery({
1418
+ queryKey: ["axcloud", "barnsport", "activities", orgId, filters],
1419
+ queryFn: () => client.get(
1420
+ `/barnsport/public/${orgId}/activities`,
1421
+ params
1422
+ ),
1423
+ staleTime: 1e3 * 60 * 5
1424
+ });
1425
+ return {
1426
+ activities: query.data?.items ?? [],
1427
+ count: query.data?.count ?? 0,
1428
+ nextCursor: query.data?.nextCursor ?? null,
1429
+ isLoading: query.isLoading,
1430
+ isError: query.isError,
1431
+ error: query.error,
1432
+ refetch: query.refetch
1433
+ };
1434
+ }
1435
+ function useBarnsportActivity(slug) {
1436
+ const { client, orgId } = useAxCloudContext();
1437
+ const query = reactQuery.useQuery({
1438
+ queryKey: ["axcloud", "barnsport", "activity", orgId, slug],
1439
+ queryFn: () => client.get(
1440
+ `/barnsport/public/${orgId}/activities/${slug || ""}`
1441
+ ),
1442
+ enabled: Boolean(slug),
1443
+ staleTime: 1e3 * 60 * 5
1444
+ });
1445
+ return {
1446
+ activity: query.data?.activity ?? null,
1447
+ isLoading: query.isLoading,
1448
+ isError: query.isError,
1449
+ error: query.error,
1450
+ refetch: query.refetch
1451
+ };
1452
+ }
1453
+ function useBarnsportSitemap(baseUrl) {
1454
+ const { client, orgId } = useAxCloudContext();
1455
+ const query = reactQuery.useQuery({
1456
+ queryKey: ["axcloud", "barnsport", "sitemap", orgId, baseUrl],
1457
+ queryFn: () => client.get(`/barnsport/public/${orgId}/sitemap`, {
1458
+ baseUrl
1459
+ }),
1460
+ staleTime: 1e3 * 60 * 15
1461
+ });
1462
+ return {
1463
+ sitemap: query.data ?? null,
1464
+ urls: query.data?.urls ?? [],
1465
+ isLoading: query.isLoading,
1466
+ isError: query.isError,
1467
+ error: query.error,
1468
+ refetch: query.refetch
1469
+ };
1470
+ }
1471
+ function useSubmitBarnsportInterest() {
1472
+ const { client, orgId } = useAxCloudContext();
1473
+ return reactQuery.useMutation({
1474
+ mutationFn: (submission) => client.post(
1475
+ `/barnsport/public/${orgId}/interest`,
1476
+ submission
1477
+ )
1478
+ });
1479
+ }
1070
1480
  function LoginButton({
1071
1481
  children = "Log in",
1072
1482
  returnTo,
@@ -1318,17 +1728,33 @@ exports.LoginButton = LoginButton;
1318
1728
  exports.LogoutButton = LogoutButton;
1319
1729
  exports.ModuleGate = ModuleGate;
1320
1730
  exports.ProtectedRoute = ProtectedRoute;
1731
+ exports.castVote = castVote;
1732
+ exports.castVoteWithCode = castVoteWithCode;
1733
+ exports.checkEligibility = checkEligibility;
1734
+ exports.checkVotingCode = checkVotingCode;
1321
1735
  exports.createClient = createClient;
1736
+ exports.createContentSlotReader = createContentSlotReader;
1322
1737
  exports.createFormHelper = createFormHelper;
1738
+ exports.getBallot = getBallot;
1323
1739
  exports.getClient = getClient;
1740
+ exports.getContentSlotMap = getContentSlotMap;
1741
+ exports.getVotingReceipt = getVotingReceipt;
1324
1742
  exports.isAxCloudError = isAxCloudError;
1325
1743
  exports.submitForm = submitForm;
1326
1744
  exports.useAuth = useAuth;
1327
1745
  exports.useAvatar = useAvatar;
1328
1746
  exports.useAxCloudContext = useAxCloudContext;
1747
+ exports.useBarnsportActivities = useBarnsportActivities;
1748
+ exports.useBarnsportActivity = useBarnsportActivity;
1749
+ exports.useBarnsportMunicipalities = useBarnsportMunicipalities;
1750
+ exports.useBarnsportSitemap = useBarnsportSitemap;
1751
+ exports.useBarnsportSports = useBarnsportSports;
1329
1752
  exports.useBranding = useBranding;
1753
+ exports.useCodeBallot = useCodeBallot;
1330
1754
  exports.useContent = useContent;
1331
1755
  exports.useContentItem = useContentItem;
1756
+ exports.useDocumentDownload = useDocumentDownload;
1757
+ exports.useDocuments = useDocuments;
1332
1758
  exports.useFormStats = useFormStats;
1333
1759
  exports.useForms = useForms;
1334
1760
  exports.useLinkedAccounts = useLinkedAccounts;
@@ -1339,7 +1765,9 @@ exports.useModuleAccess = useModuleAccess;
1339
1765
  exports.useMySection = useMySection;
1340
1766
  exports.useNews = useNews;
1341
1767
  exports.useOrg = useOrg;
1768
+ exports.usePageContent = usePageContent;
1342
1769
  exports.useSections = useSections;
1770
+ exports.useSubmitBarnsportInterest = useSubmitBarnsportInterest;
1343
1771
  exports.useUpdateFormSubmission = useUpdateFormSubmission;
1344
1772
  exports.useUser = useUser;
1345
1773
  exports.userHasAllPermissions = userHasAllPermissions;