@axcode/axcloud-sdk 0.1.7-dev.87ea138 → 0.1.7-dev.cc5cc69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -144
- package/dist/index.d.mts +291 -92
- package/dist/index.d.ts +291 -92
- package/dist/index.js +808 -171
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +799 -174
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @axcode/axcloud-sdk
|
|
2
2
|
|
|
3
|
-
SDK for building websites with AxCloud CMS and member management.
|
|
3
|
+
SDK for building websites with AxCloud CMS and member management. AxCloud is
|
|
4
|
+
headless: it owns content, publication, access control and delivery APIs, while
|
|
5
|
+
the consuming application owns markup, layout and design.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -12,109 +14,116 @@ npm install @axcode/axcloud-sdk
|
|
|
12
14
|
|
|
13
15
|
The SDK is published with two tags for different environments:
|
|
14
16
|
|
|
15
|
-
| Tag
|
|
16
|
-
|
|
17
|
-
| `latest` | Production
|
|
18
|
-
| `next`
|
|
17
|
+
| Tag | Purpose | Stability | Install |
|
|
18
|
+
| -------- | ------------------- | ---------------------------------- | -------------------------------------- |
|
|
19
|
+
| `latest` | Production | Stable, tested | `npm install @axcode/axcloud-sdk` |
|
|
20
|
+
| `next` | Development/Staging | Preview, may have breaking changes | `npm install @axcode/axcloud-sdk@next` |
|
|
19
21
|
|
|
20
|
-
**Recommended setup:**
|
|
22
|
+
**Recommended production setup:**
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
}
|
|
24
|
+
Resolve `latest` deliberately, then commit the exact version and lockfile:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-exact @axcode/axcloud-sdk@latest
|
|
28
|
+
```
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
```json
|
|
31
31
|
{
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@axcode/axcloud-sdk": "
|
|
33
|
+
"@axcode/axcloud-sdk": "0.1.6"
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Do not install a moving tag in CI or during deployment. Test a specific `next`
|
|
39
|
+
version in a branch when needed, then promote and pin the stable release.
|
|
39
40
|
|
|
40
41
|
## Quick Start
|
|
41
42
|
|
|
42
43
|
### 1. Wrap your app with AxCloudProvider
|
|
43
44
|
|
|
44
45
|
```tsx
|
|
45
|
-
import { AxCloudProvider } from '@axcode/axcloud-sdk'
|
|
46
|
+
import { AxCloudProvider } from '@axcode/axcloud-sdk';
|
|
46
47
|
|
|
47
48
|
function App() {
|
|
48
49
|
return (
|
|
49
50
|
<AxCloudProvider
|
|
50
|
-
orgId="org_ba81807b"
|
|
51
|
+
orgId="org_ba81807b" // Your organization ID
|
|
51
52
|
apiUrl="https://api.axcloud.app"
|
|
52
|
-
apiKey="pk_live_..."
|
|
53
|
+
apiKey="pk_live_..." // Public API key from Settings → API Keys
|
|
53
54
|
auth0={{
|
|
54
|
-
domain:
|
|
55
|
-
clientId:
|
|
56
|
-
redirectUri: window.location.origin
|
|
55
|
+
domain: 'auth.axcloud.app',
|
|
56
|
+
clientId: 'your-client-id',
|
|
57
|
+
redirectUri: window.location.origin,
|
|
58
|
+
organization: 'org_auth0_...', // Optional Auth0 Organization ID for invite-only member sites
|
|
57
59
|
}}
|
|
58
60
|
>
|
|
59
61
|
<Router>
|
|
60
62
|
<Routes />
|
|
61
63
|
</Router>
|
|
62
64
|
</AxCloudProvider>
|
|
63
|
-
)
|
|
65
|
+
);
|
|
64
66
|
}
|
|
65
67
|
```
|
|
66
68
|
|
|
69
|
+
`orgId` is the AxCloud organization ID used by CMS APIs, for example `org_ba81807b`.
|
|
70
|
+
`auth0.organization` is the Auth0 Organization ID used during login, for example `org_o960yILn77d4vY6u`.
|
|
71
|
+
Set `auth0.organization` for invite-only customer member sites so Universal Login is scoped to that customer organization.
|
|
72
|
+
|
|
67
73
|
### 2. Fetch content
|
|
68
74
|
|
|
69
75
|
```tsx
|
|
70
|
-
import { useNews } from '@axcode/axcloud-sdk'
|
|
76
|
+
import { useNews } from '@axcode/axcloud-sdk';
|
|
71
77
|
|
|
72
78
|
function NewsPage() {
|
|
73
|
-
const { data: news, isLoading, isError } = useNews()
|
|
79
|
+
const { data: news, isLoading, isError } = useNews();
|
|
74
80
|
|
|
75
|
-
if (isLoading) return <Spinner
|
|
76
|
-
if (isError) return <Error
|
|
81
|
+
if (isLoading) return <Spinner />;
|
|
82
|
+
if (isError) return <Error />;
|
|
77
83
|
|
|
78
84
|
return (
|
|
79
85
|
<ul>
|
|
80
|
-
{news.map(item => (
|
|
86
|
+
{news.map((item) => (
|
|
81
87
|
<li key={item.id}>{item.title}</li>
|
|
82
88
|
))}
|
|
83
89
|
</ul>
|
|
84
|
-
)
|
|
90
|
+
);
|
|
85
91
|
}
|
|
86
92
|
```
|
|
87
93
|
|
|
88
|
-
> **Content
|
|
94
|
+
> **Content delivery:** These hooks return only published snapshots. When the
|
|
95
|
+
> user is authenticated, access to internal, member and section content is
|
|
96
|
+
> derived from the verified token. Anonymous users receive only public content.
|
|
97
|
+
> Client-supplied status or section identifiers never grant broader access.
|
|
89
98
|
|
|
90
99
|
Pages and news can be stored once in AxCloud with translations and requested per language:
|
|
91
100
|
|
|
92
101
|
```tsx
|
|
93
|
-
import { useContentItem, useNews } from '@axcode/axcloud-sdk'
|
|
102
|
+
import { useContentItem, useNews } from '@axcode/axcloud-sdk';
|
|
94
103
|
|
|
95
104
|
function AboutPage({ locale }: { locale: 'sv' | 'en' }) {
|
|
96
105
|
const { data: page } = useContentItem('pages', 'about-us', {
|
|
97
106
|
lang: locale,
|
|
98
107
|
publicOnly: true,
|
|
99
|
-
})
|
|
108
|
+
});
|
|
100
109
|
|
|
101
|
-
return <h1>{page?.title}</h1
|
|
110
|
+
return <h1>{page?.title}</h1>;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
function NewsList({ locale }: { locale: 'sv' | 'en' }) {
|
|
105
|
-
const { data: news } = useNews({ lang: locale })
|
|
114
|
+
const { data: news } = useNews({ lang: locale });
|
|
106
115
|
|
|
107
|
-
return news.map(item => <article key={item.id}>{item.title}</article>)
|
|
116
|
+
return news.map((item) => <article key={item.id}>{item.title}</article>);
|
|
108
117
|
}
|
|
109
118
|
```
|
|
110
119
|
|
|
111
120
|
### 3. Add authentication
|
|
112
121
|
|
|
113
122
|
```tsx
|
|
114
|
-
import { useAuth, LoginButton, LogoutButton, ProtectedRoute } from '@axcode/axcloud-sdk'
|
|
123
|
+
import { useAuth, LoginButton, LogoutButton, ProtectedRoute } from '@axcode/axcloud-sdk';
|
|
115
124
|
|
|
116
125
|
function Header() {
|
|
117
|
-
const { isAuthenticated, user } = useAuth()
|
|
126
|
+
const { isAuthenticated, user } = useAuth();
|
|
118
127
|
|
|
119
128
|
return (
|
|
120
129
|
<header>
|
|
@@ -127,7 +136,7 @@ function Header() {
|
|
|
127
136
|
<LoginButton>Sign In</LoginButton>
|
|
128
137
|
)}
|
|
129
138
|
</header>
|
|
130
|
-
)
|
|
139
|
+
);
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
function MemberArea() {
|
|
@@ -135,87 +144,150 @@ function MemberArea() {
|
|
|
135
144
|
<ProtectedRoute>
|
|
136
145
|
<MemberContent />
|
|
137
146
|
</ProtectedRoute>
|
|
138
|
-
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The SDK keeps verification UI inside the product site. Render your own branded
|
|
152
|
+
notice when `needsEmailVerification` is true:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
const { needsEmailVerification, requestEmailVerification, refreshUser } = useAuth();
|
|
156
|
+
|
|
157
|
+
if (needsEmailVerification) {
|
|
158
|
+
// Render the product's own verification notice.
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await requestEmailVerification({ returnTo: window.location.href });
|
|
162
|
+
await refreshUser();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The API resolves the recipient and organization from the authenticated token.
|
|
166
|
+
Return URLs are restricted to the organization's configured member site.
|
|
167
|
+
|
|
168
|
+
### 3b. Add privacy-safe analytics
|
|
169
|
+
|
|
170
|
+
Analytics is optional and provider-independent. AxCloud resolves the public
|
|
171
|
+
tracker configuration from the organization, site key, deployment stage, and
|
|
172
|
+
current hostname. SDK consumers never receive the Umami API key.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { AxCloudProvider, useAnalytics } from '@axcode/axcloud-sdk';
|
|
176
|
+
|
|
177
|
+
<AxCloudProvider
|
|
178
|
+
orgId="org_ba81807b"
|
|
179
|
+
apiUrl="https://api.axcloud.app"
|
|
180
|
+
stage="prod"
|
|
181
|
+
analytics={{
|
|
182
|
+
enabled: analyticsConsent === 'accepted',
|
|
183
|
+
siteKey: 'member-site',
|
|
184
|
+
normalizePath: (path) => path.replace(/^\/member\/news\/[^/]+/, '/member/news/:id'),
|
|
185
|
+
}}
|
|
186
|
+
auth0={auth0Config}
|
|
187
|
+
>
|
|
188
|
+
<App />
|
|
189
|
+
</AxCloudProvider>;
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The SDK tracks SPA page changes after the tracker is ready. Query strings and
|
|
193
|
+
fragments are removed, common opaque IDs are replaced with `:id`, Do Not Track
|
|
194
|
+
is honored, and localhost collection is disabled unless `trackLocalhost: true`
|
|
195
|
+
is explicitly set.
|
|
196
|
+
|
|
197
|
+
Custom events must be allowlisted in the AxCloud analytics site configuration:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
function NewsArticle() {
|
|
201
|
+
const analytics = useAnalytics();
|
|
202
|
+
|
|
203
|
+
const handleOpen = () => {
|
|
204
|
+
analytics.track('news_opened', { surface: 'member_news' });
|
|
205
|
+
};
|
|
139
206
|
}
|
|
140
207
|
```
|
|
141
208
|
|
|
209
|
+
Only send coarse enums, booleans, and counts. The SDK rejects sensitive
|
|
210
|
+
property names and email-like values, but applications remain responsible for
|
|
211
|
+
never supplying names, email addresses, user IDs, document titles, search text,
|
|
212
|
+
chat text, or other personal/content data. Production trackers should only be
|
|
213
|
+
enabled after the product's privacy notice and Content Security Policy have
|
|
214
|
+
been reviewed.
|
|
215
|
+
|
|
142
216
|
### 4. Use sections
|
|
143
217
|
|
|
144
218
|
```tsx
|
|
145
|
-
import { useMySection } from '@axcode/axcloud-sdk'
|
|
219
|
+
import { useMySection } from '@axcode/axcloud-sdk';
|
|
146
220
|
|
|
147
221
|
function MySectionPage() {
|
|
148
|
-
const { section, members, isLoading } = useMySection()
|
|
222
|
+
const { section, members, isLoading } = useMySection();
|
|
149
223
|
|
|
150
|
-
if (isLoading) return <Spinner
|
|
151
|
-
if (!section) return <p>You are not assigned to a section</p
|
|
224
|
+
if (isLoading) return <Spinner />;
|
|
225
|
+
if (!section) return <p>You are not assigned to a section</p>;
|
|
152
226
|
|
|
153
227
|
return (
|
|
154
228
|
<div>
|
|
155
229
|
<h1>{section.name}</h1>
|
|
156
230
|
<h2>Members ({members.length})</h2>
|
|
157
231
|
<ul>
|
|
158
|
-
{members.map(m =>
|
|
232
|
+
{members.map((m) => (
|
|
233
|
+
<li key={m.id}>{m.name}</li>
|
|
234
|
+
))}
|
|
159
235
|
</ul>
|
|
160
236
|
</div>
|
|
161
|
-
)
|
|
237
|
+
);
|
|
162
238
|
}
|
|
163
239
|
```
|
|
164
240
|
|
|
165
|
-
### 4b.
|
|
241
|
+
### 4b. Section-scoped content
|
|
166
242
|
|
|
167
243
|
```tsx
|
|
168
|
-
import { useNews, useMySection } from '@axcode/axcloud-sdk'
|
|
244
|
+
import { useNews, useMySection } from '@axcode/axcloud-sdk';
|
|
169
245
|
|
|
170
246
|
function SectionNews() {
|
|
171
|
-
const {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const { data: sectionNews } = useNews({ sectionId })
|
|
175
|
-
|
|
176
|
-
// Or fetch all organization news
|
|
177
|
-
const { data: allNews } = useNews()
|
|
247
|
+
const { section } = useMySection();
|
|
248
|
+
// AxCloud derives the allowed sections from the authenticated membership.
|
|
249
|
+
const { data: visibleNews } = useNews();
|
|
178
250
|
|
|
179
251
|
return (
|
|
180
252
|
<div>
|
|
181
253
|
<h2>Section News</h2>
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
{
|
|
254
|
+
{visibleNews.map((item) => (
|
|
255
|
+
<NewsCard key={item.id} item={item} />
|
|
256
|
+
))}
|
|
257
|
+
{!section && <p>You are not assigned to a section.</p>}
|
|
186
258
|
</div>
|
|
187
|
-
)
|
|
259
|
+
);
|
|
188
260
|
}
|
|
189
261
|
```
|
|
190
262
|
|
|
191
263
|
### 5. Fetch organization branding and media
|
|
192
264
|
|
|
193
265
|
```tsx
|
|
194
|
-
import { useBranding, useLogo, useMedia } from '@axcode/axcloud-sdk'
|
|
266
|
+
import { useBranding, useLogo, useMedia } from '@axcode/axcloud-sdk';
|
|
195
267
|
|
|
196
268
|
function Header() {
|
|
197
|
-
const { logoUrl, branding, organizationName } = useBranding()
|
|
269
|
+
const { logoUrl, branding, organizationName } = useBranding();
|
|
198
270
|
|
|
199
271
|
return (
|
|
200
272
|
<header style={{ backgroundColor: branding?.primaryColor }}>
|
|
201
273
|
{logoUrl && <img src={logoUrl} alt={organizationName} />}
|
|
202
274
|
<h1>{organizationName}</h1>
|
|
203
275
|
</header>
|
|
204
|
-
)
|
|
276
|
+
);
|
|
205
277
|
}
|
|
206
278
|
|
|
207
279
|
// Or just the logo
|
|
208
280
|
function Logo() {
|
|
209
|
-
const { logoUrl, isLoading } = useLogo()
|
|
281
|
+
const { logoUrl, isLoading } = useLogo();
|
|
210
282
|
|
|
211
|
-
if (isLoading) return <Skeleton
|
|
212
|
-
return logoUrl ? <img src={logoUrl} alt="Logo" /> : <DefaultLogo
|
|
283
|
+
if (isLoading) return <Skeleton />;
|
|
284
|
+
return logoUrl ? <img src={logoUrl} alt="Logo" /> : <DefaultLogo />;
|
|
213
285
|
}
|
|
214
286
|
|
|
215
287
|
// Fetch specific media file
|
|
216
288
|
function HeroImage() {
|
|
217
|
-
const { mediaUrl } = useMedia('hero-banner.jpg')
|
|
218
|
-
return mediaUrl ? <img src={mediaUrl} alt="Hero" /> : null
|
|
289
|
+
const { mediaUrl } = useMedia('hero-banner.jpg');
|
|
290
|
+
return mediaUrl ? <img src={mediaUrl} alt="Hero" /> : null;
|
|
219
291
|
}
|
|
220
292
|
```
|
|
221
293
|
|
|
@@ -224,26 +296,26 @@ function HeroImage() {
|
|
|
224
296
|
### 6. Fetch public documents
|
|
225
297
|
|
|
226
298
|
```tsx
|
|
227
|
-
import { useDocuments, useDocumentDownload } from '@axcode/axcloud-sdk'
|
|
299
|
+
import { useDocuments, useDocumentDownload } from '@axcode/axcloud-sdk';
|
|
228
300
|
|
|
229
301
|
function PublicDocuments() {
|
|
230
|
-
const { documents, isLoading } = useDocuments({ tags: ['handbook'], limit: 20 })
|
|
302
|
+
const { documents, isLoading } = useDocuments({ tags: ['handbook'], limit: 20 });
|
|
231
303
|
|
|
232
|
-
if (isLoading) return <Skeleton
|
|
304
|
+
if (isLoading) return <Skeleton />;
|
|
233
305
|
return (
|
|
234
306
|
<ul>
|
|
235
|
-
{documents.map(doc => (
|
|
307
|
+
{documents.map((doc) => (
|
|
236
308
|
<li key={doc.document_id}>
|
|
237
309
|
<DocumentLink documentId={doc.document_id} title={doc.title} />
|
|
238
310
|
</li>
|
|
239
311
|
))}
|
|
240
312
|
</ul>
|
|
241
|
-
)
|
|
313
|
+
);
|
|
242
314
|
}
|
|
243
315
|
|
|
244
316
|
function DocumentLink({ documentId, title }: { documentId: string; title: string }) {
|
|
245
|
-
const { documentUrl } = useDocumentDownload(documentId)
|
|
246
|
-
return documentUrl ? <a href={documentUrl}>{title}</a> : <span>{title}</span
|
|
317
|
+
const { documentUrl } = useDocumentDownload(documentId);
|
|
318
|
+
return documentUrl ? <a href={documentUrl}>{title}</a> : <span>{title}</span>;
|
|
247
319
|
}
|
|
248
320
|
```
|
|
249
321
|
|
|
@@ -252,10 +324,10 @@ Only documents with `status: "published"` and `audience: "public"` are exposed t
|
|
|
252
324
|
### 7. Gate features by module
|
|
253
325
|
|
|
254
326
|
```tsx
|
|
255
|
-
import { ModuleGate, useOrg } from '@axcode/axcloud-sdk'
|
|
327
|
+
import { ModuleGate, useOrg } from '@axcode/axcloud-sdk';
|
|
256
328
|
|
|
257
329
|
function Dashboard() {
|
|
258
|
-
const { hasModule } = useOrg()
|
|
330
|
+
const { hasModule } = useOrg();
|
|
259
331
|
|
|
260
332
|
return (
|
|
261
333
|
<div>
|
|
@@ -267,7 +339,7 @@ function Dashboard() {
|
|
|
267
339
|
|
|
268
340
|
{hasModule('ai') && <AIChatWidget />}
|
|
269
341
|
</div>
|
|
270
|
-
)
|
|
342
|
+
);
|
|
271
343
|
}
|
|
272
344
|
```
|
|
273
345
|
|
|
@@ -275,85 +347,134 @@ function Dashboard() {
|
|
|
275
347
|
|
|
276
348
|
### Provider
|
|
277
349
|
|
|
278
|
-
| Component
|
|
279
|
-
|
|
350
|
+
| Component | Description |
|
|
351
|
+
| ----------------- | --------------------------------------------------------------- |
|
|
280
352
|
| `AxCloudProvider` | Main provider that combines Auth0, React Query, and SDK context |
|
|
281
353
|
|
|
282
354
|
### Hooks
|
|
283
355
|
|
|
284
|
-
| Hook
|
|
285
|
-
|
|
286
|
-
| `useAuth()`
|
|
287
|
-
| `useUser()`
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
356
|
+
| Hook | Description |
|
|
357
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
358
|
+
| `useAuth()` | Login/logout, email-verification state, resend, and profile refresh |
|
|
359
|
+
| `useUser()` | Fetch the normalized user and verified organization membership |
|
|
360
|
+
| `useUpdateMyProfile()` | Update the signed-in user's name, phone, and address |
|
|
361
|
+
| `useOrg()` | Fetch organization info and check modules |
|
|
362
|
+
| `useNews(options?)` | Fetch the published news collection visible to the current visitor |
|
|
363
|
+
| `useContent(type, options?)` | Fetch a published `news`, `pages`, or `events` collection |
|
|
364
|
+
| `useContentItem(type, id, opts?)` | Fetch one published item directly by ID or slug |
|
|
365
|
+
| `usePageContent(slug, options?)` | Fetch a page and read structured template slots |
|
|
366
|
+
| `useMySection()` | Fetch current user's section with members |
|
|
367
|
+
| `useSections()` | Fetch all organization sections |
|
|
368
|
+
| `useBranding()` | Fetch organization branding (logo, colors) |
|
|
369
|
+
| `useLogo()` | Fetch organization logo URL |
|
|
370
|
+
| `useMedia(mediaId)` | Fetch specific media file URL |
|
|
371
|
+
| `useAvatar(userId)` | Fetch user avatar URL |
|
|
372
|
+
| `useMediaList(type)` | List media files |
|
|
373
|
+
| `useDocuments(options?)` | List published public documents |
|
|
374
|
+
| `useDocumentDownload(documentId)` | Fetch a signed document download URL |
|
|
375
|
+
|
|
376
|
+
Profile self-service is deliberately limited to personal contact fields. Organization title,
|
|
377
|
+
email, roles, status, organization, and section assignments remain administrator-managed.
|
|
378
|
+
|
|
379
|
+
`useUser()` normalizes the API response to camelCase. Organization-specific values always come
|
|
380
|
+
from the membership for the provider's `orgId`:
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
const {
|
|
384
|
+
user,
|
|
385
|
+
roleInOrg,
|
|
386
|
+
title,
|
|
387
|
+
section, // { id, name, role } | null
|
|
388
|
+
sections, // all verified section memberships
|
|
389
|
+
sectionId,
|
|
390
|
+
sectionIds,
|
|
391
|
+
memberSince, // normalized from joined_at
|
|
392
|
+
} = useUser();
|
|
393
|
+
|
|
394
|
+
return <p>{section?.name ?? 'No section'} · member since {memberSince}</p>;
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
import { useUpdateMyProfile } from '@axcode/axcloud-sdk';
|
|
399
|
+
|
|
400
|
+
function ProfileForm() {
|
|
401
|
+
const updateProfile = useUpdateMyProfile();
|
|
402
|
+
|
|
403
|
+
const save = () => updateProfile.mutateAsync({
|
|
404
|
+
firstName: 'Anna',
|
|
405
|
+
lastName: 'Pilot',
|
|
406
|
+
phone: '+46701234567',
|
|
407
|
+
address: 'Flyggatan 1',
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return <button onClick={save} disabled={updateProfile.isPending}>Save profile</button>;
|
|
411
|
+
}
|
|
412
|
+
```
|
|
300
413
|
|
|
301
414
|
### Content Query Options
|
|
302
415
|
|
|
303
416
|
```typescript
|
|
304
417
|
interface ContentQueryOptions {
|
|
305
|
-
page?: number
|
|
306
|
-
pageSize?: number
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
sortOrder?: 'asc' | 'desc'
|
|
314
|
-
lang?: string // Return translated title/slug/blocks when available
|
|
315
|
-
publicOnly?: boolean // Force public endpoint even when authenticated
|
|
418
|
+
page?: number; // Page number (default: 1)
|
|
419
|
+
pageSize?: number; // Items per page (default: 20)
|
|
420
|
+
search?: string; // Search in title/content
|
|
421
|
+
sortBy?: 'publishedAt' | 'createdAt' | 'updatedAt' | 'title';
|
|
422
|
+
sortOrder?: 'asc' | 'desc';
|
|
423
|
+
tags?: string[];
|
|
424
|
+
lang?: string; // Return translated title/slug/blocks when available
|
|
425
|
+
publicOnly?: boolean; // Force public endpoint even when authenticated
|
|
316
426
|
}
|
|
317
427
|
```
|
|
318
428
|
|
|
429
|
+
Collection hooks return `data`, `count`, `total`, `page`, `pageSize`,
|
|
430
|
+
`hasMore`, `nextCursor`, `isLoading`, `isFetching`, `isError`, `error` and
|
|
431
|
+
`refetch`. During Auth0's initial session check, member-capable hooks wait
|
|
432
|
+
instead of briefly requesting public content. Member caches are isolated per
|
|
433
|
+
Auth0 subject and removed on logout or account change.
|
|
434
|
+
All other identity-bound AxCloud query data is removed at the same boundary.
|
|
435
|
+
|
|
436
|
+
Structured slots are untrusted CMS input. Prefer `text()`, `list()` and
|
|
437
|
+
`image()`. `unsafeHtml()` (and the deprecated `html()` alias) must be sanitized
|
|
438
|
+
before use with `dangerouslySetInnerHTML`.
|
|
439
|
+
|
|
319
440
|
### Components
|
|
320
441
|
|
|
321
|
-
| Component
|
|
322
|
-
|
|
323
|
-
| `<LoginButton>`
|
|
324
|
-
| `<LogoutButton>`
|
|
325
|
-
| `<ProtectedRoute>` | Require authentication to view content
|
|
326
|
-
| `<ModuleGate>`
|
|
442
|
+
| Component | Description |
|
|
443
|
+
| ------------------ | --------------------------------------------- |
|
|
444
|
+
| `<LoginButton>` | Trigger Auth0 login |
|
|
445
|
+
| `<LogoutButton>` | Trigger logout |
|
|
446
|
+
| `<ProtectedRoute>` | Require authentication to view content |
|
|
447
|
+
| `<ModuleGate>` | Conditionally render based on enabled modules |
|
|
327
448
|
|
|
328
449
|
## Advanced Usage
|
|
329
450
|
|
|
330
451
|
### Custom API calls
|
|
331
452
|
|
|
332
453
|
```tsx
|
|
333
|
-
import { useAuth } from '@axcode/axcloud-sdk'
|
|
454
|
+
import { useAuth } from '@axcode/axcloud-sdk';
|
|
334
455
|
|
|
335
456
|
function MyComponent() {
|
|
336
|
-
const { getAccessToken } = useAuth()
|
|
457
|
+
const { getAccessToken } = useAuth();
|
|
337
458
|
|
|
338
459
|
const fetchCustomData = async () => {
|
|
339
|
-
const token = await getAccessToken()
|
|
460
|
+
const token = await getAccessToken();
|
|
340
461
|
const response = await fetch('/api/custom', {
|
|
341
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
342
|
-
})
|
|
343
|
-
return response.json()
|
|
344
|
-
}
|
|
462
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
463
|
+
});
|
|
464
|
+
return response.json();
|
|
465
|
+
};
|
|
345
466
|
}
|
|
346
467
|
```
|
|
347
468
|
|
|
348
469
|
### Access the underlying Auth0 client
|
|
349
470
|
|
|
350
471
|
```tsx
|
|
351
|
-
const { getAuth0Client } = useAuth()
|
|
352
|
-
const auth0 = getAuth0Client()
|
|
472
|
+
const { getAuth0Client } = useAuth();
|
|
473
|
+
const auth0 = getAuth0Client();
|
|
353
474
|
|
|
354
475
|
// Full Auth0 API available
|
|
355
|
-
await auth0.getAccessTokenSilently()
|
|
356
|
-
await auth0.getIdTokenClaims()
|
|
476
|
+
await auth0.getAccessTokenSilently();
|
|
477
|
+
await auth0.getIdTokenClaims();
|
|
357
478
|
```
|
|
358
479
|
|
|
359
480
|
## Security Model
|
|
@@ -362,58 +483,58 @@ await auth0.getIdTokenClaims()
|
|
|
362
483
|
|
|
363
484
|
The SDK uses two types of credentials:
|
|
364
485
|
|
|
365
|
-
1. **API Key** (`apiKey` prop): A
|
|
486
|
+
1. **API Key** (`apiKey` prop): A _public identifier_ for your organization
|
|
366
487
|
- Safe to include in client-side code
|
|
367
488
|
- Used for public content endpoints
|
|
368
489
|
- Backend validates and rate-limits based on API key
|
|
369
490
|
- Without a valid API key, requests return 401
|
|
370
491
|
|
|
371
|
-
2. **Authentication Token** (via Auth0): A
|
|
492
|
+
2. **Authentication Token** (via Auth0): A _secret_ JWT token
|
|
372
493
|
- Automatically managed by the SDK
|
|
373
494
|
- Required for member-only content and user data
|
|
374
495
|
- Never exposed in client code
|
|
375
496
|
|
|
376
497
|
### What the API Key Protects
|
|
377
498
|
|
|
378
|
-
| Without API Key | With API Key
|
|
379
|
-
|
|
380
|
-
| Nothing
|
|
381
|
-
|
|
|
382
|
-
|
|
|
383
|
-
|
|
|
499
|
+
| Without API Key | With API Key | With Auth Token |
|
|
500
|
+
| --------------- | --------------------- | --------------- |
|
|
501
|
+
| Nothing | Public news/content | Member content |
|
|
502
|
+
| | Organization branding | User profile |
|
|
503
|
+
| | Public media | Linked accounts |
|
|
504
|
+
| | | Admin functions |
|
|
384
505
|
|
|
385
506
|
### Endpoint Security
|
|
386
507
|
|
|
387
|
-
| Endpoint Type
|
|
388
|
-
|
|
389
|
-
| Public content | API key (x-api-key header) | `/content/public/{orgId}/news`
|
|
390
|
-
| Member content | JWT Bearer token
|
|
391
|
-
| User data
|
|
392
|
-
| Admin
|
|
508
|
+
| Endpoint Type | Authentication | Example |
|
|
509
|
+
| -------------- | -------------------------- | ------------------------------- |
|
|
510
|
+
| Public content | API key (x-api-key header) | `/content/public/{orgId}/news` |
|
|
511
|
+
| Member content | JWT Bearer token | `/content/members/{orgId}/news` |
|
|
512
|
+
| User data | JWT Bearer token | `/api/users/me` |
|
|
513
|
+
| Admin | JWT Bearer token + role | `/api/admin/forms/submissions` |
|
|
393
514
|
|
|
394
515
|
## Error Handling
|
|
395
516
|
|
|
396
517
|
```tsx
|
|
397
|
-
import { useNews, isAxCloudError } from '@axcode/axcloud-sdk'
|
|
518
|
+
import { useNews, isAxCloudError } from '@axcode/axcloud-sdk';
|
|
398
519
|
|
|
399
520
|
function NewsPage() {
|
|
400
|
-
const { data, error, isError } = useNews()
|
|
521
|
+
const { data, error, isError } = useNews();
|
|
401
522
|
|
|
402
523
|
if (isError) {
|
|
403
524
|
if (isAxCloudError(error)) {
|
|
404
525
|
switch (error.code) {
|
|
405
526
|
case 'UNAUTHORIZED':
|
|
406
|
-
return <LoginPrompt
|
|
527
|
+
return <LoginPrompt />;
|
|
407
528
|
case 'NOT_FOUND':
|
|
408
|
-
return <NotFound
|
|
529
|
+
return <NotFound />;
|
|
409
530
|
default:
|
|
410
|
-
return <Error message={error.message}
|
|
531
|
+
return <Error message={error.message} />;
|
|
411
532
|
}
|
|
412
533
|
}
|
|
413
|
-
return <Error message="Something went wrong"
|
|
534
|
+
return <Error message="Something went wrong" />;
|
|
414
535
|
}
|
|
415
536
|
|
|
416
|
-
return <NewsList items={data}
|
|
537
|
+
return <NewsList items={data} />;
|
|
417
538
|
}
|
|
418
539
|
```
|
|
419
540
|
|