@axcode/axcloud-sdk 0.1.4 → 0.1.6

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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # AxCloud SDK License
2
+
3
+ Copyright (c) 2026 Axcode AB. All rights reserved.
4
+
5
+ This package is proprietary software owned by Axcode AB.
6
+
7
+ Use, copying, modification, distribution, or sublicensing of this package is only permitted under a separate written agreement with Axcode AB.
8
+
9
+ No open source license is granted.
package/README.md CHANGED
@@ -87,6 +87,21 @@ function NewsPage() {
87
87
 
88
88
  > **Content visibility:** When the user is authenticated, `useNews`, `useContent('pages')`, and `useContent('events')` automatically fetch **public + internal + members-only** content based on the user's organization membership. For anonymous users, only public content is returned.
89
89
 
90
+ Pages can be stored once in AxCloud with translations and requested per language:
91
+
92
+ ```tsx
93
+ import { useContentItem } from '@axcode/axcloud-sdk'
94
+
95
+ function AboutPage({ locale }: { locale: 'sv' | 'en' }) {
96
+ const { data: page } = useContentItem('pages', 'about-us', {
97
+ lang: locale,
98
+ publicOnly: true,
99
+ })
100
+
101
+ return <h1>{page?.title}</h1>
102
+ }
103
+ ```
104
+
90
105
  ### 3. Add authentication
91
106
 
92
107
  ```tsx
@@ -254,11 +269,14 @@ interface ContentQueryOptions {
254
269
  page?: number // Page number (default: 1)
255
270
  pageSize?: number // Items per page (default: 20)
256
271
  status?: 'draft' | 'published' | 'archived'
257
- visibility?: 'public' | 'members' | 'section'
272
+ visibility?: 'draft' | 'published' | 'archived'
273
+ audience?: 'public' | 'internal' | 'members' | 'section'
258
274
  sectionId?: string // Filter by specific section
259
275
  search?: string // Search in title/content
260
276
  sortBy?: string // Sort field (default: 'createdAt')
261
277
  sortOrder?: 'asc' | 'desc'
278
+ lang?: string // Return translated title/slug/blocks when available
279
+ publicOnly?: boolean // Force public endpoint even when authenticated
262
280
  }
263
281
  ```
264
282
 
package/dist/index.d.mts CHANGED
@@ -197,21 +197,72 @@ interface Membership {
197
197
  expiresAt?: string;
198
198
  metadata?: Record<string, unknown>;
199
199
  }
200
+ interface ContentBlock {
201
+ id: string;
202
+ type: string;
203
+ content?: string;
204
+ url?: string;
205
+ alt?: string;
206
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
207
+ items?: unknown[];
208
+ language?: string;
209
+ caption?: string;
210
+ attributes?: Record<string, unknown>;
211
+ ordered?: boolean;
212
+ author?: string;
213
+ }
214
+ interface ContentTranslation {
215
+ title: string;
216
+ slug: string;
217
+ description?: string;
218
+ blocks: ContentBlock[];
219
+ /** Structured website/template fields for this language. */
220
+ fields?: Record<string, unknown>;
221
+ metaTitle?: string;
222
+ metaDescription?: string;
223
+ keywords?: string[];
224
+ }
225
+ type ContentPublicationState = 'draft' | 'published' | 'archived';
226
+ type ContentAudience = 'public' | 'internal' | 'members' | 'section';
200
227
  interface ContentItem {
201
228
  id: string;
202
229
  organizationId: string;
203
230
  type: string;
204
231
  title: string;
205
232
  slug: string;
206
- content: string;
233
+ /** Legacy string content. New AxCloud content uses `blocks`. */
234
+ content?: string;
235
+ description?: string;
236
+ blocks?: ContentBlock[];
237
+ /** Template identifier used by websites, e.g. "home" or "landing". */
238
+ template?: string;
239
+ /** Structured website/template fields. Frontends own layout and read these values by key. */
240
+ fields?: Record<string, unknown>;
207
241
  excerpt?: string;
208
242
  author?: {
209
243
  id: string;
210
244
  name: string;
211
245
  };
212
- status: 'draft' | 'published' | 'archived';
213
- visibility: 'public' | 'members' | 'section';
246
+ status?: ContentPublicationState;
247
+ visibility?: ContentPublicationState;
248
+ audience?: ContentAudience;
214
249
  sectionId?: string;
250
+ tags?: string[];
251
+ lang?: string;
252
+ featuredImage?: string;
253
+ metaTitle?: string;
254
+ metaDescription?: string;
255
+ keywords?: string[];
256
+ translations?: Record<string, ContentTranslation>;
257
+ currentLanguage?: string;
258
+ availableLanguages?: string[];
259
+ seo?: {
260
+ canonical: string;
261
+ hreflang: Array<{
262
+ lang: string;
263
+ href: string;
264
+ }>;
265
+ };
215
266
  publishedAt?: string;
216
267
  createdAt: string;
217
268
  updatedAt: string;
@@ -253,12 +304,15 @@ interface AxCloudConfig {
253
304
  interface ContentQueryOptions {
254
305
  page?: number;
255
306
  pageSize?: number;
256
- status?: 'draft' | 'published' | 'archived';
257
- visibility?: 'public' | 'members' | 'section';
307
+ status?: ContentPublicationState;
308
+ visibility?: ContentPublicationState;
309
+ audience?: ContentAudience;
258
310
  sectionId?: string;
259
311
  search?: string;
260
312
  sortBy?: string;
261
313
  sortOrder?: 'asc' | 'desc';
314
+ /** Return the requested translation when content has language variants. */
315
+ lang?: string;
262
316
  /** Force public endpoint even when authenticated (for public pages) */
263
317
  publicOnly?: boolean;
264
318
  }
@@ -313,6 +367,24 @@ interface MediaListResponse {
313
367
  prefix: string;
314
368
  }
315
369
 
370
+ type ContentSlotValue = string | number | boolean | null | undefined | string[] | Record<string, unknown> | unknown[];
371
+ type ContentSlotMap = Record<string, ContentSlotValue>;
372
+ interface ContentSlotImage {
373
+ url: string;
374
+ alt?: string;
375
+ caption?: string;
376
+ }
377
+ interface ContentSlotReader {
378
+ fields: ContentSlotMap;
379
+ value<T = ContentSlotValue>(key: string, fallback?: T): ContentSlotValue | T;
380
+ text(key: string, fallback?: string): string;
381
+ html(key: string, fallback?: string): string;
382
+ list(key: string, fallback?: string[]): string[];
383
+ image(key: string, fallback?: ContentSlotImage): ContentSlotImage | undefined;
384
+ }
385
+ declare function getContentSlotMap(content?: Pick<ContentItem, 'fields' | 'blocks'> | null): ContentSlotMap;
386
+ declare function createContentSlotReader(content?: Pick<ContentItem, 'fields' | 'blocks'> | null): ContentSlotReader;
387
+
316
388
  /**
317
389
  * AxCloud Forms Module
318
390
  *
@@ -685,7 +757,7 @@ interface PublicOrganization {
685
757
  *
686
758
  * Automatically uses the appropriate endpoint:
687
759
  * - With API key (public): `/content/public/{orgId}/org`
688
- * - With JWT auth: `/{stage}/api/organizations/{orgId}`
760
+ * - With JWT auth: `/orgs/api/organizations/{orgId}`
689
761
  *
690
762
  * @example
691
763
  * ```tsx
@@ -817,6 +889,8 @@ declare function useContent<T extends ContentItem = ContentItem>(contentType: st
817
889
  interface ContentItemOptions {
818
890
  /** Force public endpoint even when authenticated */
819
891
  publicOnly?: boolean;
892
+ /** Return the requested translation when content has language variants. */
893
+ lang?: string;
820
894
  }
821
895
  /**
822
896
  * useContentItem - Fetch a single content item by ID or slug
@@ -833,7 +907,7 @@ interface ContentItemOptions {
833
907
  * @example
834
908
  * ```tsx
835
909
  * // Fetch a page by slug
836
- * const { data: page } = useContentItem('pages', 'about-us')
910
+ * const { data: page } = useContentItem('pages', 'about-us', { lang: 'sv' })
837
911
  *
838
912
  * // Force public endpoint
839
913
  * const { data: page } = useContentItem('pages', 'about-us', { publicOnly: true })
@@ -846,6 +920,22 @@ declare function useContentItem<T extends ContentItem = ContentItem>(contentType
846
920
  error: Error | null;
847
921
  refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<_tanstack_react_query.NoInfer<T>, Error>>;
848
922
  };
923
+ interface PageContentState<T extends ContentItem = ContentItem> extends ContentSlotReader {
924
+ page: T | null;
925
+ data: T | null;
926
+ isLoading: boolean;
927
+ isError: boolean;
928
+ error: Error | null;
929
+ refetch: () => unknown;
930
+ }
931
+ /**
932
+ * usePageContent - Fetch a website page and expose structured template fields.
933
+ *
934
+ * Use `page.content`/`page.blocks` for article-like rich text pages. Use
935
+ * `text('hero.title')`, `list('benefits.items')`, etc. for designed website templates
936
+ * where the frontend owns layout and AxCloud owns content.
937
+ */
938
+ declare function usePageContent<T extends ContentItem = ContentItem>(slug: string, options?: ContentItemOptions): PageContentState<T>;
849
939
 
850
940
  interface SectionWithMembers extends Section {
851
941
  members?: User[];
@@ -1479,4 +1569,4 @@ interface LinkedAccountsListProps {
1479
1569
  */
1480
1570
  declare function LinkedAccountsList({ providers, showConnectButtons, className, onUnlink, onLinkInitiated, renderIdentity, renderAvailableProvider, }: LinkedAccountsListProps): react_jsx_runtime.JSX.Element;
1481
1571
 
1482
- export { type AvailableProvider, AxCloudClient, type AxCloudConfig, AxCloudError, type AxCloudErrorCode, type AxCloudModule, AxCloudProvider, type BaseRole, type ContentItem, type ContentQueryOptions, type CustomRole, type FormStatsResponse, type FormSubmission, type FormSubmissionRecord, type FormSubmissionResponse, type FormSubmissionsResponse, LinkedAccountsList, type LinkedAccountsListProps, type LinkedIdentity, LoginButton, LogoutButton, type MediaItem, type MediaListResponse, type Membership, ModuleGate, type NewsItem, type Organization, type OrganizationBranding, type OrganizationSettings, type Page, type PaginatedResponse, ProtectedRoute, type Section, type SectionPermissions, type SignedMediaUrl, type SystemRole, type UseLinkedAccountsReturn, type User, type UserRole, createClient, createFormHelper, getClient, isAxCloudError, submitForm, useAuth, useAvatar, useAxCloudContext, useBranding, useContent, useContentItem, useFormStats, useForms, useLinkedAccounts, useLogo, useMedia, useMediaList, useModuleAccess, useMySection, useNews, useOrg, useSections, useUpdateFormSubmission, useUser, userHasAllPermissions, userHasAnyPermission, userHasPermission };
1572
+ export { type AvailableProvider, AxCloudClient, type AxCloudConfig, AxCloudError, type AxCloudErrorCode, type AxCloudModule, AxCloudProvider, type BaseRole, type ContentItem, type ContentQueryOptions, type ContentSlotImage, type ContentSlotMap, type ContentSlotReader, type ContentSlotValue, type CustomRole, type FormStatsResponse, type FormSubmission, type FormSubmissionRecord, type FormSubmissionResponse, type FormSubmissionsResponse, LinkedAccountsList, type LinkedAccountsListProps, type LinkedIdentity, LoginButton, LogoutButton, type MediaItem, type MediaListResponse, type Membership, ModuleGate, type NewsItem, type Organization, type OrganizationBranding, type OrganizationSettings, type Page, type PageContentState, type PaginatedResponse, ProtectedRoute, type Section, type SectionPermissions, type SignedMediaUrl, type SystemRole, type UseLinkedAccountsReturn, type User, type UserRole, createClient, createContentSlotReader, createFormHelper, getClient, getContentSlotMap, isAxCloudError, submitForm, useAuth, useAvatar, useAxCloudContext, useBranding, useContent, useContentItem, useFormStats, useForms, useLinkedAccounts, useLogo, useMedia, useMediaList, useModuleAccess, useMySection, useNews, useOrg, usePageContent, useSections, useUpdateFormSubmission, useUser, userHasAllPermissions, userHasAnyPermission, userHasPermission };
package/dist/index.d.ts CHANGED
@@ -197,21 +197,72 @@ interface Membership {
197
197
  expiresAt?: string;
198
198
  metadata?: Record<string, unknown>;
199
199
  }
200
+ interface ContentBlock {
201
+ id: string;
202
+ type: string;
203
+ content?: string;
204
+ url?: string;
205
+ alt?: string;
206
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
207
+ items?: unknown[];
208
+ language?: string;
209
+ caption?: string;
210
+ attributes?: Record<string, unknown>;
211
+ ordered?: boolean;
212
+ author?: string;
213
+ }
214
+ interface ContentTranslation {
215
+ title: string;
216
+ slug: string;
217
+ description?: string;
218
+ blocks: ContentBlock[];
219
+ /** Structured website/template fields for this language. */
220
+ fields?: Record<string, unknown>;
221
+ metaTitle?: string;
222
+ metaDescription?: string;
223
+ keywords?: string[];
224
+ }
225
+ type ContentPublicationState = 'draft' | 'published' | 'archived';
226
+ type ContentAudience = 'public' | 'internal' | 'members' | 'section';
200
227
  interface ContentItem {
201
228
  id: string;
202
229
  organizationId: string;
203
230
  type: string;
204
231
  title: string;
205
232
  slug: string;
206
- content: string;
233
+ /** Legacy string content. New AxCloud content uses `blocks`. */
234
+ content?: string;
235
+ description?: string;
236
+ blocks?: ContentBlock[];
237
+ /** Template identifier used by websites, e.g. "home" or "landing". */
238
+ template?: string;
239
+ /** Structured website/template fields. Frontends own layout and read these values by key. */
240
+ fields?: Record<string, unknown>;
207
241
  excerpt?: string;
208
242
  author?: {
209
243
  id: string;
210
244
  name: string;
211
245
  };
212
- status: 'draft' | 'published' | 'archived';
213
- visibility: 'public' | 'members' | 'section';
246
+ status?: ContentPublicationState;
247
+ visibility?: ContentPublicationState;
248
+ audience?: ContentAudience;
214
249
  sectionId?: string;
250
+ tags?: string[];
251
+ lang?: string;
252
+ featuredImage?: string;
253
+ metaTitle?: string;
254
+ metaDescription?: string;
255
+ keywords?: string[];
256
+ translations?: Record<string, ContentTranslation>;
257
+ currentLanguage?: string;
258
+ availableLanguages?: string[];
259
+ seo?: {
260
+ canonical: string;
261
+ hreflang: Array<{
262
+ lang: string;
263
+ href: string;
264
+ }>;
265
+ };
215
266
  publishedAt?: string;
216
267
  createdAt: string;
217
268
  updatedAt: string;
@@ -253,12 +304,15 @@ interface AxCloudConfig {
253
304
  interface ContentQueryOptions {
254
305
  page?: number;
255
306
  pageSize?: number;
256
- status?: 'draft' | 'published' | 'archived';
257
- visibility?: 'public' | 'members' | 'section';
307
+ status?: ContentPublicationState;
308
+ visibility?: ContentPublicationState;
309
+ audience?: ContentAudience;
258
310
  sectionId?: string;
259
311
  search?: string;
260
312
  sortBy?: string;
261
313
  sortOrder?: 'asc' | 'desc';
314
+ /** Return the requested translation when content has language variants. */
315
+ lang?: string;
262
316
  /** Force public endpoint even when authenticated (for public pages) */
263
317
  publicOnly?: boolean;
264
318
  }
@@ -313,6 +367,24 @@ interface MediaListResponse {
313
367
  prefix: string;
314
368
  }
315
369
 
370
+ type ContentSlotValue = string | number | boolean | null | undefined | string[] | Record<string, unknown> | unknown[];
371
+ type ContentSlotMap = Record<string, ContentSlotValue>;
372
+ interface ContentSlotImage {
373
+ url: string;
374
+ alt?: string;
375
+ caption?: string;
376
+ }
377
+ interface ContentSlotReader {
378
+ fields: ContentSlotMap;
379
+ value<T = ContentSlotValue>(key: string, fallback?: T): ContentSlotValue | T;
380
+ text(key: string, fallback?: string): string;
381
+ html(key: string, fallback?: string): string;
382
+ list(key: string, fallback?: string[]): string[];
383
+ image(key: string, fallback?: ContentSlotImage): ContentSlotImage | undefined;
384
+ }
385
+ declare function getContentSlotMap(content?: Pick<ContentItem, 'fields' | 'blocks'> | null): ContentSlotMap;
386
+ declare function createContentSlotReader(content?: Pick<ContentItem, 'fields' | 'blocks'> | null): ContentSlotReader;
387
+
316
388
  /**
317
389
  * AxCloud Forms Module
318
390
  *
@@ -685,7 +757,7 @@ interface PublicOrganization {
685
757
  *
686
758
  * Automatically uses the appropriate endpoint:
687
759
  * - With API key (public): `/content/public/{orgId}/org`
688
- * - With JWT auth: `/{stage}/api/organizations/{orgId}`
760
+ * - With JWT auth: `/orgs/api/organizations/{orgId}`
689
761
  *
690
762
  * @example
691
763
  * ```tsx
@@ -817,6 +889,8 @@ declare function useContent<T extends ContentItem = ContentItem>(contentType: st
817
889
  interface ContentItemOptions {
818
890
  /** Force public endpoint even when authenticated */
819
891
  publicOnly?: boolean;
892
+ /** Return the requested translation when content has language variants. */
893
+ lang?: string;
820
894
  }
821
895
  /**
822
896
  * useContentItem - Fetch a single content item by ID or slug
@@ -833,7 +907,7 @@ interface ContentItemOptions {
833
907
  * @example
834
908
  * ```tsx
835
909
  * // Fetch a page by slug
836
- * const { data: page } = useContentItem('pages', 'about-us')
910
+ * const { data: page } = useContentItem('pages', 'about-us', { lang: 'sv' })
837
911
  *
838
912
  * // Force public endpoint
839
913
  * const { data: page } = useContentItem('pages', 'about-us', { publicOnly: true })
@@ -846,6 +920,22 @@ declare function useContentItem<T extends ContentItem = ContentItem>(contentType
846
920
  error: Error | null;
847
921
  refetch: (options?: _tanstack_react_query.RefetchOptions) => Promise<_tanstack_react_query.QueryObserverResult<_tanstack_react_query.NoInfer<T>, Error>>;
848
922
  };
923
+ interface PageContentState<T extends ContentItem = ContentItem> extends ContentSlotReader {
924
+ page: T | null;
925
+ data: T | null;
926
+ isLoading: boolean;
927
+ isError: boolean;
928
+ error: Error | null;
929
+ refetch: () => unknown;
930
+ }
931
+ /**
932
+ * usePageContent - Fetch a website page and expose structured template fields.
933
+ *
934
+ * Use `page.content`/`page.blocks` for article-like rich text pages. Use
935
+ * `text('hero.title')`, `list('benefits.items')`, etc. for designed website templates
936
+ * where the frontend owns layout and AxCloud owns content.
937
+ */
938
+ declare function usePageContent<T extends ContentItem = ContentItem>(slug: string, options?: ContentItemOptions): PageContentState<T>;
849
939
 
850
940
  interface SectionWithMembers extends Section {
851
941
  members?: User[];
@@ -1479,4 +1569,4 @@ interface LinkedAccountsListProps {
1479
1569
  */
1480
1570
  declare function LinkedAccountsList({ providers, showConnectButtons, className, onUnlink, onLinkInitiated, renderIdentity, renderAvailableProvider, }: LinkedAccountsListProps): react_jsx_runtime.JSX.Element;
1481
1571
 
1482
- export { type AvailableProvider, AxCloudClient, type AxCloudConfig, AxCloudError, type AxCloudErrorCode, type AxCloudModule, AxCloudProvider, type BaseRole, type ContentItem, type ContentQueryOptions, type CustomRole, type FormStatsResponse, type FormSubmission, type FormSubmissionRecord, type FormSubmissionResponse, type FormSubmissionsResponse, LinkedAccountsList, type LinkedAccountsListProps, type LinkedIdentity, LoginButton, LogoutButton, type MediaItem, type MediaListResponse, type Membership, ModuleGate, type NewsItem, type Organization, type OrganizationBranding, type OrganizationSettings, type Page, type PaginatedResponse, ProtectedRoute, type Section, type SectionPermissions, type SignedMediaUrl, type SystemRole, type UseLinkedAccountsReturn, type User, type UserRole, createClient, createFormHelper, getClient, isAxCloudError, submitForm, useAuth, useAvatar, useAxCloudContext, useBranding, useContent, useContentItem, useFormStats, useForms, useLinkedAccounts, useLogo, useMedia, useMediaList, useModuleAccess, useMySection, useNews, useOrg, useSections, useUpdateFormSubmission, useUser, userHasAllPermissions, userHasAnyPermission, userHasPermission };
1572
+ export { type AvailableProvider, AxCloudClient, type AxCloudConfig, AxCloudError, type AxCloudErrorCode, type AxCloudModule, AxCloudProvider, type BaseRole, type ContentItem, type ContentQueryOptions, type ContentSlotImage, type ContentSlotMap, type ContentSlotReader, type ContentSlotValue, type CustomRole, type FormStatsResponse, type FormSubmission, type FormSubmissionRecord, type FormSubmissionResponse, type FormSubmissionsResponse, LinkedAccountsList, type LinkedAccountsListProps, type LinkedIdentity, LoginButton, LogoutButton, type MediaItem, type MediaListResponse, type Membership, ModuleGate, type NewsItem, type Organization, type OrganizationBranding, type OrganizationSettings, type Page, type PageContentState, type PaginatedResponse, ProtectedRoute, type Section, type SectionPermissions, type SignedMediaUrl, type SystemRole, type UseLinkedAccountsReturn, type User, type UserRole, createClient, createContentSlotReader, createFormHelper, getClient, getContentSlotMap, isAxCloudError, submitForm, useAuth, useAvatar, useAxCloudContext, useBranding, useContent, useContentItem, useFormStats, useForms, useLinkedAccounts, useLogo, useMedia, useMediaList, useModuleAccess, useMySection, useNews, useOrg, usePageContent, useSections, useUpdateFormSubmission, useUser, userHasAllPermissions, userHasAnyPermission, userHasPermission };
package/dist/index.js CHANGED
@@ -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) {
@@ -706,6 +788,7 @@ function useNews(options = {}) {
706
788
  };
707
789
  }
708
790
  var MEMBER_ENDPOINT_TYPES = ["news", "pages", "events"];
791
+ var PAGE_CONTENT_TYPES = ["page", "pages"];
709
792
  function useContent(contentType, options = {}) {
710
793
  const { client, orgId, isPublicMode } = useAxCloudContext();
711
794
  const {
@@ -717,7 +800,8 @@ function useContent(contentType, options = {}) {
717
800
  sectionId,
718
801
  search,
719
802
  sortBy = "createdAt",
720
- sortOrder = "desc"
803
+ sortOrder = "desc",
804
+ lang
721
805
  } = options;
722
806
  const query = reactQuery.useQuery({
723
807
  queryKey: [
@@ -734,7 +818,8 @@ function useContent(contentType, options = {}) {
734
818
  sectionId,
735
819
  search,
736
820
  sortBy,
737
- sortOrder
821
+ sortOrder,
822
+ lang
738
823
  ],
739
824
  queryFn: async () => {
740
825
  const useMemberEndpoint = !isPublicMode && !publicOnly && MEMBER_ENDPOINT_TYPES.includes(contentType);
@@ -747,7 +832,8 @@ function useContent(contentType, options = {}) {
747
832
  sectionId,
748
833
  search,
749
834
  sortBy,
750
- sortOrder
835
+ sortOrder,
836
+ lang
751
837
  });
752
838
  },
753
839
  staleTime: 1e3 * 60 * 5
@@ -765,13 +851,15 @@ function useContent(contentType, options = {}) {
765
851
  }
766
852
  function useContentItem(contentType, idOrSlug, options = {}) {
767
853
  const { client, orgId, isPublicMode } = useAxCloudContext();
768
- const { publicOnly = false } = options;
769
- const useMemberEndpoint = !isPublicMode && !publicOnly && MEMBER_ENDPOINT_TYPES.includes(contentType);
854
+ const { publicOnly = false, lang } = options;
855
+ const useMemberEndpoint = !isPublicMode && !publicOnly && (MEMBER_ENDPOINT_TYPES.includes(contentType) || PAGE_CONTENT_TYPES.includes(contentType));
770
856
  const query = reactQuery.useQuery({
771
- queryKey: ["axcloud", "content", orgId, contentType, idOrSlug, useMemberEndpoint],
857
+ queryKey: ["axcloud", "content", orgId, contentType, idOrSlug, useMemberEndpoint, lang],
772
858
  queryFn: async () => {
773
859
  const basePath = useMemberEndpoint ? "members" : "public";
774
- return client.get(`/content/${basePath}/${orgId}/${contentType}/${idOrSlug}`);
860
+ const encodedIdOrSlug = encodeURIComponent(idOrSlug);
861
+ const endpoint = PAGE_CONTENT_TYPES.includes(contentType) ? `/content/${basePath}/${orgId}/page/${encodedIdOrSlug}` : `/content/${basePath}/${orgId}/${contentType}/${encodedIdOrSlug}`;
862
+ return lang ? client.get(endpoint, { lang }) : client.get(endpoint);
775
863
  },
776
864
  enabled: !!idOrSlug,
777
865
  staleTime: 1e3 * 60 * 5
@@ -785,6 +873,15 @@ function useContentItem(contentType, idOrSlug, options = {}) {
785
873
  refetch: query.refetch
786
874
  };
787
875
  }
876
+ function usePageContent(slug, options = {}) {
877
+ const state = useContentItem("pages", slug, options);
878
+ const reader = react.useMemo(() => createContentSlotReader(state.data), [state.data]);
879
+ return {
880
+ ...state,
881
+ page: state.data,
882
+ ...reader
883
+ };
884
+ }
788
885
  function useMySection(options = {}) {
789
886
  const { includeMembers = true } = options;
790
887
  const { isAuthenticated } = auth0React.useAuth0();
@@ -800,7 +897,7 @@ function useMySection(options = {}) {
800
897
  `/orgs/api/organizations/${orgId}/sections/${sectionId}`,
801
898
  { includeMembers: includeMembers ? "true" : void 0 }
802
899
  );
803
- return "success" in response && response.data ? response.data : response;
900
+ return "success" in response && response.success === true && response.data ? response.data : response;
804
901
  },
805
902
  enabled: isAuthenticated && !isUserLoading && !!sectionId,
806
903
  staleTime: 1e3 * 60 * 5
@@ -827,7 +924,7 @@ function useSections() {
827
924
  queryKey: ["axcloud", "sections", orgId, stage],
828
925
  queryFn: async () => {
829
926
  const response = await client.get(`/orgs/api/organizations/${orgId}/sections`);
830
- return "success" in response && response.data ? response.data : response;
927
+ return "success" in response && response.success === true && response.data ? response.data : response;
831
928
  },
832
929
  staleTime: 1e3 * 60 * 10
833
930
  // 10 minutes
@@ -1313,8 +1410,10 @@ exports.LogoutButton = LogoutButton;
1313
1410
  exports.ModuleGate = ModuleGate;
1314
1411
  exports.ProtectedRoute = ProtectedRoute;
1315
1412
  exports.createClient = createClient;
1413
+ exports.createContentSlotReader = createContentSlotReader;
1316
1414
  exports.createFormHelper = createFormHelper;
1317
1415
  exports.getClient = getClient;
1416
+ exports.getContentSlotMap = getContentSlotMap;
1318
1417
  exports.isAxCloudError = isAxCloudError;
1319
1418
  exports.submitForm = submitForm;
1320
1419
  exports.useAuth = useAuth;
@@ -1333,6 +1432,7 @@ exports.useModuleAccess = useModuleAccess;
1333
1432
  exports.useMySection = useMySection;
1334
1433
  exports.useNews = useNews;
1335
1434
  exports.useOrg = useOrg;
1435
+ exports.usePageContent = usePageContent;
1336
1436
  exports.useSections = useSections;
1337
1437
  exports.useUpdateFormSubmission = useUpdateFormSubmission;
1338
1438
  exports.useUser = useUser;