@commercetools-demo/puck-content-manager 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,1105 @@
1
+ // src/ContentManager.tsx
2
+ import { useState } from "react";
3
+ import { PuckApiProvider, usePuckContents } from "@commercetools-demo/puck-api";
4
+ import DataTable from "@commercetools-uikit/data-table";
5
+ import PrimaryButton from "@commercetools-uikit/primary-button";
6
+ import SecondaryButton from "@commercetools-uikit/secondary-button";
7
+ import FlatButton from "@commercetools-uikit/flat-button";
8
+ import Card from "@commercetools-uikit/card";
9
+ import Spacings from "@commercetools-uikit/spacings";
10
+ import Text from "@commercetools-uikit/text";
11
+ import LoadingSpinner from "@commercetools-uikit/loading-spinner";
12
+ import TextInput from "@commercetools-uikit/text-input";
13
+ import Label from "@commercetools-uikit/label";
14
+ import { PlusThinIcon, SearchIcon } from "@commercetools-uikit/icons";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ var StatusBadge = ({ variant }) => {
17
+ const styles = variant === "published" ? { background: "var(--color-success-95)", color: "var(--color-success-40)", border: "1px solid var(--color-success-85)" } : variant === "draft" ? { background: "var(--color-warning-95)", color: "var(--color-warning-40)", border: "1px solid var(--color-warning-85)" } : { background: "var(--color-neutral-95)", color: "var(--color-neutral-50)", border: "1px solid var(--color-neutral-85)" };
18
+ return /* @__PURE__ */ jsx(
19
+ "span",
20
+ {
21
+ style: {
22
+ ...styles,
23
+ display: "inline-flex",
24
+ alignItems: "center",
25
+ padding: "2px 8px",
26
+ borderRadius: "var(--border-radius-20)",
27
+ fontSize: "var(--font-size-10)",
28
+ fontWeight: "var(--font-weight-600)",
29
+ marginRight: "4px",
30
+ whiteSpace: "nowrap"
31
+ },
32
+ children: variant === "published" ? "Published" : variant === "draft" ? "Draft" : "No state"
33
+ }
34
+ );
35
+ };
36
+ var columns = [
37
+ { key: "name", label: "Name" },
38
+ { key: "contentType", label: "Content Type" },
39
+ { key: "status", label: "Status" },
40
+ { key: "updatedAt", label: "Updated" },
41
+ { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
42
+ ];
43
+ var ContentList = ({ defaultContentType, onEdit }) => {
44
+ const { contents, loading, error, fetchContents, createContent, deleteContent, refresh } = usePuckContents(defaultContentType);
45
+ const [filterType, setFilterType] = useState(defaultContentType ?? "");
46
+ const [showCreate, setShowCreate] = useState(false);
47
+ const [createName, setCreateName] = useState("");
48
+ const [createType, setCreateType] = useState(defaultContentType ?? "");
49
+ const [createError, setCreateError] = useState(null);
50
+ const [creating, setCreating] = useState(false);
51
+ const [deleting, setDeleting] = useState(null);
52
+ const handleFilter = () => void fetchContents(filterType || void 0);
53
+ const handleCreate = async () => {
54
+ setCreateError(null);
55
+ if (!createName.trim()) {
56
+ setCreateError("Name is required");
57
+ return;
58
+ }
59
+ if (!createType.trim()) {
60
+ setCreateError("Content type is required");
61
+ return;
62
+ }
63
+ setCreating(true);
64
+ try {
65
+ const input = {
66
+ name: createName.trim(),
67
+ contentType: createType.trim(),
68
+ data: { content: [], root: { props: {} } }
69
+ };
70
+ await createContent(input);
71
+ setShowCreate(false);
72
+ setCreateName("");
73
+ setCreateType(defaultContentType ?? "");
74
+ } catch (err) {
75
+ setCreateError(err.message);
76
+ } finally {
77
+ setCreating(false);
78
+ }
79
+ };
80
+ const handleDelete = async (key) => {
81
+ if (!confirm("Delete this content item and all its versions?")) return;
82
+ setDeleting(key);
83
+ try {
84
+ await deleteContent(key);
85
+ } finally {
86
+ setDeleting(null);
87
+ }
88
+ };
89
+ const rows = contents.map((c) => ({ ...c, id: c.key }));
90
+ return /* @__PURE__ */ jsx("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "l", children: [
91
+ /* @__PURE__ */ jsxs(Spacings.Inline, { justifyContent: "space-between", alignItems: "center", children: [
92
+ /* @__PURE__ */ jsx(Text.Headline, { as: "h1", children: "Content Items" }),
93
+ /* @__PURE__ */ jsx(
94
+ PrimaryButton,
95
+ {
96
+ label: "New Content",
97
+ iconLeft: /* @__PURE__ */ jsx(PlusThinIcon, {}),
98
+ onClick: () => setShowCreate((v) => !v)
99
+ }
100
+ )
101
+ ] }),
102
+ showCreate && /* @__PURE__ */ jsx(Card, { insetScale: "l", children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "m", children: [
103
+ /* @__PURE__ */ jsx(Text.Subheadline, { as: "h4", isBold: true, children: "Create Content Item" }),
104
+ createError && /* @__PURE__ */ jsx(Text.Body, { tone: "negative", children: createError }),
105
+ /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "m", children: [
106
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
107
+ /* @__PURE__ */ jsx(Label, { htmlFor: "create-name", children: "Name" }),
108
+ /* @__PURE__ */ jsx(
109
+ TextInput,
110
+ {
111
+ id: "create-name",
112
+ value: createName,
113
+ onChange: (e) => setCreateName(e.target.value),
114
+ placeholder: "e.g. Homepage Hero"
115
+ }
116
+ )
117
+ ] }) }),
118
+ /* @__PURE__ */ jsx("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
119
+ /* @__PURE__ */ jsx(Label, { htmlFor: "create-type", children: "Content Type" }),
120
+ /* @__PURE__ */ jsx(
121
+ TextInput,
122
+ {
123
+ id: "create-type",
124
+ value: createType,
125
+ onChange: (e) => setCreateType(e.target.value),
126
+ placeholder: "e.g. hero, banner"
127
+ }
128
+ )
129
+ ] }) })
130
+ ] }),
131
+ /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", children: [
132
+ /* @__PURE__ */ jsx(
133
+ PrimaryButton,
134
+ {
135
+ label: creating ? "Creating\u2026" : "Create",
136
+ onClick: () => void handleCreate(),
137
+ isDisabled: creating
138
+ }
139
+ ),
140
+ /* @__PURE__ */ jsx(SecondaryButton, { label: "Cancel", onClick: () => setShowCreate(false) })
141
+ ] })
142
+ ] }) }),
143
+ /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", alignItems: "center", children: [
144
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, maxWidth: "280px" }, children: /* @__PURE__ */ jsx(
145
+ TextInput,
146
+ {
147
+ value: filterType,
148
+ onChange: (e) => setFilterType(e.target.value),
149
+ placeholder: "Filter by content type\u2026"
150
+ }
151
+ ) }),
152
+ /* @__PURE__ */ jsx(
153
+ SecondaryButton,
154
+ {
155
+ label: "Filter",
156
+ iconLeft: /* @__PURE__ */ jsx(SearchIcon, {}),
157
+ onClick: handleFilter
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsx(
161
+ FlatButton,
162
+ {
163
+ label: "Clear",
164
+ onClick: () => {
165
+ setFilterType("");
166
+ void fetchContents(void 0);
167
+ }
168
+ }
169
+ ),
170
+ /* @__PURE__ */ jsx(FlatButton, { label: "Refresh", onClick: () => void refresh() })
171
+ ] }),
172
+ error && /* @__PURE__ */ jsx(Text.Body, { tone: "negative", children: error }),
173
+ loading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx(LoadingSpinner, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx(Spacings.Stack, { scale: "m", alignItems: "center", children: /* @__PURE__ */ jsx(Text.Body, { tone: "secondary", children: "No content items found." }) }) : /* @__PURE__ */ jsx(
174
+ DataTable,
175
+ {
176
+ columns,
177
+ rows,
178
+ itemRenderer: (row, column) => {
179
+ switch (column.key) {
180
+ case "name":
181
+ return /* @__PURE__ */ jsx(Text.Body, { isBold: true, children: row.value.name });
182
+ case "contentType":
183
+ return /* @__PURE__ */ jsx(
184
+ "code",
185
+ {
186
+ style: {
187
+ background: "var(--color-neutral-95)",
188
+ padding: "2px 6px",
189
+ borderRadius: "var(--border-radius-4)",
190
+ fontSize: "var(--font-size-10)",
191
+ fontFamily: "monospace"
192
+ },
193
+ children: row.value.contentType
194
+ }
195
+ );
196
+ case "status": {
197
+ const hasDraft = !!row.states.draft;
198
+ const hasPublished = !!row.states.published;
199
+ return /* @__PURE__ */ jsxs("span", { children: [
200
+ hasDraft && /* @__PURE__ */ jsx(StatusBadge, { variant: "draft" }),
201
+ hasPublished && /* @__PURE__ */ jsx(StatusBadge, { variant: "published" }),
202
+ !hasDraft && !hasPublished && /* @__PURE__ */ jsx(StatusBadge, { variant: "none" })
203
+ ] });
204
+ }
205
+ case "updatedAt":
206
+ return /* @__PURE__ */ jsx(Text.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleDateString() });
207
+ case "actions":
208
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
209
+ /* @__PURE__ */ jsx(PrimaryButton, { label: "Edit", size: "20", onClick: () => onEdit(row) }),
210
+ /* @__PURE__ */ jsx(
211
+ FlatButton,
212
+ {
213
+ tone: "critical",
214
+ label: deleting === row.key ? "\u2026" : "Delete",
215
+ isDisabled: deleting === row.key,
216
+ onClick: () => void handleDelete(row.key)
217
+ }
218
+ )
219
+ ] });
220
+ default:
221
+ return null;
222
+ }
223
+ }
224
+ }
225
+ )
226
+ ] }) });
227
+ };
228
+ var ContentManager = ({
229
+ baseURL,
230
+ projectKey,
231
+ businessUnitKey,
232
+ jwtToken,
233
+ defaultContentType,
234
+ onEdit
235
+ }) => /* @__PURE__ */ jsx(
236
+ PuckApiProvider,
237
+ {
238
+ baseURL,
239
+ projectKey,
240
+ businessUnitKey,
241
+ jwtToken,
242
+ children: /* @__PURE__ */ jsx(ContentList, { defaultContentType, onEdit })
243
+ }
244
+ );
245
+
246
+ // src/ContentEditor.tsx
247
+ import { useCallback, useMemo, useRef, useState as useState2 } from "react";
248
+ import { Puck } from "@measured/puck";
249
+ import "@measured/puck/puck.css";
250
+ import { PuckApiProvider as PuckApiProvider2, usePuckContent } from "@commercetools-demo/puck-api";
251
+ import {
252
+ ComponentSearchProvider,
253
+ ComponentsPanel,
254
+ ComponentItemFilter
255
+ } from "@commercetools-demo/puck-editor";
256
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
257
+ var BADGE_STYLES = {
258
+ saving: { bg: "rgba(251, 191, 36, 0.12)", color: "var(--status-saving)", border: "rgba(251, 191, 36, 0.3)" },
259
+ unsaved: { bg: "rgba(100, 116, 139, 0.12)", color: "var(--text-muted)", border: "rgba(100, 116, 139, 0.3)" },
260
+ draft: { bg: "rgba(129, 140, 248, 0.12)", color: "var(--status-draft)", border: "rgba(129, 140, 248, 0.3)" },
261
+ published: { bg: "rgba(6, 214, 160, 0.12)", color: "var(--status-published)", border: "rgba(6, 214, 160, 0.3)" }
262
+ };
263
+ var StatusBadge2 = ({
264
+ label,
265
+ variant
266
+ }) => {
267
+ const bs = BADGE_STYLES[variant];
268
+ return /* @__PURE__ */ jsx2(
269
+ "span",
270
+ {
271
+ style: {
272
+ display: "inline-flex",
273
+ alignItems: "center",
274
+ padding: "2px 10px",
275
+ borderRadius: "12px",
276
+ fontSize: "12px",
277
+ fontWeight: 600,
278
+ background: bs.bg,
279
+ color: bs.color,
280
+ border: `1px solid ${bs.border}`
281
+ },
282
+ children: label
283
+ }
284
+ );
285
+ };
286
+ var ContentToolbar = ({
287
+ saving,
288
+ isDirty,
289
+ states,
290
+ onSave,
291
+ onPublish,
292
+ onRevert
293
+ }) => {
294
+ const hasDraft = Boolean(states.draft);
295
+ const hasPublished = Boolean(states.published);
296
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" }, children: [
297
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
298
+ saving && /* @__PURE__ */ jsx2(StatusBadge2, { label: "Saving\u2026", variant: "saving" }),
299
+ !saving && isDirty && /* @__PURE__ */ jsx2(StatusBadge2, { label: "Unsaved", variant: "unsaved" }),
300
+ !saving && !isDirty && hasDraft && /* @__PURE__ */ jsx2(StatusBadge2, { label: "Draft", variant: "draft" }),
301
+ hasPublished && /* @__PURE__ */ jsx2(StatusBadge2, { label: "Published", variant: "published" })
302
+ ] }),
303
+ hasDraft && hasPublished && /* @__PURE__ */ jsx2(
304
+ "button",
305
+ {
306
+ onClick: onRevert,
307
+ disabled: saving,
308
+ style: {
309
+ padding: "6px 14px",
310
+ borderRadius: "4px",
311
+ border: "1px solid var(--border-glow)",
312
+ background: "transparent",
313
+ color: "var(--text-muted)",
314
+ fontWeight: 500,
315
+ fontSize: "13px",
316
+ cursor: saving ? "not-allowed" : "pointer",
317
+ opacity: saving ? 0.5 : 1
318
+ },
319
+ children: "Revert to Published"
320
+ }
321
+ ),
322
+ /* @__PURE__ */ jsx2(
323
+ "button",
324
+ {
325
+ onClick: onSave,
326
+ disabled: !isDirty || saving,
327
+ style: {
328
+ padding: "6px 14px",
329
+ borderRadius: "4px",
330
+ border: "1px solid var(--border-glow)",
331
+ background: "transparent",
332
+ color: "var(--text-muted)",
333
+ fontWeight: 500,
334
+ fontSize: "13px",
335
+ cursor: !isDirty || saving ? "not-allowed" : "pointer",
336
+ opacity: !isDirty || saving ? 0.4 : 1
337
+ },
338
+ children: "Save"
339
+ }
340
+ ),
341
+ /* @__PURE__ */ jsx2(
342
+ "button",
343
+ {
344
+ onClick: onPublish,
345
+ disabled: saving,
346
+ style: {
347
+ padding: "6px 16px",
348
+ borderRadius: "4px",
349
+ border: "1px solid var(--accent-cyan)",
350
+ background: "rgba(0, 212, 255, 0.1)",
351
+ color: "var(--accent-cyan)",
352
+ fontWeight: 600,
353
+ fontSize: "13px",
354
+ cursor: saving ? "not-allowed" : "pointer",
355
+ opacity: saving ? 0.5 : 1,
356
+ boxShadow: "0 0 12px rgba(0, 212, 255, 0.15)"
357
+ },
358
+ children: hasPublished ? "Re-publish" : "Publish"
359
+ }
360
+ )
361
+ ] });
362
+ };
363
+ var ContentEditorInner = ({
364
+ contentKey,
365
+ config,
366
+ onPublish,
367
+ onSave,
368
+ onError
369
+ }) => {
370
+ const {
371
+ content,
372
+ states,
373
+ saving,
374
+ loading,
375
+ error,
376
+ saveDraft,
377
+ publish,
378
+ revertToPublished
379
+ } = usePuckContent(contentKey);
380
+ const latestDataRef = useRef(null);
381
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState2(false);
382
+ const handleChange = useCallback((data) => {
383
+ latestDataRef.current = data;
384
+ setHasUnsavedChanges(true);
385
+ }, []);
386
+ const handleSave = useCallback(async () => {
387
+ const data = latestDataRef.current;
388
+ if (!data) return;
389
+ try {
390
+ await saveDraft(data);
391
+ setHasUnsavedChanges(false);
392
+ onSave?.(data);
393
+ } catch (err) {
394
+ onError?.(err);
395
+ }
396
+ }, [saveDraft, onSave, onError]);
397
+ const handlePublish = useCallback(
398
+ async (data) => {
399
+ try {
400
+ await saveDraft(data);
401
+ setHasUnsavedChanges(false);
402
+ await publish(false);
403
+ onPublish?.(data);
404
+ } catch (err) {
405
+ onError?.(err);
406
+ }
407
+ },
408
+ [saveDraft, publish, onPublish, onError]
409
+ );
410
+ const handleRevert = useCallback(async () => {
411
+ try {
412
+ await revertToPublished();
413
+ setHasUnsavedChanges(false);
414
+ } catch (err) {
415
+ onError?.(err);
416
+ }
417
+ }, [revertToPublished, onError]);
418
+ const contentConfig = useMemo(() => {
419
+ const otherRootFields = Object.fromEntries(
420
+ Object.entries(config.root?.fields ?? {}).filter(([k]) => k !== "title")
421
+ );
422
+ return {
423
+ ...config,
424
+ root: {
425
+ ...config.root,
426
+ fields: {
427
+ title: { type: "text", label: "Content Title" },
428
+ slot: { type: "text", label: "Slot" },
429
+ ...otherRootFields
430
+ },
431
+ defaultProps: {
432
+ title: "New Content",
433
+ slot: "",
434
+ ...config.root?.defaultProps
435
+ }
436
+ }
437
+ };
438
+ }, [config]);
439
+ if (loading) {
440
+ return /* @__PURE__ */ jsx2(
441
+ "div",
442
+ {
443
+ style: {
444
+ display: "flex",
445
+ alignItems: "center",
446
+ justifyContent: "center",
447
+ height: "100vh",
448
+ fontSize: "16px",
449
+ color: "var(--text-muted)"
450
+ },
451
+ children: "Loading editor\u2026"
452
+ }
453
+ );
454
+ }
455
+ if (error) {
456
+ return /* @__PURE__ */ jsxs2(
457
+ "div",
458
+ {
459
+ style: {
460
+ padding: "32px",
461
+ color: "var(--status-error)",
462
+ background: "rgba(248, 113, 113, 0.08)",
463
+ border: "1px solid rgba(248, 113, 113, 0.25)",
464
+ borderRadius: "8px",
465
+ margin: "16px"
466
+ },
467
+ children: [
468
+ /* @__PURE__ */ jsx2("strong", { children: "Error loading content:" }),
469
+ " ",
470
+ error
471
+ ]
472
+ }
473
+ );
474
+ }
475
+ const activeData = states.draft?.data ?? content?.data ?? {
476
+ content: [],
477
+ root: { props: {} }
478
+ };
479
+ return /* @__PURE__ */ jsx2(ComponentSearchProvider, { children: /* @__PURE__ */ jsx2(
480
+ Puck,
481
+ {
482
+ config: contentConfig,
483
+ data: activeData,
484
+ onChange: handleChange,
485
+ onPublish: handlePublish,
486
+ overrides: {
487
+ headerActions: () => /* @__PURE__ */ jsx2(
488
+ ContentToolbar,
489
+ {
490
+ saving,
491
+ isDirty: hasUnsavedChanges,
492
+ states,
493
+ onSave: () => void handleSave(),
494
+ onPublish: () => void handlePublish(activeData),
495
+ onRevert: () => void handleRevert()
496
+ }
497
+ ),
498
+ components: ({ children }) => /* @__PURE__ */ jsx2(ComponentsPanel, { children }),
499
+ componentItem: ({ children, name }) => /* @__PURE__ */ jsx2(ComponentItemFilter, { name, children })
500
+ }
501
+ }
502
+ ) });
503
+ };
504
+ var ContentEditor = ({
505
+ baseURL,
506
+ projectKey,
507
+ businessUnitKey,
508
+ jwtToken,
509
+ contentKey,
510
+ config,
511
+ onPublish,
512
+ onSave,
513
+ onError
514
+ }) => /* @__PURE__ */ jsx2(
515
+ PuckApiProvider2,
516
+ {
517
+ baseURL,
518
+ projectKey,
519
+ businessUnitKey,
520
+ jwtToken,
521
+ children: /* @__PURE__ */ jsx2(
522
+ ContentEditorInner,
523
+ {
524
+ contentKey,
525
+ config,
526
+ onPublish,
527
+ onSave,
528
+ onError
529
+ }
530
+ )
531
+ }
532
+ );
533
+
534
+ // src/ContentManagerRouter.tsx
535
+ import { useState as useState3, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2 } from "react";
536
+ import {
537
+ BrowserRouter,
538
+ Routes,
539
+ Route,
540
+ useNavigate,
541
+ useParams,
542
+ useLocation
543
+ } from "react-router-dom";
544
+ import { Puck as Puck2 } from "@measured/puck";
545
+ import "@measured/puck/puck.css";
546
+ import {
547
+ ComponentSearchProvider as ComponentSearchProvider2,
548
+ ComponentsPanel as ComponentsPanel2,
549
+ ComponentItemFilter as ComponentItemFilter2
550
+ } from "@commercetools-demo/puck-editor";
551
+ import {
552
+ PuckApiProvider as PuckApiProvider3,
553
+ usePuckContents as usePuckContents2,
554
+ usePuckContent as usePuckContent2
555
+ } from "@commercetools-demo/puck-api";
556
+ import DataTable2 from "@commercetools-uikit/data-table";
557
+ import PrimaryButton2 from "@commercetools-uikit/primary-button";
558
+ import SecondaryButton2 from "@commercetools-uikit/secondary-button";
559
+ import FlatButton2 from "@commercetools-uikit/flat-button";
560
+ import Card2 from "@commercetools-uikit/card";
561
+ import Spacings2 from "@commercetools-uikit/spacings";
562
+ import Text2 from "@commercetools-uikit/text";
563
+ import LoadingSpinner2 from "@commercetools-uikit/loading-spinner";
564
+ import TextInput2 from "@commercetools-uikit/text-input";
565
+ import Label2 from "@commercetools-uikit/label";
566
+ import { PlusThinIcon as PlusThinIcon2, SearchIcon as SearchIcon2, AngleLeftIcon } from "@commercetools-uikit/icons";
567
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
568
+ var StatusBadge3 = ({ variant }) => {
569
+ const styles = variant === "published" ? { background: "var(--color-success-95)", color: "var(--color-success-40)", border: "1px solid var(--color-success-85)" } : variant === "draft" ? { background: "var(--color-warning-95)", color: "var(--color-warning-40)", border: "1px solid var(--color-warning-85)" } : { background: "var(--color-neutral-95)", color: "var(--color-neutral-50)", border: "1px solid var(--color-neutral-85)" };
570
+ return /* @__PURE__ */ jsx3(
571
+ "span",
572
+ {
573
+ style: {
574
+ ...styles,
575
+ display: "inline-flex",
576
+ alignItems: "center",
577
+ padding: "2px 8px",
578
+ borderRadius: "var(--border-radius-20)",
579
+ fontSize: "var(--font-size-10)",
580
+ fontWeight: "var(--font-weight-600)",
581
+ marginRight: "4px",
582
+ whiteSpace: "nowrap"
583
+ },
584
+ children: variant === "published" ? "Published" : variant === "draft" ? "Draft" : "No state"
585
+ }
586
+ );
587
+ };
588
+ var BADGE_STYLES2 = {
589
+ saving: { bg: "rgba(251, 191, 36, 0.12)", color: "var(--status-saving)", border: "rgba(251, 191, 36, 0.3)" },
590
+ unsaved: { bg: "rgba(100, 116, 139, 0.12)", color: "var(--text-muted)", border: "rgba(100, 116, 139, 0.3)" },
591
+ draft: { bg: "rgba(129, 140, 248, 0.12)", color: "var(--status-draft)", border: "rgba(129, 140, 248, 0.3)" },
592
+ published: { bg: "rgba(6, 214, 160, 0.12)", color: "var(--status-published)", border: "rgba(6, 214, 160, 0.3)" }
593
+ };
594
+ var EditorStatusBadge = ({ label, variant }) => {
595
+ const bs = BADGE_STYLES2[variant];
596
+ return /* @__PURE__ */ jsx3(
597
+ "span",
598
+ {
599
+ style: {
600
+ display: "inline-flex",
601
+ alignItems: "center",
602
+ padding: "2px 10px",
603
+ borderRadius: "12px",
604
+ fontSize: "12px",
605
+ fontWeight: 600,
606
+ background: bs.bg,
607
+ color: bs.color,
608
+ border: `1px solid ${bs.border}`
609
+ },
610
+ children: label
611
+ }
612
+ );
613
+ };
614
+ var ContentToolbar2 = ({ saving, isDirty, states, onSave, onPublish, onRevert }) => {
615
+ const hasDraft = Boolean(states.draft);
616
+ const hasPublished = Boolean(states.published);
617
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" }, children: [
618
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
619
+ saving && /* @__PURE__ */ jsx3(EditorStatusBadge, { label: "Saving\u2026", variant: "saving" }),
620
+ !saving && isDirty && /* @__PURE__ */ jsx3(EditorStatusBadge, { label: "Unsaved", variant: "unsaved" }),
621
+ !saving && !isDirty && hasDraft && /* @__PURE__ */ jsx3(EditorStatusBadge, { label: "Draft", variant: "draft" }),
622
+ hasPublished && /* @__PURE__ */ jsx3(EditorStatusBadge, { label: "Published", variant: "published" })
623
+ ] }),
624
+ hasDraft && hasPublished && /* @__PURE__ */ jsx3(
625
+ "button",
626
+ {
627
+ onClick: onRevert,
628
+ disabled: saving,
629
+ style: {
630
+ padding: "6px 14px",
631
+ borderRadius: "4px",
632
+ border: "1px solid var(--border-glow)",
633
+ background: "transparent",
634
+ color: "var(--text-muted)",
635
+ fontWeight: 500,
636
+ fontSize: "13px",
637
+ cursor: saving ? "not-allowed" : "pointer",
638
+ opacity: saving ? 0.5 : 1
639
+ },
640
+ children: "Revert to Published"
641
+ }
642
+ ),
643
+ /* @__PURE__ */ jsx3(
644
+ "button",
645
+ {
646
+ onClick: onSave,
647
+ disabled: !isDirty || saving,
648
+ style: {
649
+ padding: "6px 14px",
650
+ borderRadius: "4px",
651
+ border: "1px solid var(--border-glow)",
652
+ background: "transparent",
653
+ color: "var(--text-muted)",
654
+ fontWeight: 500,
655
+ fontSize: "13px",
656
+ cursor: !isDirty || saving ? "not-allowed" : "pointer",
657
+ opacity: !isDirty || saving ? 0.4 : 1
658
+ },
659
+ children: "Save"
660
+ }
661
+ ),
662
+ /* @__PURE__ */ jsx3(
663
+ "button",
664
+ {
665
+ onClick: onPublish,
666
+ disabled: saving,
667
+ style: {
668
+ padding: "6px 16px",
669
+ borderRadius: "4px",
670
+ border: "1px solid var(--accent-cyan)",
671
+ background: "rgba(0, 212, 255, 0.1)",
672
+ color: "var(--accent-cyan)",
673
+ fontWeight: 600,
674
+ fontSize: "13px",
675
+ cursor: saving ? "not-allowed" : "pointer",
676
+ opacity: saving ? 0.5 : 1,
677
+ boxShadow: "0 0 12px rgba(0, 212, 255, 0.15)"
678
+ },
679
+ children: hasPublished ? "Re-publish" : "Publish"
680
+ }
681
+ )
682
+ ] });
683
+ };
684
+ var NAV_BAR_STYLE = {
685
+ position: "sticky",
686
+ top: 0,
687
+ display: "flex",
688
+ alignItems: "center",
689
+ gap: "12px",
690
+ padding: "8px 16px",
691
+ background: "var(--color-surface, #fff)",
692
+ borderBottom: "1px solid var(--color-neutral-90)",
693
+ zIndex: 200,
694
+ flexShrink: 0
695
+ };
696
+ var COLUMNS = [
697
+ { key: "name", label: "Name" },
698
+ { key: "contentType", label: "Content Type" },
699
+ { key: "status", label: "Status" },
700
+ { key: "updatedAt", label: "Updated" },
701
+ { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
702
+ ];
703
+ var ContentListRoute = ({ defaultContentType, backButton }) => {
704
+ const navigate = useNavigate();
705
+ const { contents, loading, error, fetchContents, createContent, deleteContent, refresh } = usePuckContents2(defaultContentType);
706
+ const [filterType, setFilterType] = useState3(defaultContentType ?? "");
707
+ const [showCreate, setShowCreate] = useState3(false);
708
+ const [createName, setCreateName] = useState3("");
709
+ const [createType, setCreateType] = useState3(defaultContentType ?? "");
710
+ const [createError, setCreateError] = useState3(null);
711
+ const [creating, setCreating] = useState3(false);
712
+ const [deleting, setDeleting] = useState3(null);
713
+ const handleFilter = () => void fetchContents(filterType || void 0);
714
+ const handleCreate = async () => {
715
+ setCreateError(null);
716
+ if (!createName.trim()) {
717
+ setCreateError("Name is required");
718
+ return;
719
+ }
720
+ if (!createType.trim()) {
721
+ setCreateError("Content type is required");
722
+ return;
723
+ }
724
+ setCreating(true);
725
+ try {
726
+ const input = {
727
+ name: createName.trim(),
728
+ contentType: createType.trim(),
729
+ data: { content: [], root: { props: {} } }
730
+ };
731
+ const created = await createContent(input);
732
+ setShowCreate(false);
733
+ setCreateName("");
734
+ setCreateType(defaultContentType ?? "");
735
+ navigate(`/${created.key}`, { state: { contentName: created.value.name } });
736
+ } catch (err) {
737
+ setCreateError(err.message);
738
+ } finally {
739
+ setCreating(false);
740
+ }
741
+ };
742
+ const handleDelete = async (key) => {
743
+ if (!confirm("Delete this content item and all its versions?")) return;
744
+ setDeleting(key);
745
+ try {
746
+ await deleteContent(key);
747
+ } finally {
748
+ setDeleting(null);
749
+ }
750
+ };
751
+ const rows = contents.map((c) => ({ ...c, id: c.key }));
752
+ return /* @__PURE__ */ jsx3("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs3(Spacings2.Stack, { scale: "l", children: [
753
+ /* @__PURE__ */ jsxs3(Spacings2.Inline, { justifyContent: "space-between", alignItems: "center", children: [
754
+ /* @__PURE__ */ jsxs3(Spacings2.Inline, { scale: "m", alignItems: "center", children: [
755
+ backButton,
756
+ /* @__PURE__ */ jsx3(Text2.Headline, { as: "h1", children: "Content Items" })
757
+ ] }),
758
+ /* @__PURE__ */ jsx3(
759
+ PrimaryButton2,
760
+ {
761
+ label: "New Content",
762
+ iconLeft: /* @__PURE__ */ jsx3(PlusThinIcon2, {}),
763
+ onClick: () => setShowCreate((v) => !v)
764
+ }
765
+ )
766
+ ] }),
767
+ showCreate && /* @__PURE__ */ jsx3(Card2, { insetScale: "l", children: /* @__PURE__ */ jsxs3(Spacings2.Stack, { scale: "m", children: [
768
+ /* @__PURE__ */ jsx3(Text2.Subheadline, { as: "h4", isBold: true, children: "Create Content Item" }),
769
+ createError && /* @__PURE__ */ jsx3(Text2.Body, { tone: "negative", children: createError }),
770
+ /* @__PURE__ */ jsxs3(Spacings2.Inline, { scale: "m", children: [
771
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(Spacings2.Stack, { scale: "xs", children: [
772
+ /* @__PURE__ */ jsx3(Label2, { htmlFor: "create-content-name", children: "Name" }),
773
+ /* @__PURE__ */ jsx3(
774
+ TextInput2,
775
+ {
776
+ id: "create-content-name",
777
+ value: createName,
778
+ onChange: (e) => setCreateName(e.target.value),
779
+ placeholder: "e.g. Homepage Hero"
780
+ }
781
+ )
782
+ ] }) }),
783
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(Spacings2.Stack, { scale: "xs", children: [
784
+ /* @__PURE__ */ jsx3(Label2, { htmlFor: "create-content-type", children: "Content Type" }),
785
+ /* @__PURE__ */ jsx3(
786
+ TextInput2,
787
+ {
788
+ id: "create-content-type",
789
+ value: createType,
790
+ onChange: (e) => setCreateType(e.target.value),
791
+ placeholder: "e.g. hero, banner"
792
+ }
793
+ )
794
+ ] }) })
795
+ ] }),
796
+ /* @__PURE__ */ jsxs3(Spacings2.Inline, { scale: "s", children: [
797
+ /* @__PURE__ */ jsx3(
798
+ PrimaryButton2,
799
+ {
800
+ label: creating ? "Creating\u2026" : "Create",
801
+ onClick: () => void handleCreate(),
802
+ isDisabled: creating
803
+ }
804
+ ),
805
+ /* @__PURE__ */ jsx3(SecondaryButton2, { label: "Cancel", onClick: () => setShowCreate(false) })
806
+ ] })
807
+ ] }) }),
808
+ /* @__PURE__ */ jsxs3(Spacings2.Inline, { scale: "s", alignItems: "center", children: [
809
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, maxWidth: "280px" }, children: /* @__PURE__ */ jsx3(
810
+ TextInput2,
811
+ {
812
+ value: filterType,
813
+ onChange: (e) => setFilterType(e.target.value),
814
+ placeholder: "Filter by content type\u2026"
815
+ }
816
+ ) }),
817
+ /* @__PURE__ */ jsx3(
818
+ SecondaryButton2,
819
+ {
820
+ label: "Filter",
821
+ iconLeft: /* @__PURE__ */ jsx3(SearchIcon2, {}),
822
+ onClick: handleFilter
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsx3(
826
+ FlatButton2,
827
+ {
828
+ label: "Clear",
829
+ onClick: () => {
830
+ setFilterType("");
831
+ void fetchContents(void 0);
832
+ }
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsx3(FlatButton2, { label: "Refresh", onClick: () => void refresh() })
836
+ ] }),
837
+ error && /* @__PURE__ */ jsx3(Text2.Body, { tone: "negative", children: error }),
838
+ loading ? /* @__PURE__ */ jsx3("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx3(LoadingSpinner2, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx3(Spacings2.Stack, { scale: "m", alignItems: "center", children: /* @__PURE__ */ jsx3(Text2.Body, { tone: "secondary", children: "No content items found." }) }) : /* @__PURE__ */ jsx3(
839
+ DataTable2,
840
+ {
841
+ columns: COLUMNS,
842
+ rows,
843
+ itemRenderer: (row, column) => {
844
+ switch (column.key) {
845
+ case "name":
846
+ return /* @__PURE__ */ jsx3(Text2.Body, { isBold: true, children: row.value.name });
847
+ case "contentType":
848
+ return /* @__PURE__ */ jsx3(
849
+ "code",
850
+ {
851
+ style: {
852
+ background: "var(--color-neutral-95)",
853
+ padding: "2px 6px",
854
+ borderRadius: "var(--border-radius-4)",
855
+ fontSize: "var(--font-size-10)",
856
+ fontFamily: "monospace"
857
+ },
858
+ children: row.value.contentType
859
+ }
860
+ );
861
+ case "status": {
862
+ const hasDraft = !!row.states.draft;
863
+ const hasPublished = !!row.states.published;
864
+ return /* @__PURE__ */ jsxs3("span", { children: [
865
+ hasDraft && /* @__PURE__ */ jsx3(StatusBadge3, { variant: "draft" }),
866
+ hasPublished && /* @__PURE__ */ jsx3(StatusBadge3, { variant: "published" }),
867
+ !hasDraft && !hasPublished && /* @__PURE__ */ jsx3(StatusBadge3, { variant: "none" })
868
+ ] });
869
+ }
870
+ case "updatedAt":
871
+ return /* @__PURE__ */ jsx3(Text2.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleDateString() });
872
+ case "actions":
873
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
874
+ /* @__PURE__ */ jsx3(
875
+ PrimaryButton2,
876
+ {
877
+ label: "Edit",
878
+ size: "20",
879
+ onClick: () => navigate(`/${row.key}`, { state: { contentName: row.value.name } })
880
+ }
881
+ ),
882
+ /* @__PURE__ */ jsx3(
883
+ FlatButton2,
884
+ {
885
+ tone: "critical",
886
+ label: deleting === row.key ? "\u2026" : "Delete",
887
+ isDisabled: deleting === row.key,
888
+ onClick: () => void handleDelete(row.key)
889
+ }
890
+ )
891
+ ] });
892
+ default:
893
+ return null;
894
+ }
895
+ }
896
+ }
897
+ )
898
+ ] }) });
899
+ };
900
+ var ContentEditorRoute = ({ config, backButton }) => {
901
+ const { contentKey } = useParams();
902
+ const navigate = useNavigate();
903
+ const location = useLocation();
904
+ const contentName = location.state?.contentName ?? contentKey ?? "Content";
905
+ const {
906
+ content,
907
+ states,
908
+ saving,
909
+ loading,
910
+ error,
911
+ saveDraft,
912
+ publish,
913
+ revertToPublished
914
+ } = usePuckContent2(contentKey);
915
+ const latestDataRef = useRef2(null);
916
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState3(false);
917
+ const handleChange = useCallback2((data) => {
918
+ latestDataRef.current = data;
919
+ setHasUnsavedChanges(true);
920
+ }, []);
921
+ const handleSave = useCallback2(async () => {
922
+ const data = latestDataRef.current;
923
+ if (!data) return;
924
+ try {
925
+ await saveDraft(data);
926
+ setHasUnsavedChanges(false);
927
+ } catch (err) {
928
+ console.error("[ContentManagerRouter] save error:", err);
929
+ }
930
+ }, [saveDraft]);
931
+ const handlePublish = useCallback2(
932
+ async (data) => {
933
+ try {
934
+ await saveDraft(data);
935
+ setHasUnsavedChanges(false);
936
+ await publish(false);
937
+ } catch (err) {
938
+ console.error("[ContentManagerRouter] publish error:", err);
939
+ }
940
+ },
941
+ [saveDraft, publish]
942
+ );
943
+ const handleRevert = useCallback2(async () => {
944
+ try {
945
+ await revertToPublished();
946
+ setHasUnsavedChanges(false);
947
+ } catch (err) {
948
+ console.error("[ContentManagerRouter] revert error:", err);
949
+ }
950
+ }, [revertToPublished]);
951
+ const contentConfig = useMemo2(() => {
952
+ const otherRootFields = Object.fromEntries(
953
+ Object.entries(config.root?.fields ?? {}).filter(([k]) => k !== "title")
954
+ );
955
+ return {
956
+ ...config,
957
+ root: {
958
+ ...config.root,
959
+ fields: {
960
+ title: { type: "text", label: "Content Title" },
961
+ slot: { type: "text", label: "Slot" },
962
+ ...otherRootFields
963
+ },
964
+ defaultProps: {
965
+ title: "New Content",
966
+ slot: "",
967
+ ...config.root?.defaultProps
968
+ }
969
+ }
970
+ };
971
+ }, [config]);
972
+ if (loading) {
973
+ return /* @__PURE__ */ jsx3(
974
+ "div",
975
+ {
976
+ style: {
977
+ display: "flex",
978
+ alignItems: "center",
979
+ justifyContent: "center",
980
+ height: "100vh",
981
+ fontSize: "16px",
982
+ color: "var(--text-muted)"
983
+ },
984
+ children: "Loading editor\u2026"
985
+ }
986
+ );
987
+ }
988
+ if (error) {
989
+ return /* @__PURE__ */ jsxs3(
990
+ "div",
991
+ {
992
+ style: {
993
+ padding: "32px",
994
+ color: "var(--status-error)",
995
+ background: "rgba(248, 113, 113, 0.08)",
996
+ border: "1px solid rgba(248, 113, 113, 0.25)",
997
+ borderRadius: "8px",
998
+ margin: "16px"
999
+ },
1000
+ children: [
1001
+ /* @__PURE__ */ jsx3("strong", { children: "Error loading content:" }),
1002
+ " ",
1003
+ error
1004
+ ]
1005
+ }
1006
+ );
1007
+ }
1008
+ const activeData = states.draft?.data ?? content?.data ?? {
1009
+ content: [],
1010
+ root: { props: {} }
1011
+ };
1012
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
1013
+ /* @__PURE__ */ jsxs3("div", { style: NAV_BAR_STYLE, children: [
1014
+ backButton,
1015
+ backButton && /* @__PURE__ */ jsx3(Text2.Body, { tone: "secondary", children: "/" }),
1016
+ /* @__PURE__ */ jsx3(
1017
+ FlatButton2,
1018
+ {
1019
+ label: "Content Items",
1020
+ icon: /* @__PURE__ */ jsx3(AngleLeftIcon, {}),
1021
+ iconPosition: "left",
1022
+ onClick: () => navigate("/")
1023
+ }
1024
+ ),
1025
+ /* @__PURE__ */ jsx3(Text2.Body, { tone: "secondary", children: "/" }),
1026
+ /* @__PURE__ */ jsx3(Text2.Body, { isBold: true, children: contentName })
1027
+ ] }),
1028
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsx3(ComponentSearchProvider2, { children: /* @__PURE__ */ jsx3(
1029
+ Puck2,
1030
+ {
1031
+ config: contentConfig,
1032
+ data: activeData,
1033
+ onChange: handleChange,
1034
+ onPublish: handlePublish,
1035
+ overrides: {
1036
+ headerActions: () => /* @__PURE__ */ jsx3(
1037
+ ContentToolbar2,
1038
+ {
1039
+ saving,
1040
+ isDirty: hasUnsavedChanges,
1041
+ states,
1042
+ onSave: () => void handleSave(),
1043
+ onPublish: () => void handlePublish(activeData),
1044
+ onRevert: () => void handleRevert()
1045
+ }
1046
+ ),
1047
+ components: ({ children }) => /* @__PURE__ */ jsx3(ComponentsPanel2, { children }),
1048
+ componentItem: ({ children, name }) => /* @__PURE__ */ jsx3(ComponentItemFilter2, { name, children })
1049
+ }
1050
+ }
1051
+ ) }) })
1052
+ ] });
1053
+ };
1054
+ var ContentManagerRouterInner = ({
1055
+ config,
1056
+ defaultContentType,
1057
+ backButton
1058
+ }) => /* @__PURE__ */ jsxs3(Routes, { children: [
1059
+ /* @__PURE__ */ jsx3(
1060
+ Route,
1061
+ {
1062
+ path: "/",
1063
+ element: /* @__PURE__ */ jsx3(ContentListRoute, { defaultContentType, backButton })
1064
+ }
1065
+ ),
1066
+ /* @__PURE__ */ jsx3(
1067
+ Route,
1068
+ {
1069
+ path: "/:contentKey",
1070
+ element: /* @__PURE__ */ jsx3(ContentEditorRoute, { config, backButton })
1071
+ }
1072
+ )
1073
+ ] });
1074
+ var ContentManagerRouter = ({
1075
+ parentUrl,
1076
+ baseURL,
1077
+ projectKey,
1078
+ businessUnitKey,
1079
+ jwtToken,
1080
+ config,
1081
+ defaultContentType,
1082
+ backButton
1083
+ }) => /* @__PURE__ */ jsx3(
1084
+ PuckApiProvider3,
1085
+ {
1086
+ baseURL,
1087
+ projectKey,
1088
+ businessUnitKey,
1089
+ jwtToken,
1090
+ children: /* @__PURE__ */ jsx3(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx3(
1091
+ ContentManagerRouterInner,
1092
+ {
1093
+ config,
1094
+ defaultContentType,
1095
+ backButton
1096
+ }
1097
+ ) })
1098
+ }
1099
+ );
1100
+ export {
1101
+ ContentEditor,
1102
+ ContentManager,
1103
+ ContentManagerRouter
1104
+ };
1105
+ //# sourceMappingURL=index.mjs.map