@utilsy/cms-nextjs 0.3.0 → 0.5.0
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 +134 -4
- package/dist/{client-Ba-p3TtH.d.cts → client-awWyf-VL.d.cts} +64 -3
- package/dist/{client-Ba-p3TtH.d.ts → client-awWyf-VL.d.ts} +64 -3
- package/dist/index.cjs +146 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -3
- package/dist/index.d.ts +109 -3
- package/dist/index.js +135 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +119 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +33 -2
- package/dist/react.d.ts +33 -2
- package/dist/react.js +115 -1
- package/dist/react.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -164,7 +164,68 @@ export function ContactForm() {
|
|
|
164
164
|
|
|
165
165
|
Optional honeypot: include `_honeypot` in `data`; submissions with a non-empty value are rejected.
|
|
166
166
|
|
|
167
|
-
### 6.
|
|
167
|
+
### 6. Newsletter (subscribe, unsubscribe, preferences)
|
|
168
|
+
|
|
169
|
+
Use your own signup / preference-center UI—no hosted form embed.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// app/actions/newsletter.ts
|
|
173
|
+
"use server";
|
|
174
|
+
|
|
175
|
+
import { cms } from "@/lib/cms";
|
|
176
|
+
|
|
177
|
+
export async function subscribeNewsletter(email: string) {
|
|
178
|
+
return cms.newsletter.subscribe({ email, source: "footer" });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function unsubscribeNewsletter(email: string) {
|
|
182
|
+
return cms.newsletter.unsubscribe({ email });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function updateNewsletterPrefs(
|
|
186
|
+
token: string,
|
|
187
|
+
customFields: Record<string, unknown>,
|
|
188
|
+
) {
|
|
189
|
+
return cms.newsletter.updatePreferences({ token, customFields });
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
"use client";
|
|
195
|
+
|
|
196
|
+
import { CmsProvider, useNewsletterSubscribe } from "@utilsy/cms-nextjs/react";
|
|
197
|
+
import { cms } from "@/lib/cms";
|
|
198
|
+
|
|
199
|
+
export function NewsletterSignup() {
|
|
200
|
+
return (
|
|
201
|
+
<CmsProvider client={cms}>
|
|
202
|
+
<NewsletterSignupInner />
|
|
203
|
+
</CmsProvider>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function NewsletterSignupInner() {
|
|
208
|
+
const { subscribe, submitting, error } = useNewsletterSubscribe();
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<form
|
|
212
|
+
onSubmit={async (e) => {
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
const fd = new FormData(e.currentTarget);
|
|
215
|
+
await subscribe({ email: String(fd.get("email") ?? "") });
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
<input name="email" type="email" required />
|
|
219
|
+
<button type="submit" disabled={submitting}>Subscribe</button>
|
|
220
|
+
{error && <p>{error.message}</p>}
|
|
221
|
+
</form>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Confirmation links from email hit `GET /public/newsletter/confirm/:token` (no `siteId` required on the server; the SDK still sends `siteId` when configured).
|
|
227
|
+
|
|
228
|
+
### 7. Client content list (hooks)
|
|
168
229
|
|
|
169
230
|
```tsx
|
|
170
231
|
"use client";
|
|
@@ -223,6 +284,41 @@ For local dev or gateway-only deployments, always set `siteId` in the client con
|
|
|
223
284
|
- `filters` are exact matches on `data.<field>` (JSON query param). Example: `{ category: "news" }`.
|
|
224
285
|
- Use `contentTypeApiId` (kebab-case slug from CMS), not the Mongo content type id.
|
|
225
286
|
|
|
287
|
+
### COMPONENT and DYNAMIC_ZONE fields
|
|
288
|
+
|
|
289
|
+
Published entry `data` may include nested structures from the CMS builder:
|
|
290
|
+
|
|
291
|
+
| Field type | Shape in `entry.data` |
|
|
292
|
+
|------------|------------------------|
|
|
293
|
+
| **COMPONENT** (single) | `{ title: "…", … }` |
|
|
294
|
+
| **COMPONENT** (repeatable) | `[{ … }, { … }]` |
|
|
295
|
+
| **DYNAMIC_ZONE** | `[{ __component: "<apiId>", …fields }, …]` |
|
|
296
|
+
|
|
297
|
+
Helpers (no extra API calls):
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import {
|
|
301
|
+
getComponentItems,
|
|
302
|
+
getDynamicZoneBlocks,
|
|
303
|
+
filterDynamicZoneByComponent,
|
|
304
|
+
parseContentTypeFields,
|
|
305
|
+
} from "@utilsy/cms-nextjs";
|
|
306
|
+
|
|
307
|
+
const entry = await cms.content.getMappedEntry("landing", "home");
|
|
308
|
+
if (!entry) return;
|
|
309
|
+
|
|
310
|
+
const slides = getComponentItems(entry, "slides");
|
|
311
|
+
const ctas = filterDynamicZoneByComponent(
|
|
312
|
+
getDynamicZoneBlocks(entry, "blocks"),
|
|
313
|
+
"cta",
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Optional: inspect schema from populated contentType on the entry DTO
|
|
317
|
+
const fields = parseContentTypeFields(entry.contentType?.fields);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Types: `FieldType`, `ContentTypeField`, `DynamicZoneBlock`, `ContentTypeCategory`.
|
|
321
|
+
|
|
226
322
|
## API reference
|
|
227
323
|
|
|
228
324
|
### Client (`createCmsClient`)
|
|
@@ -251,10 +347,25 @@ For local dev or gateway-only deployments, always set `siteId` in the client con
|
|
|
251
347
|
| `content.getEntry(contentTypeApiId, idOrSlug, init?)` | Single raw DTO |
|
|
252
348
|
| `content.listMappedEntries(...)` | List + `mapCmsEntryToContentEntry` |
|
|
253
349
|
| `content.getMappedEntry(...)` | Single mapped `ContentEntry` |
|
|
350
|
+
| `content.getSingleMappedEntry(...)` | First mapped entry for SINGLE types (`limit=1`) |
|
|
351
|
+
|
|
352
|
+
Query params for `listEntries` / `listMappedEntries`: `page`, `limit`, `search`, `sort`, `order`, `filters` (object). For **SINGLE** content types the public API caps `limit` at 1 and may include `category: "SINGLE"` and `maxEntries: 1` in the list response.
|
|
353
|
+
|
|
354
|
+
#### Single content types
|
|
355
|
+
|
|
356
|
+
Use a CMS content type with category **SINGLE** for one document per site (homepage settings, global SEO, etc.). Admin creates the type and edits the auto-created draft on the **Content** tab.
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
// Prefer for singleton types — no entry id/slug required
|
|
360
|
+
const settings = await client.content.getSingleMappedEntry("site-settings");
|
|
254
361
|
|
|
255
|
-
|
|
362
|
+
// Or with React
|
|
363
|
+
const { entry, loading } = useSingleContentEntry("site-settings");
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`listEntries` / `listMappedEntries` still work; pass `limit: 1` or use `getSingleMappedEntry`.
|
|
256
367
|
|
|
257
|
-
Helpers: `serializeContentFilters`, `mapCmsEntryToContentEntry`.
|
|
368
|
+
Helpers: `serializeContentFilters`, `mapCmsEntryToContentEntry`, `getComponentItems`, `getDynamicZoneBlocks`, `filterDynamicZoneByComponent`, `parseContentTypeFields`.
|
|
258
369
|
|
|
259
370
|
#### Leads
|
|
260
371
|
|
|
@@ -266,6 +377,20 @@ Helpers: `serializeContentFilters`, `mapCmsEntryToContentEntry`.
|
|
|
266
377
|
- Content type must be `LEAD_CAPTURE` and `apiAccess: PUBLIC`
|
|
267
378
|
- Field keys in `data` must match the content type schema; CRM mapping uses `leadCaptureConfig` in admin
|
|
268
379
|
|
|
380
|
+
#### Newsletter
|
|
381
|
+
|
|
382
|
+
| Method | Description |
|
|
383
|
+
|--------|-------------|
|
|
384
|
+
| `newsletter.subscribe(input, init?)` | Subscribe by email; may send confirmation email |
|
|
385
|
+
| `newsletter.unsubscribe(input, init?)` | Unsubscribe by `token` (from email) or `email` |
|
|
386
|
+
| `newsletter.confirm(token, init?)` | Confirm subscription from email link |
|
|
387
|
+
| `newsletter.updatePreferences(input, init?)` | Update `customFields`, `tags`, and/or `listId` |
|
|
388
|
+
|
|
389
|
+
- Base path: `/public/newsletter` (plus optional `pathPrefix`)
|
|
390
|
+
- `subscribe`: `email` required; optional `source`, `listId`, `customFields`
|
|
391
|
+
- `unsubscribe` / `updatePreferences`: provide `token` or `email`
|
|
392
|
+
- `updatePreferences`: at least one of `customFields`, `tags`, or `listId`; optional `tagsAction` (`set` \| `add` \| `remove`)
|
|
393
|
+
|
|
269
394
|
### React (`@utilsy/cms-nextjs/react`)
|
|
270
395
|
|
|
271
396
|
| Export | Description |
|
|
@@ -278,7 +403,12 @@ Helpers: `serializeContentFilters`, `mapCmsEntryToContentEntry`.
|
|
|
278
403
|
| `useBlogComments` | List + submit comments |
|
|
279
404
|
| `useContentEntries` | List mapped entries by content type |
|
|
280
405
|
| `useContentEntry` | Single mapped entry by id or slug |
|
|
406
|
+
| `useSingleContentEntry` | Mapped entry for SINGLE types (no id/slug) |
|
|
281
407
|
| `useSubmitLead` | Submit lead from client (prefer Server Action for forms) |
|
|
408
|
+
| `useNewsletterSubscribe` | Subscribe from client (prefer Server Action) |
|
|
409
|
+
| `useNewsletterUnsubscribe` | Unsubscribe by token or email |
|
|
410
|
+
| `useNewsletterUpdatePreferences` | Update subscriber preferences |
|
|
411
|
+
| `useNewsletterConfirm` | Confirm subscription token (e.g. preference page) |
|
|
282
412
|
|
|
283
413
|
## CORS
|
|
284
414
|
|
|
@@ -287,7 +417,7 @@ Public CMS reads from the browser require either:
|
|
|
287
417
|
- Same-origin proxy (e.g. Next.js Route Handler forwarding to gateway), or
|
|
288
418
|
- Server Components / Route Handlers calling the SDK server-side.
|
|
289
419
|
|
|
290
|
-
Mutations (comments, likes, lead submit) should run server-side (Server Action / Route Handler) or via a same-origin proxy with CORS configured on the gateway.
|
|
420
|
+
Mutations (comments, likes, lead submit, newsletter subscribe/unsubscribe/preferences) should run server-side (Server Action / Route Handler) or via a same-origin proxy with CORS configured on the gateway.
|
|
291
421
|
|
|
292
422
|
## License
|
|
293
423
|
|
|
@@ -181,6 +181,8 @@ type CmsContentTypeDto = {
|
|
|
181
181
|
id?: string;
|
|
182
182
|
name?: string;
|
|
183
183
|
apiId?: string;
|
|
184
|
+
/** COLLECTION (many entries), SINGLE (one entry), or COMPONENT (embedded schema only). */
|
|
185
|
+
category?: string;
|
|
184
186
|
fields?: unknown[];
|
|
185
187
|
status?: string;
|
|
186
188
|
};
|
|
@@ -199,11 +201,15 @@ type CmsContentEntriesPage = {
|
|
|
199
201
|
message?: string;
|
|
200
202
|
docs: CmsContentEntryDto[];
|
|
201
203
|
totalDocs: number;
|
|
204
|
+
/** Present when the content type category is SINGLE (at most one entry per site). */
|
|
205
|
+
category?: string;
|
|
206
|
+
maxEntries?: number;
|
|
202
207
|
};
|
|
203
208
|
type ContentTypeMeta = {
|
|
204
209
|
id: string;
|
|
205
210
|
name: string;
|
|
206
211
|
apiId: string;
|
|
212
|
+
category?: string;
|
|
207
213
|
fields?: unknown[];
|
|
208
214
|
status?: string;
|
|
209
215
|
};
|
|
@@ -219,6 +225,8 @@ type ContentEntry = {
|
|
|
219
225
|
type MappedContentEntriesPage = {
|
|
220
226
|
entries: ContentEntry[];
|
|
221
227
|
totalDocs: number;
|
|
228
|
+
category?: string;
|
|
229
|
+
maxEntries?: number;
|
|
222
230
|
};
|
|
223
231
|
|
|
224
232
|
type ContentRequestContext = RequestContext & {
|
|
@@ -229,6 +237,7 @@ declare function createContentApi(ctx: ContentRequestContext): {
|
|
|
229
237
|
getEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<CmsContentEntryDto | null>;
|
|
230
238
|
listMappedEntries(contentTypeApiId: string, query?: ListContentEntriesQuery, init?: FetchRequestInit): Promise<MappedContentEntriesPage | null>;
|
|
231
239
|
getMappedEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<ContentEntry | null>;
|
|
240
|
+
getSingleMappedEntry(contentTypeApiId: string, init?: FetchRequestInit): Promise<ContentEntry | null>;
|
|
232
241
|
};
|
|
233
242
|
type ContentApi = ReturnType<typeof createContentApi>;
|
|
234
243
|
|
|
@@ -250,14 +259,65 @@ declare function createLeadsApi(ctx: LeadsRequestContext): {
|
|
|
250
259
|
};
|
|
251
260
|
type LeadsApi = ReturnType<typeof createLeadsApi>;
|
|
252
261
|
|
|
262
|
+
type NewsletterSubscriberSummary = {
|
|
263
|
+
id: string;
|
|
264
|
+
email?: string;
|
|
265
|
+
status?: string;
|
|
266
|
+
tags?: string[];
|
|
267
|
+
customFields?: Record<string, unknown>;
|
|
268
|
+
};
|
|
269
|
+
type NewsletterActionResult = {
|
|
270
|
+
ok: boolean;
|
|
271
|
+
message: string;
|
|
272
|
+
subscriberId?: string;
|
|
273
|
+
email?: string;
|
|
274
|
+
subscriber?: NewsletterSubscriberSummary;
|
|
275
|
+
};
|
|
276
|
+
type SubscribeNewsletterInput = {
|
|
277
|
+
email: string;
|
|
278
|
+
source?: string;
|
|
279
|
+
listId?: string;
|
|
280
|
+
customFields?: Record<string, unknown>;
|
|
281
|
+
};
|
|
282
|
+
type UnsubscribeNewsletterInput = {
|
|
283
|
+
/** Token from confirmation or unsubscribe email link */
|
|
284
|
+
token?: string;
|
|
285
|
+
email?: string;
|
|
286
|
+
};
|
|
287
|
+
type UpdateNewsletterPreferencesInput = {
|
|
288
|
+
token?: string;
|
|
289
|
+
email?: string;
|
|
290
|
+
/** Merged into existing customFields on the server */
|
|
291
|
+
customFields?: Record<string, unknown>;
|
|
292
|
+
tags?: string[];
|
|
293
|
+
tagsAction?: "set" | "add" | "remove";
|
|
294
|
+
listId?: string;
|
|
295
|
+
};
|
|
296
|
+
type ConfirmNewsletterResult = {
|
|
297
|
+
ok: boolean;
|
|
298
|
+
message: string;
|
|
299
|
+
email?: string;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
type NewsletterRequestContext = RequestContext & {
|
|
303
|
+
newsletterBasePath: string;
|
|
304
|
+
};
|
|
305
|
+
declare function createNewsletterApi(ctx: NewsletterRequestContext): {
|
|
306
|
+
subscribe(input: SubscribeNewsletterInput, init?: FetchRequestInit): Promise<NewsletterActionResult>;
|
|
307
|
+
unsubscribe(input: UnsubscribeNewsletterInput, init?: FetchRequestInit): Promise<NewsletterActionResult>;
|
|
308
|
+
confirm(token: string, init?: FetchRequestInit): Promise<ConfirmNewsletterResult>;
|
|
309
|
+
updatePreferences(input: UpdateNewsletterPreferencesInput, init?: FetchRequestInit): Promise<NewsletterActionResult>;
|
|
310
|
+
};
|
|
311
|
+
type NewsletterApi = ReturnType<typeof createNewsletterApi>;
|
|
312
|
+
|
|
253
313
|
type CmsClientConfig = {
|
|
254
314
|
/** Base URL, e.g. https://cms-gateway.example.com */
|
|
255
315
|
baseUrl: string;
|
|
256
316
|
/** CMS site Mongo id — appended as ?siteId= on every public request */
|
|
257
317
|
siteId?: string;
|
|
258
318
|
/**
|
|
259
|
-
* Path prefix before /public/blog and /public/
|
|
260
|
-
* Default '' for gateway-cms direct (/public/blog/...,
|
|
319
|
+
* Path prefix before /public/blog, /public/api, and /public/newsletter.
|
|
320
|
+
* Default '' for gateway-cms direct (/public/blog/..., etc.).
|
|
261
321
|
* Use '/api/backend/cms' when routing through the main API gateway.
|
|
262
322
|
*/
|
|
263
323
|
pathPrefix?: string;
|
|
@@ -268,7 +328,8 @@ type CmsClient = {
|
|
|
268
328
|
blog: BlogApi;
|
|
269
329
|
content: ContentApi;
|
|
270
330
|
leads: LeadsApi;
|
|
331
|
+
newsletter: NewsletterApi;
|
|
271
332
|
};
|
|
272
333
|
declare function createCmsClient(config: CmsClientConfig): CmsClient;
|
|
273
334
|
|
|
274
|
-
export {
|
|
335
|
+
export { type NewsletterApi as A, type BlogApi as B, type CmsApiResponse as C, type NewsletterSubscriberSummary as D, type SubmitLeadResult as E, type FetchRequestInit as F, type SubscribeNewsletterInput as G, type UpdateNewsletterPreferencesInput as H, createCmsClient as I, serializeContentFilters as J, type LeadSubmissionData as L, type MappedContentEntriesPage as M, type NewsletterActionResult as N, type SubmitLeadOptions as S, type UnsubscribeNewsletterInput as U, type BlogAuthor as a, type BlogCategory as b, type BlogComment as c, type BlogCommentReply as d, type BlogEngagement as e, type BlogLikeResult as f, type BlogPost as g, type CmsBlogCategoryDto as h, type CmsBlogCommentDto as i, type CmsBlogPostDto as j, type CmsBlogPostsPage as k, type CmsClient as l, type CmsClientConfig as m, type CmsContentEntriesPage as n, type CmsContentEntryDto as o, type CmsContentTypeDto as p, type ConfirmNewsletterResult as q, type ContentApi as r, type ContentEntry as s, type ContentFilters as t, type ContentTypeMeta as u, type CreateBlogCommentInput as v, type CreateBlogCommentResult as w, type LeadsApi as x, type ListBlogPostsQuery as y, type ListContentEntriesQuery as z };
|
|
@@ -181,6 +181,8 @@ type CmsContentTypeDto = {
|
|
|
181
181
|
id?: string;
|
|
182
182
|
name?: string;
|
|
183
183
|
apiId?: string;
|
|
184
|
+
/** COLLECTION (many entries), SINGLE (one entry), or COMPONENT (embedded schema only). */
|
|
185
|
+
category?: string;
|
|
184
186
|
fields?: unknown[];
|
|
185
187
|
status?: string;
|
|
186
188
|
};
|
|
@@ -199,11 +201,15 @@ type CmsContentEntriesPage = {
|
|
|
199
201
|
message?: string;
|
|
200
202
|
docs: CmsContentEntryDto[];
|
|
201
203
|
totalDocs: number;
|
|
204
|
+
/** Present when the content type category is SINGLE (at most one entry per site). */
|
|
205
|
+
category?: string;
|
|
206
|
+
maxEntries?: number;
|
|
202
207
|
};
|
|
203
208
|
type ContentTypeMeta = {
|
|
204
209
|
id: string;
|
|
205
210
|
name: string;
|
|
206
211
|
apiId: string;
|
|
212
|
+
category?: string;
|
|
207
213
|
fields?: unknown[];
|
|
208
214
|
status?: string;
|
|
209
215
|
};
|
|
@@ -219,6 +225,8 @@ type ContentEntry = {
|
|
|
219
225
|
type MappedContentEntriesPage = {
|
|
220
226
|
entries: ContentEntry[];
|
|
221
227
|
totalDocs: number;
|
|
228
|
+
category?: string;
|
|
229
|
+
maxEntries?: number;
|
|
222
230
|
};
|
|
223
231
|
|
|
224
232
|
type ContentRequestContext = RequestContext & {
|
|
@@ -229,6 +237,7 @@ declare function createContentApi(ctx: ContentRequestContext): {
|
|
|
229
237
|
getEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<CmsContentEntryDto | null>;
|
|
230
238
|
listMappedEntries(contentTypeApiId: string, query?: ListContentEntriesQuery, init?: FetchRequestInit): Promise<MappedContentEntriesPage | null>;
|
|
231
239
|
getMappedEntry(contentTypeApiId: string, idOrSlug: string, init?: FetchRequestInit): Promise<ContentEntry | null>;
|
|
240
|
+
getSingleMappedEntry(contentTypeApiId: string, init?: FetchRequestInit): Promise<ContentEntry | null>;
|
|
232
241
|
};
|
|
233
242
|
type ContentApi = ReturnType<typeof createContentApi>;
|
|
234
243
|
|
|
@@ -250,14 +259,65 @@ declare function createLeadsApi(ctx: LeadsRequestContext): {
|
|
|
250
259
|
};
|
|
251
260
|
type LeadsApi = ReturnType<typeof createLeadsApi>;
|
|
252
261
|
|
|
262
|
+
type NewsletterSubscriberSummary = {
|
|
263
|
+
id: string;
|
|
264
|
+
email?: string;
|
|
265
|
+
status?: string;
|
|
266
|
+
tags?: string[];
|
|
267
|
+
customFields?: Record<string, unknown>;
|
|
268
|
+
};
|
|
269
|
+
type NewsletterActionResult = {
|
|
270
|
+
ok: boolean;
|
|
271
|
+
message: string;
|
|
272
|
+
subscriberId?: string;
|
|
273
|
+
email?: string;
|
|
274
|
+
subscriber?: NewsletterSubscriberSummary;
|
|
275
|
+
};
|
|
276
|
+
type SubscribeNewsletterInput = {
|
|
277
|
+
email: string;
|
|
278
|
+
source?: string;
|
|
279
|
+
listId?: string;
|
|
280
|
+
customFields?: Record<string, unknown>;
|
|
281
|
+
};
|
|
282
|
+
type UnsubscribeNewsletterInput = {
|
|
283
|
+
/** Token from confirmation or unsubscribe email link */
|
|
284
|
+
token?: string;
|
|
285
|
+
email?: string;
|
|
286
|
+
};
|
|
287
|
+
type UpdateNewsletterPreferencesInput = {
|
|
288
|
+
token?: string;
|
|
289
|
+
email?: string;
|
|
290
|
+
/** Merged into existing customFields on the server */
|
|
291
|
+
customFields?: Record<string, unknown>;
|
|
292
|
+
tags?: string[];
|
|
293
|
+
tagsAction?: "set" | "add" | "remove";
|
|
294
|
+
listId?: string;
|
|
295
|
+
};
|
|
296
|
+
type ConfirmNewsletterResult = {
|
|
297
|
+
ok: boolean;
|
|
298
|
+
message: string;
|
|
299
|
+
email?: string;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
type NewsletterRequestContext = RequestContext & {
|
|
303
|
+
newsletterBasePath: string;
|
|
304
|
+
};
|
|
305
|
+
declare function createNewsletterApi(ctx: NewsletterRequestContext): {
|
|
306
|
+
subscribe(input: SubscribeNewsletterInput, init?: FetchRequestInit): Promise<NewsletterActionResult>;
|
|
307
|
+
unsubscribe(input: UnsubscribeNewsletterInput, init?: FetchRequestInit): Promise<NewsletterActionResult>;
|
|
308
|
+
confirm(token: string, init?: FetchRequestInit): Promise<ConfirmNewsletterResult>;
|
|
309
|
+
updatePreferences(input: UpdateNewsletterPreferencesInput, init?: FetchRequestInit): Promise<NewsletterActionResult>;
|
|
310
|
+
};
|
|
311
|
+
type NewsletterApi = ReturnType<typeof createNewsletterApi>;
|
|
312
|
+
|
|
253
313
|
type CmsClientConfig = {
|
|
254
314
|
/** Base URL, e.g. https://cms-gateway.example.com */
|
|
255
315
|
baseUrl: string;
|
|
256
316
|
/** CMS site Mongo id — appended as ?siteId= on every public request */
|
|
257
317
|
siteId?: string;
|
|
258
318
|
/**
|
|
259
|
-
* Path prefix before /public/blog and /public/
|
|
260
|
-
* Default '' for gateway-cms direct (/public/blog/...,
|
|
319
|
+
* Path prefix before /public/blog, /public/api, and /public/newsletter.
|
|
320
|
+
* Default '' for gateway-cms direct (/public/blog/..., etc.).
|
|
261
321
|
* Use '/api/backend/cms' when routing through the main API gateway.
|
|
262
322
|
*/
|
|
263
323
|
pathPrefix?: string;
|
|
@@ -268,7 +328,8 @@ type CmsClient = {
|
|
|
268
328
|
blog: BlogApi;
|
|
269
329
|
content: ContentApi;
|
|
270
330
|
leads: LeadsApi;
|
|
331
|
+
newsletter: NewsletterApi;
|
|
271
332
|
};
|
|
272
333
|
declare function createCmsClient(config: CmsClientConfig): CmsClient;
|
|
273
334
|
|
|
274
|
-
export {
|
|
335
|
+
export { type NewsletterApi as A, type BlogApi as B, type CmsApiResponse as C, type NewsletterSubscriberSummary as D, type SubmitLeadResult as E, type FetchRequestInit as F, type SubscribeNewsletterInput as G, type UpdateNewsletterPreferencesInput as H, createCmsClient as I, serializeContentFilters as J, type LeadSubmissionData as L, type MappedContentEntriesPage as M, type NewsletterActionResult as N, type SubmitLeadOptions as S, type UnsubscribeNewsletterInput as U, type BlogAuthor as a, type BlogCategory as b, type BlogComment as c, type BlogCommentReply as d, type BlogEngagement as e, type BlogLikeResult as f, type BlogPost as g, type CmsBlogCategoryDto as h, type CmsBlogCommentDto as i, type CmsBlogPostDto as j, type CmsBlogPostsPage as k, type CmsClient as l, type CmsClientConfig as m, type CmsContentEntriesPage as n, type CmsContentEntryDto as o, type CmsContentTypeDto as p, type ConfirmNewsletterResult as q, type ContentApi as r, type ContentEntry as s, type ContentFilters as t, type ContentTypeMeta as u, type CreateBlogCommentInput as v, type CreateBlogCommentResult as w, type LeadsApi as x, type ListBlogPostsQuery as y, type ListContentEntriesQuery as z };
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,19 @@ async function publicGetNullable(ctx, path, init) {
|
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
async function publicPost(ctx, path, body, init) {
|
|
45
|
+
const res = await ctx.fetchFn(buildPublicUrl(ctx, path), {
|
|
46
|
+
...init,
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
...ctx.defaultHeaders,
|
|
51
|
+
...init?.headers
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify(body)
|
|
54
|
+
});
|
|
55
|
+
return unwrapResponse(res);
|
|
56
|
+
}
|
|
44
57
|
|
|
45
58
|
// src/blog/mappers.ts
|
|
46
59
|
function mediaUrl(value) {
|
|
@@ -226,6 +239,7 @@ function mapContentType(dto) {
|
|
|
226
239
|
id,
|
|
227
240
|
name: dto.name ?? "",
|
|
228
241
|
apiId: dto.apiId ?? "",
|
|
242
|
+
category: dto.category,
|
|
229
243
|
fields: dto.fields,
|
|
230
244
|
status: dto.status
|
|
231
245
|
};
|
|
@@ -276,13 +290,23 @@ function createContentApi(ctx) {
|
|
|
276
290
|
if (!page?.docs) return null;
|
|
277
291
|
return {
|
|
278
292
|
entries: page.docs.map(mapCmsEntryToContentEntry),
|
|
279
|
-
totalDocs: page.totalDocs
|
|
293
|
+
totalDocs: page.totalDocs,
|
|
294
|
+
...page.category ? { category: page.category } : {},
|
|
295
|
+
...page.maxEntries != null ? { maxEntries: page.maxEntries } : {}
|
|
280
296
|
};
|
|
281
297
|
},
|
|
282
298
|
async getMappedEntry(contentTypeApiId, idOrSlug, init) {
|
|
283
299
|
const dto = await this.getEntry(contentTypeApiId, idOrSlug, init);
|
|
284
300
|
if (!dto) return null;
|
|
285
301
|
return mapCmsEntryToContentEntry(dto);
|
|
302
|
+
},
|
|
303
|
+
async getSingleMappedEntry(contentTypeApiId, init) {
|
|
304
|
+
const page = await this.listMappedEntries(
|
|
305
|
+
contentTypeApiId,
|
|
306
|
+
{ limit: 1, page: 1 },
|
|
307
|
+
init
|
|
308
|
+
);
|
|
309
|
+
return page?.entries?.[0] ?? null;
|
|
286
310
|
}
|
|
287
311
|
};
|
|
288
312
|
}
|
|
@@ -315,6 +339,29 @@ function createLeadsApi(ctx) {
|
|
|
315
339
|
};
|
|
316
340
|
}
|
|
317
341
|
|
|
342
|
+
// src/newsletter/endpoints.ts
|
|
343
|
+
function createNewsletterApi(ctx) {
|
|
344
|
+
const prefix = ctx.newsletterBasePath;
|
|
345
|
+
return {
|
|
346
|
+
subscribe(input, init) {
|
|
347
|
+
return publicPost(ctx, `${prefix}/subscribe`, input, init);
|
|
348
|
+
},
|
|
349
|
+
unsubscribe(input, init) {
|
|
350
|
+
const body = {};
|
|
351
|
+
if (input.token) body.token = input.token;
|
|
352
|
+
if (input.email) body.email = input.email;
|
|
353
|
+
return publicPost(ctx, `${prefix}/unsubscribe`, body, init);
|
|
354
|
+
},
|
|
355
|
+
confirm(token, init) {
|
|
356
|
+
const path = `${prefix}/confirm/${encodeURIComponent(token)}`;
|
|
357
|
+
return publicGet(ctx, path, init);
|
|
358
|
+
},
|
|
359
|
+
updatePreferences(input, init) {
|
|
360
|
+
return publicPost(ctx, `${prefix}/preferences`, input, init);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
318
365
|
// src/client.ts
|
|
319
366
|
function normalizeBaseUrl(url) {
|
|
320
367
|
return url.replace(/\/$/, "");
|
|
@@ -329,6 +376,7 @@ function createCmsClient(config) {
|
|
|
329
376
|
const pathPrefix = normalizePathPrefix(config.pathPrefix ?? "");
|
|
330
377
|
const blogBasePath = `${pathPrefix}/public/blog`.replace(/\/+/g, "/");
|
|
331
378
|
const contentBasePath = `${pathPrefix}/public/api`.replace(/\/+/g, "/");
|
|
379
|
+
const newsletterBasePath = `${pathPrefix}/public/newsletter`.replace(/\/+/g, "/");
|
|
332
380
|
const ctx = {
|
|
333
381
|
baseUrl,
|
|
334
382
|
siteId: config.siteId,
|
|
@@ -338,16 +386,112 @@ function createCmsClient(config) {
|
|
|
338
386
|
return {
|
|
339
387
|
blog: createBlogApi({ ...ctx, blogBasePath }),
|
|
340
388
|
content: createContentApi({ ...ctx, contentBasePath }),
|
|
341
|
-
leads: createLeadsApi({ ...ctx, contentBasePath })
|
|
389
|
+
leads: createLeadsApi({ ...ctx, contentBasePath }),
|
|
390
|
+
newsletter: createNewsletterApi({ ...ctx, newsletterBasePath })
|
|
342
391
|
};
|
|
343
392
|
}
|
|
344
393
|
|
|
394
|
+
// src/content/field-types.ts
|
|
395
|
+
var ContentTypeCategory = {
|
|
396
|
+
COLLECTION: "COLLECTION",
|
|
397
|
+
SINGLE: "SINGLE",
|
|
398
|
+
COMPONENT: "COMPONENT"
|
|
399
|
+
};
|
|
400
|
+
var FieldType = {
|
|
401
|
+
TEXT: "TEXT",
|
|
402
|
+
TEXTAREA: "TEXTAREA",
|
|
403
|
+
RICHTEXT: "RICHTEXT",
|
|
404
|
+
NUMBER: "NUMBER",
|
|
405
|
+
BOOLEAN: "BOOLEAN",
|
|
406
|
+
DATE: "DATE",
|
|
407
|
+
DATETIME: "DATETIME",
|
|
408
|
+
TIME: "TIME",
|
|
409
|
+
EMAIL: "EMAIL",
|
|
410
|
+
URL: "URL",
|
|
411
|
+
MEDIA: "MEDIA",
|
|
412
|
+
RELATION: "RELATION",
|
|
413
|
+
JSON: "JSON",
|
|
414
|
+
ENUM: "ENUM",
|
|
415
|
+
PHONE: "PHONE",
|
|
416
|
+
WEEKDAY: "WEEKDAY",
|
|
417
|
+
COMPONENT: "COMPONENT",
|
|
418
|
+
DYNAMIC_ZONE: "DYNAMIC_ZONE",
|
|
419
|
+
PAGE_EDITOR: "PAGE_EDITOR"
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// src/content/structured-data.ts
|
|
423
|
+
function isPlainObject(value) {
|
|
424
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
425
|
+
}
|
|
426
|
+
function parseContentTypeFields(raw) {
|
|
427
|
+
if (!Array.isArray(raw)) return [];
|
|
428
|
+
const out = [];
|
|
429
|
+
for (const item of raw) {
|
|
430
|
+
if (!isPlainObject(item)) continue;
|
|
431
|
+
const name = item.name;
|
|
432
|
+
const type = item.type;
|
|
433
|
+
if (typeof name !== "string" || !name.trim()) continue;
|
|
434
|
+
if (typeof type !== "string" || !type.trim()) continue;
|
|
435
|
+
out.push(item);
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
function findContentTypeField(fields, fieldName) {
|
|
440
|
+
return fields?.find((f) => f.name === fieldName);
|
|
441
|
+
}
|
|
442
|
+
function getEntryFieldValue(entry, fieldName) {
|
|
443
|
+
return entry.data[fieldName];
|
|
444
|
+
}
|
|
445
|
+
function getComponentItems(entry, fieldName) {
|
|
446
|
+
const value = entry.data[fieldName];
|
|
447
|
+
if (value == null) return [];
|
|
448
|
+
if (Array.isArray(value)) {
|
|
449
|
+
return value.filter(isPlainObject);
|
|
450
|
+
}
|
|
451
|
+
if (isPlainObject(value)) {
|
|
452
|
+
return [value];
|
|
453
|
+
}
|
|
454
|
+
return [];
|
|
455
|
+
}
|
|
456
|
+
function getComponentItem(entry, fieldName) {
|
|
457
|
+
const items = getComponentItems(entry, fieldName);
|
|
458
|
+
return items[0];
|
|
459
|
+
}
|
|
460
|
+
function getDynamicZoneBlocks(entry, fieldName) {
|
|
461
|
+
const value = entry.data[fieldName];
|
|
462
|
+
if (!Array.isArray(value)) return [];
|
|
463
|
+
return value.filter(
|
|
464
|
+
(item) => isPlainObject(item) && typeof item.__component === "string" && item.__component.length > 0
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function filterDynamicZoneByComponent(blocks, componentApiId) {
|
|
468
|
+
return blocks.filter((b) => b.__component === componentApiId);
|
|
469
|
+
}
|
|
470
|
+
function isRepeatableComponentField(field) {
|
|
471
|
+
return field.type === FieldType.COMPONENT && field.component?.repeatable === true;
|
|
472
|
+
}
|
|
473
|
+
function isDynamicZoneField(field) {
|
|
474
|
+
return field.type === FieldType.DYNAMIC_ZONE;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
exports.ContentTypeCategory = ContentTypeCategory;
|
|
478
|
+
exports.FieldType = FieldType;
|
|
345
479
|
exports.createCmsClient = createCmsClient;
|
|
480
|
+
exports.filterDynamicZoneByComponent = filterDynamicZoneByComponent;
|
|
481
|
+
exports.findContentTypeField = findContentTypeField;
|
|
482
|
+
exports.getComponentItem = getComponentItem;
|
|
483
|
+
exports.getComponentItems = getComponentItems;
|
|
484
|
+
exports.getDynamicZoneBlocks = getDynamicZoneBlocks;
|
|
485
|
+
exports.getEntryFieldValue = getEntryFieldValue;
|
|
486
|
+
exports.isDynamicZoneField = isDynamicZoneField;
|
|
487
|
+
exports.isPlainObject = isPlainObject;
|
|
488
|
+
exports.isRepeatableComponentField = isRepeatableComponentField;
|
|
346
489
|
exports.mapCmsCategoryToBlogCategory = mapCmsCategoryToBlogCategory;
|
|
347
490
|
exports.mapCmsCommentToBlogComment = mapCmsCommentToBlogComment;
|
|
348
491
|
exports.mapCmsEntryToContentEntry = mapCmsEntryToContentEntry;
|
|
349
492
|
exports.mapCmsPostToBlogPost = mapCmsPostToBlogPost;
|
|
350
493
|
exports.mediaUrl = mediaUrl;
|
|
494
|
+
exports.parseContentTypeFields = parseContentTypeFields;
|
|
351
495
|
exports.serializeContentFilters = serializeContentFilters;
|
|
352
496
|
//# sourceMappingURL=index.cjs.map
|
|
353
497
|
//# sourceMappingURL=index.cjs.map
|