@commercetools-demo/puck-content-manager 0.5.2 → 0.6.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 CHANGED
@@ -1,18 +1,26 @@
1
1
  // src/ContentManager.tsx
2
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 Stamp from "@commercetools-uikit/stamp";
3
+ import {
4
+ PuckApiProvider,
5
+ usePuckContents,
6
+ usePuckTemplates
7
+ } from "@commercetools-demo/puck-api";
8
+ import {
9
+ Badge,
10
+ Button,
11
+ Card,
12
+ DataTable,
13
+ Dialog,
14
+ FormField,
15
+ Icon,
16
+ IconButton,
17
+ LoadingSpinner,
18
+ Select,
19
+ Stack,
20
+ Text,
21
+ TextInput
22
+ } from "@commercetools/nimbus";
23
+ import { Add, Close, Delete, Edit } from "@commercetools/nimbus-icons";
16
24
 
17
25
  // src/EnsureIntlProvider.tsx
18
26
  import { useContext } from "react";
@@ -38,25 +46,28 @@ var EnsureIntlProvider = ({ children }) => {
38
46
  );
39
47
  };
40
48
 
49
+ // src/EnsureNimbusProvider.tsx
50
+ import { NimbusProvider } from "@commercetools/nimbus";
51
+ import { jsx as jsx2 } from "react/jsx-runtime";
52
+ var EnsureNimbusProvider = ({
53
+ locale = "en",
54
+ children
55
+ }) => /* @__PURE__ */ jsx2(NimbusProvider, { locale, children });
56
+
41
57
  // src/ContentManager.tsx
42
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
43
- var columns = [
44
- { key: "name", label: "Name" },
45
- { key: "contentType", label: "Content Type" },
46
- { key: "status", label: "Status" },
47
- { key: "updatedAt", label: "Updated" },
48
- { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
49
- ];
58
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
50
59
  var ContentList = ({ defaultContentType, onEdit }) => {
51
- const { contents, loading, error, fetchContents, createContent, deleteContent, refresh } = usePuckContents(defaultContentType);
52
- const [filterType, setFilterType] = useState(defaultContentType ?? "");
60
+ const { contents, loading, error, createContent, deleteContent } = usePuckContents(defaultContentType);
61
+ const { templates } = usePuckTemplates("content");
62
+ const [search, setSearch] = useState("");
53
63
  const [showCreate, setShowCreate] = useState(false);
54
64
  const [createName, setCreateName] = useState("");
55
65
  const [createType, setCreateType] = useState(defaultContentType ?? "");
66
+ const [templateKey, setTemplateKey] = useState("");
56
67
  const [createError, setCreateError] = useState(null);
57
68
  const [creating, setCreating] = useState(false);
58
69
  const [deleting, setDeleting] = useState(null);
59
- const handleFilter = () => void fetchContents(filterType || void 0);
70
+ const [pendingDelete, setPendingDelete] = useState(null);
60
71
  const handleCreate = async () => {
61
72
  setCreateError(null);
62
73
  if (!createName.trim()) {
@@ -69,168 +80,231 @@ var ContentList = ({ defaultContentType, onEdit }) => {
69
80
  }
70
81
  setCreating(true);
71
82
  try {
83
+ const template = templateKey ? templates.find((t) => t.key === templateKey) : void 0;
72
84
  const input = {
73
85
  name: createName.trim(),
74
86
  contentType: createType.trim(),
75
- data: { content: [], root: { props: {} } }
87
+ data: template?.value.puckData ?? { content: [], root: { props: {} } }
76
88
  };
77
89
  await createContent(input);
78
90
  setShowCreate(false);
79
91
  setCreateName("");
80
92
  setCreateType(defaultContentType ?? "");
93
+ setTemplateKey("");
81
94
  } catch (err) {
82
95
  setCreateError(err.message);
83
96
  } finally {
84
97
  setCreating(false);
85
98
  }
86
99
  };
87
- const handleDelete = async (key) => {
88
- if (!confirm("Delete this content item and all its versions?")) return;
89
- setDeleting(key);
100
+ const handleDelete = async (item) => {
101
+ setDeleting(item.key);
90
102
  try {
91
- await deleteContent(key);
103
+ await deleteContent(item.key);
104
+ setPendingDelete(null);
92
105
  } finally {
93
106
  setDeleting(null);
94
107
  }
95
108
  };
96
- const rows = contents.map((c) => ({ ...c, id: c.key }));
97
- return /* @__PURE__ */ jsx2("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "l", children: [
98
- /* @__PURE__ */ jsxs(Spacings.Inline, { justifyContent: "space-between", alignItems: "center", children: [
99
- /* @__PURE__ */ jsx2(Text.Headline, { as: "h1", children: "Content Items" }),
100
- /* @__PURE__ */ jsx2(
101
- PrimaryButton,
109
+ const term = search.trim().toLowerCase();
110
+ const filteredContents = term ? contents.filter(
111
+ (c) => c.value.name.toLowerCase().includes(term) || c.value.contentType.toLowerCase().includes(term)
112
+ ) : contents;
113
+ const rows = filteredContents.map((c) => ({ ...c, id: c.key }));
114
+ const columns = [
115
+ {
116
+ id: "name",
117
+ header: "Name",
118
+ accessor: (row) => row.value.name,
119
+ render: ({ row }) => /* @__PURE__ */ jsx3(Text, { fontWeight: "bold", children: row.value.name })
120
+ },
121
+ {
122
+ id: "contentType",
123
+ header: "Content Type",
124
+ accessor: (row) => row.value.contentType,
125
+ render: ({ row }) => /* @__PURE__ */ jsx3(
126
+ "code",
102
127
  {
103
- label: "New Content",
104
- iconLeft: /* @__PURE__ */ jsx2(PlusThinIcon, {}),
105
- onClick: () => setShowCreate((v) => !v)
128
+ style: {
129
+ background: "#f4f4f4",
130
+ padding: "2px 6px",
131
+ borderRadius: "4px",
132
+ fontSize: "11px",
133
+ fontFamily: "monospace"
134
+ },
135
+ children: row.value.contentType
106
136
  }
107
137
  )
108
- ] }),
109
- showCreate && /* @__PURE__ */ jsx2(Card, { insetScale: "l", children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "m", children: [
110
- /* @__PURE__ */ jsx2(Text.Subheadline, { as: "h4", isBold: true, children: "Create Content Item" }),
111
- createError && /* @__PURE__ */ jsx2(Text.Body, { tone: "negative", children: createError }),
112
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "m", children: [
113
- /* @__PURE__ */ jsx2("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
114
- /* @__PURE__ */ jsx2(Label, { htmlFor: "create-name", children: "Name" }),
115
- /* @__PURE__ */ jsx2(
116
- TextInput,
117
- {
118
- id: "create-name",
119
- value: createName,
120
- onChange: (e) => setCreateName(e.target.value),
121
- placeholder: "e.g. Homepage Hero"
122
- }
123
- )
124
- ] }) }),
125
- /* @__PURE__ */ jsx2("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
126
- /* @__PURE__ */ jsx2(Label, { htmlFor: "create-type", children: "Content Type" }),
127
- /* @__PURE__ */ jsx2(
128
- TextInput,
129
- {
130
- id: "create-type",
131
- value: createType,
132
- onChange: (e) => setCreateType(e.target.value),
133
- placeholder: "e.g. hero, banner"
134
- }
135
- )
136
- ] }) })
137
- ] }),
138
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", children: [
139
- /* @__PURE__ */ jsx2(
140
- PrimaryButton,
138
+ },
139
+ {
140
+ id: "status",
141
+ header: "Status",
142
+ accessor: () => "",
143
+ isSortable: false,
144
+ render: ({ row }) => {
145
+ const hasDraft = !!row.states.draft;
146
+ const hasPublished = !!row.states.published;
147
+ return /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "100", wrap: "wrap", children: [
148
+ hasDraft && /* @__PURE__ */ jsx3(Badge, { colorPalette: "warning", size: "xs", children: "Draft" }),
149
+ hasPublished && /* @__PURE__ */ jsx3(Badge, { colorPalette: "positive", size: "xs", children: "Published" }),
150
+ !hasDraft && !hasPublished && /* @__PURE__ */ jsx3(Badge, { colorPalette: "neutral", size: "xs", children: "No state" })
151
+ ] });
152
+ }
153
+ },
154
+ {
155
+ id: "updatedAt",
156
+ header: "Updated",
157
+ accessor: (row) => row.value.updatedAt,
158
+ render: ({ row }) => /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: new Date(row.value.updatedAt).toLocaleDateString() })
159
+ },
160
+ {
161
+ id: "actions",
162
+ header: "Actions",
163
+ accessor: () => "",
164
+ isSortable: false,
165
+ render: ({ row }) => /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "100", alignItems: "center", children: [
166
+ /* @__PURE__ */ jsx3(
167
+ IconButton,
141
168
  {
142
- label: creating ? "Creating\u2026" : "Create",
143
- onClick: () => void handleCreate(),
144
- isDisabled: creating
169
+ "aria-label": `Edit ${row.value.name}`,
170
+ variant: "ghost",
171
+ size: "xs",
172
+ onPress: () => onEdit(row),
173
+ children: /* @__PURE__ */ jsx3(Edit, {})
145
174
  }
146
175
  ),
147
- /* @__PURE__ */ jsx2(SecondaryButton, { label: "Cancel", onClick: () => setShowCreate(false) })
176
+ /* @__PURE__ */ jsx3(
177
+ IconButton,
178
+ {
179
+ "aria-label": `Delete ${row.value.name}`,
180
+ variant: "ghost",
181
+ colorPalette: "critical",
182
+ size: "xs",
183
+ isDisabled: deleting === row.key,
184
+ onPress: () => setPendingDelete(row),
185
+ children: /* @__PURE__ */ jsx3(Delete, {})
186
+ }
187
+ )
148
188
  ] })
149
- ] }) }),
150
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", alignItems: "center", children: [
151
- /* @__PURE__ */ jsx2("div", { style: { flex: 1, maxWidth: "280px" }, children: /* @__PURE__ */ jsx2(
189
+ }
190
+ ];
191
+ return /* @__PURE__ */ jsxs("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: [
192
+ /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: "600", children: [
193
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
194
+ /* @__PURE__ */ jsx3(Text, { as: "h1", fontSize: "2xl", fontWeight: "700", children: "Content Items" }),
195
+ /* @__PURE__ */ jsxs(Button, { variant: "solid", onPress: () => setShowCreate((v) => !v), children: [
196
+ /* @__PURE__ */ jsx3(Icon, { as: Add }),
197
+ " New Content"
198
+ ] })
199
+ ] }),
200
+ showCreate && /* @__PURE__ */ jsx3(Card.Root, { variant: "outlined", children: /* @__PURE__ */ jsx3(Card.Body, { children: /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: "400", children: [
201
+ /* @__PURE__ */ jsx3(Text, { as: "h4", fontSize: "xl", fontWeight: "700", children: "Create Content Item" }),
202
+ createError && /* @__PURE__ */ jsx3(Text, { color: "critical.11", children: createError }),
203
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "400", children: [
204
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(FormField.Root, { children: [
205
+ /* @__PURE__ */ jsx3(FormField.Label, { children: "Name" }),
206
+ /* @__PURE__ */ jsx3(FormField.Input, { children: /* @__PURE__ */ jsx3(
207
+ TextInput,
208
+ {
209
+ value: createName,
210
+ onChange: (v) => setCreateName(v),
211
+ placeholder: "e.g. Homepage Hero"
212
+ }
213
+ ) })
214
+ ] }) }),
215
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(FormField.Root, { children: [
216
+ /* @__PURE__ */ jsx3(FormField.Label, { children: "Content Type" }),
217
+ /* @__PURE__ */ jsx3(FormField.Input, { children: /* @__PURE__ */ jsx3(
218
+ TextInput,
219
+ {
220
+ value: createType,
221
+ onChange: (v) => setCreateType(v),
222
+ placeholder: "e.g. hero, banner"
223
+ }
224
+ ) })
225
+ ] }) })
226
+ ] }),
227
+ /* @__PURE__ */ jsxs(FormField.Root, { children: [
228
+ /* @__PURE__ */ jsx3(FormField.Label, { children: "Template" }),
229
+ /* @__PURE__ */ jsx3(FormField.Input, { children: /* @__PURE__ */ jsx3(
230
+ Select.Root,
231
+ {
232
+ "aria-label": "Template",
233
+ selectedKey: templateKey || "empty",
234
+ onSelectionChange: (key) => setTemplateKey(key == null || key === "empty" ? "" : String(key)),
235
+ children: /* @__PURE__ */ jsxs(Select.Options, { children: [
236
+ /* @__PURE__ */ jsx3(Select.Option, { id: "empty", children: "Empty" }),
237
+ templates.map((t) => /* @__PURE__ */ jsx3(Select.Option, { id: t.key, children: t.value.name }, t.key))
238
+ ] })
239
+ }
240
+ ) })
241
+ ] }),
242
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "200", children: [
243
+ /* @__PURE__ */ jsx3(Button, { variant: "solid", onPress: () => void handleCreate(), isDisabled: creating, children: creating ? "Creating\u2026" : "Create" }),
244
+ /* @__PURE__ */ jsx3(Button, { variant: "outline", onPress: () => setShowCreate(false), children: "Cancel" })
245
+ ] })
246
+ ] }) }) }),
247
+ /* @__PURE__ */ jsx3("div", { style: { maxWidth: 360 }, children: /* @__PURE__ */ jsx3(
152
248
  TextInput,
153
249
  {
154
- value: filterType,
155
- onChange: (e) => setFilterType(e.target.value),
156
- placeholder: "Filter by content type\u2026"
250
+ "aria-label": "Search content",
251
+ placeholder: "Search by name or content type\u2026",
252
+ value: search,
253
+ onChange: (v) => setSearch(v),
254
+ width: "100%",
255
+ trailingElement: search !== "" ? /* @__PURE__ */ jsx3(
256
+ IconButton,
257
+ {
258
+ "aria-label": "Clear search",
259
+ variant: "ghost",
260
+ colorPalette: "neutral",
261
+ size: "2xs",
262
+ onPress: () => setSearch(""),
263
+ children: /* @__PURE__ */ jsx3(Close, {})
264
+ }
265
+ ) : void 0
157
266
  }
158
267
  ) }),
159
- /* @__PURE__ */ jsx2(
160
- SecondaryButton,
161
- {
162
- label: "Filter",
163
- iconLeft: /* @__PURE__ */ jsx2(SearchIcon, {}),
164
- onClick: handleFilter
165
- }
166
- ),
167
- /* @__PURE__ */ jsx2(
168
- FlatButton,
169
- {
170
- label: "Clear",
171
- onClick: () => {
172
- setFilterType("");
173
- void fetchContents(void 0);
174
- }
175
- }
176
- ),
177
- /* @__PURE__ */ jsx2(FlatButton, { label: "Refresh", onClick: () => void refresh() })
268
+ error && /* @__PURE__ */ jsx3(Text, { color: "critical.11", children: error }),
269
+ loading ? /* @__PURE__ */ jsx3("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx3(LoadingSpinner, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx3(Stack, { direction: "column", gap: "400", alignItems: "center", children: /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: "No content items found." }) }) : /* @__PURE__ */ jsx3(DataTable, { columns, rows, "aria-label": "Content items" })
178
270
  ] }),
179
- error && /* @__PURE__ */ jsx2(Text.Body, { tone: "negative", children: error }),
180
- loading ? /* @__PURE__ */ jsx2("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx2(LoadingSpinner, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx2(Spacings.Stack, { scale: "m", alignItems: "center", children: /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: "No content items found." }) }) : /* @__PURE__ */ jsx2(
181
- DataTable,
271
+ /* @__PURE__ */ jsx3(
272
+ Dialog.Root,
182
273
  {
183
- columns,
184
- rows,
185
- itemRenderer: (row, column) => {
186
- switch (column.key) {
187
- case "name":
188
- return /* @__PURE__ */ jsx2(Text.Body, { fontWeight: "bold", children: row.value.name });
189
- case "contentType":
190
- return /* @__PURE__ */ jsx2(
191
- "code",
192
- {
193
- style: {
194
- background: "var(--color-neutral-95)",
195
- padding: "2px 6px",
196
- borderRadius: "var(--border-radius-4)",
197
- fontSize: "var(--font-size-10)",
198
- fontFamily: "monospace"
199
- },
200
- children: row.value.contentType
201
- }
202
- );
203
- case "status": {
204
- const hasDraft = !!row.states.draft;
205
- const hasPublished = !!row.states.published;
206
- return /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", gap: "4px", flexWrap: "wrap" }, children: [
207
- hasDraft && /* @__PURE__ */ jsx2(Stamp, { tone: "warning", label: "Draft", isCondensed: true }),
208
- hasPublished && /* @__PURE__ */ jsx2(Stamp, { tone: "positive", label: "Published", isCondensed: true }),
209
- !hasDraft && !hasPublished && /* @__PURE__ */ jsx2(Stamp, { tone: "secondary", label: "No state", isCondensed: true })
210
- ] });
211
- }
212
- case "updatedAt":
213
- return /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleDateString() });
214
- case "actions":
215
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
216
- /* @__PURE__ */ jsx2(PrimaryButton, { label: "Edit", size: "20", onClick: () => onEdit(row) }),
217
- /* @__PURE__ */ jsx2(
218
- FlatButton,
219
- {
220
- tone: "critical",
221
- label: deleting === row.key ? "\u2026" : "Delete",
222
- isDisabled: deleting === row.key,
223
- onClick: () => void handleDelete(row.key)
224
- }
225
- )
226
- ] });
227
- default:
228
- return null;
229
- }
230
- }
274
+ isOpen: pendingDelete !== null,
275
+ onOpenChange: (open) => {
276
+ if (!open) setPendingDelete(null);
277
+ },
278
+ children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
279
+ /* @__PURE__ */ jsxs(Dialog.Header, { children: [
280
+ /* @__PURE__ */ jsx3(Dialog.Title, { children: "Delete content item?" }),
281
+ /* @__PURE__ */ jsx3(Dialog.CloseTrigger, {})
282
+ ] }),
283
+ /* @__PURE__ */ jsx3(Dialog.Body, { children: /* @__PURE__ */ jsxs(Text, { children: [
284
+ "Are you sure you want to delete",
285
+ " ",
286
+ /* @__PURE__ */ jsx3(Text, { as: "span", fontWeight: "700", children: pendingDelete?.value.name }),
287
+ " ",
288
+ "and all its versions? This cannot be undone."
289
+ ] }) }),
290
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
291
+ /* @__PURE__ */ jsx3(Button, { slot: "close", variant: "outline", isDisabled: deleting !== null, children: "Cancel" }),
292
+ /* @__PURE__ */ jsx3(
293
+ Button,
294
+ {
295
+ colorPalette: "critical",
296
+ isDisabled: deleting !== null,
297
+ onPress: () => {
298
+ if (pendingDelete) void handleDelete(pendingDelete);
299
+ },
300
+ children: deleting !== null ? "Deleting\u2026" : "Delete"
301
+ }
302
+ )
303
+ ] })
304
+ ] })
231
305
  }
232
306
  )
233
- ] }) });
307
+ ] });
234
308
  };
235
309
  var ContentManagerList = ({
236
310
  baseURL,
@@ -239,27 +313,35 @@ var ContentManagerList = ({
239
313
  jwtToken,
240
314
  defaultContentType,
241
315
  onEdit
242
- }) => /* @__PURE__ */ jsx2(EnsureIntlProvider, { children: /* @__PURE__ */ jsx2(
316
+ }) => /* @__PURE__ */ jsx3(EnsureNimbusProvider, { children: /* @__PURE__ */ jsx3(EnsureIntlProvider, { children: /* @__PURE__ */ jsx3(
243
317
  PuckApiProvider,
244
318
  {
245
319
  baseURL,
246
320
  projectKey,
247
321
  businessUnitKey,
248
322
  jwtToken,
249
- children: /* @__PURE__ */ jsx2(ContentList, { defaultContentType, onEdit })
323
+ children: /* @__PURE__ */ jsx3(ContentList, { defaultContentType, onEdit })
250
324
  }
251
- ) });
325
+ ) }) });
252
326
 
253
327
  // src/ContentEditor.tsx
254
328
  import { useCallback, useMemo, useRef, useState as useState2 } from "react";
255
329
  import { Puck } from "@measured/puck";
256
330
  import "@measured/puck/puck.css";
257
- import { PuckApiProvider as PuckApiProvider2, usePuckContent } from "@commercetools-demo/puck-api";
331
+ import {
332
+ PuckApiProvider as PuckApiProvider2,
333
+ usePuckContent,
334
+ usePuckTemplates as usePuckTemplates2
335
+ } from "@commercetools-demo/puck-api";
258
336
  import {
259
337
  ComponentSearchProvider,
260
338
  ComponentsPanel,
261
339
  ComponentItemFilter,
262
- EditorToolbar
340
+ CreateTemplateDialog,
341
+ EditorToolbar,
342
+ nimbusFieldTypes,
343
+ stripPuckDataToTemplate,
344
+ useDirtyState
263
345
  } from "@commercetools-demo/puck-editor";
264
346
  import {
265
347
  VersionHistoryProvider,
@@ -269,10 +351,8 @@ import {
269
351
  useVersionHistoryPanel,
270
352
  useVersionDiff
271
353
  } from "@commercetools-demo/puck-version-history";
272
- import LoadingSpinner2 from "@commercetools-uikit/loading-spinner";
273
- import Text2 from "@commercetools-uikit/text";
274
- import Spacings2 from "@commercetools-uikit/spacings";
275
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
354
+ import { LoadingSpinner as LoadingSpinner2, Stack as Stack2, Text as Text2 } from "@commercetools/nimbus";
355
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
276
356
  var ContentEditorInner = ({
277
357
  contentKey,
278
358
  config,
@@ -292,9 +372,12 @@ var ContentEditorInner = ({
292
372
  revertToPublished,
293
373
  loadVersions
294
374
  } = usePuckContent(contentKey);
375
+ const { createTemplate } = usePuckTemplates2("content");
295
376
  const latestDataRef = useRef(null);
296
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState2(false);
297
377
  const [isApplyingVersion, setIsApplyingVersion] = useState2(false);
378
+ const [reloadNonce, setReloadNonce] = useState2(0);
379
+ const [templateDialogOpen, setTemplateDialogOpen] = useState2(false);
380
+ const [templateSaving, setTemplateSaving] = useState2(false);
298
381
  const currentData = states.draft?.data ?? content?.data ?? {
299
382
  content: [],
300
383
  root: { props: {} }
@@ -310,39 +393,44 @@ var ContentEditorInner = ({
310
393
  );
311
394
  const isPreviewingRef = useRef(false);
312
395
  isPreviewingRef.current = versionHistory.isPreviewingHistory;
313
- const handleChange = useCallback((data) => {
314
- if (isPreviewingRef.current) return;
315
- latestDataRef.current = data;
316
- setHasUnsavedChanges(true);
317
- }, []);
396
+ const canvasKey = `${contentKey}:${versionHistory.selectedVersionId ?? "current"}:${reloadNonce}`;
397
+ const { isDirty: hasUnsavedChanges, markChange, markSaved } = useDirtyState(canvasKey);
398
+ const handleChange = useCallback(
399
+ (data) => {
400
+ if (isPreviewingRef.current) return;
401
+ latestDataRef.current = data;
402
+ markChange(data);
403
+ },
404
+ [markChange]
405
+ );
318
406
  const handleSave = useCallback(async () => {
319
407
  const data = latestDataRef.current;
320
408
  if (!data) return;
321
409
  try {
322
410
  await saveDraft(data);
323
- setHasUnsavedChanges(false);
411
+ markSaved(data);
324
412
  onSave?.(data);
325
413
  } catch (err) {
326
414
  onError?.(err);
327
415
  }
328
- }, [saveDraft, onSave, onError]);
416
+ }, [saveDraft, onSave, onError, markSaved]);
329
417
  const handlePublish = useCallback(
330
418
  async (data) => {
331
419
  try {
332
420
  await saveDraft(data);
333
- setHasUnsavedChanges(false);
421
+ markSaved(data);
334
422
  await publish(false);
335
423
  onPublish?.(data);
336
424
  } catch (err) {
337
425
  onError?.(err);
338
426
  }
339
427
  },
340
- [saveDraft, publish, onPublish, onError]
428
+ [saveDraft, publish, onPublish, onError, markSaved]
341
429
  );
342
430
  const handleRevert = useCallback(async () => {
343
431
  try {
344
432
  await revertToPublished();
345
- setHasUnsavedChanges(false);
433
+ setReloadNonce((n) => n + 1);
346
434
  } catch (err) {
347
435
  onError?.(err);
348
436
  }
@@ -353,7 +441,7 @@ var ContentEditorInner = ({
353
441
  setIsApplyingVersion(true);
354
442
  try {
355
443
  await saveDraft(versionData);
356
- setHasUnsavedChanges(false);
444
+ markSaved(versionData);
357
445
  onSave?.(versionData);
358
446
  versionHistory.clearSelection();
359
447
  } catch (err) {
@@ -361,7 +449,7 @@ var ContentEditorInner = ({
361
449
  } finally {
362
450
  setIsApplyingVersion(false);
363
451
  }
364
- }, [versionHistory, saveDraft, onSave, onError]);
452
+ }, [versionHistory, saveDraft, onSave, onError, markSaved]);
365
453
  const contentConfig = useMemo(() => {
366
454
  const otherRootFields = Object.fromEntries(
367
455
  Object.entries(config.root?.fields ?? {}).filter(([k]) => k !== "title")
@@ -383,22 +471,38 @@ var ContentEditorInner = ({
383
471
  }
384
472
  };
385
473
  }, [config]);
474
+ const handleCreateTemplate = useCallback(
475
+ async (name, withoutData) => {
476
+ const source = latestDataRef.current ?? currentData;
477
+ const puckData = withoutData ? stripPuckDataToTemplate(source, contentConfig) : source;
478
+ setTemplateSaving(true);
479
+ try {
480
+ await createTemplate({ name, kind: "content", puckData });
481
+ setTemplateDialogOpen(false);
482
+ } catch (err) {
483
+ onError?.(err);
484
+ } finally {
485
+ setTemplateSaving(false);
486
+ }
487
+ },
488
+ [createTemplate, currentData, contentConfig, onError]
489
+ );
386
490
  if (loading) {
387
- return /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }, children: /* @__PURE__ */ jsxs2(Spacings2.Stack, { scale: "m", alignItems: "center", children: [
388
- /* @__PURE__ */ jsx3(LoadingSpinner2, {}),
389
- /* @__PURE__ */ jsx3(Text2.Body, { tone: "secondary", children: "Loading editor\u2026" })
491
+ return /* @__PURE__ */ jsx4("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }, children: /* @__PURE__ */ jsxs2(Stack2, { direction: "column", gap: "400", alignItems: "center", children: [
492
+ /* @__PURE__ */ jsx4(LoadingSpinner2, {}),
493
+ /* @__PURE__ */ jsx4(Text2, { color: "neutral.11", children: "Loading editor\u2026" })
390
494
  ] }) });
391
495
  }
392
496
  if (error) {
393
- return /* @__PURE__ */ jsx3("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs2(Text2.Body, { tone: "negative", children: [
394
- /* @__PURE__ */ jsx3("strong", { children: "Error loading content:" }),
497
+ return /* @__PURE__ */ jsx4("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs2(Text2, { color: "critical.11", children: [
498
+ /* @__PURE__ */ jsx4("strong", { children: "Error loading content:" }),
395
499
  " ",
396
500
  error
397
501
  ] }) });
398
502
  }
399
503
  const activeData = versionHistory.previewData ?? currentData;
400
504
  const toolbarStates = states;
401
- return /* @__PURE__ */ jsx3(
505
+ return /* @__PURE__ */ jsx4(
402
506
  VersionHistoryProvider,
403
507
  {
404
508
  diff,
@@ -411,44 +515,70 @@ var ContentEditorInner = ({
411
515
  onApply: () => void handleApplyVersion(),
412
516
  onDiscard: versionHistory.clearSelection,
413
517
  onLoadVersions: versionHistory.openPanel,
414
- children: /* @__PURE__ */ jsx3(ComponentSearchProvider, { children: /* @__PURE__ */ jsx3(
415
- Puck,
416
- {
417
- config: contentConfig,
418
- data: activeData,
419
- onChange: handleChange,
420
- onPublish: handlePublish,
421
- overrides: {
422
- headerActions: () => versionHistory.isPreviewingHistory ? /* @__PURE__ */ jsxs2(Spacings2.Inline, { scale: "s", alignItems: "center", children: [
423
- /* @__PURE__ */ jsx3(
424
- VersionPreviewBanner,
518
+ children: /* @__PURE__ */ jsxs2(ComponentSearchProvider, { children: [
519
+ /* @__PURE__ */ jsx4(
520
+ Puck,
521
+ {
522
+ config: contentConfig,
523
+ data: activeData,
524
+ onChange: handleChange,
525
+ onPublish: handlePublish,
526
+ overrides: {
527
+ fieldTypes: nimbusFieldTypes,
528
+ header: () => versionHistory.isPreviewingHistory ? /* @__PURE__ */ jsxs2(
529
+ Stack2,
530
+ {
531
+ gridArea: "header",
532
+ direction: "row",
533
+ gap: "200",
534
+ alignItems: "center",
535
+ justifyContent: "flex-end",
536
+ padding: "200",
537
+ children: [
538
+ /* @__PURE__ */ jsx4(
539
+ VersionPreviewBanner,
540
+ {
541
+ timestamp: versionHistory.selectedVersion.timestamp,
542
+ onApply: () => void handleApplyVersion(),
543
+ onDiscard: versionHistory.clearSelection,
544
+ isApplying: isApplyingVersion
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsx4(VersionHistoryButton, { disabled: isApplyingVersion })
548
+ ]
549
+ }
550
+ ) : /* @__PURE__ */ jsx4(
551
+ EditorToolbar,
425
552
  {
426
- timestamp: versionHistory.selectedVersion.timestamp,
427
- onApply: () => void handleApplyVersion(),
428
- onDiscard: versionHistory.clearSelection,
429
- isApplying: isApplyingVersion
553
+ title: content?.name ?? "Content",
554
+ saving,
555
+ isDirty: hasUnsavedChanges,
556
+ states: toolbarStates,
557
+ onSave: () => void handleSave(),
558
+ onPublish: () => void handlePublish(activeData),
559
+ onRevert: () => void handleRevert(),
560
+ onCreateTemplate: () => setTemplateDialogOpen(true),
561
+ createTemplateLabel: "Create a template from this content",
562
+ showPublishButton: true
430
563
  }
431
564
  ),
432
- /* @__PURE__ */ jsx3(VersionHistoryButton, { disabled: isApplyingVersion })
433
- ] }) : /* @__PURE__ */ jsx3(
434
- EditorToolbar,
435
- {
436
- saving,
437
- isDirty: hasUnsavedChanges,
438
- states: toolbarStates,
439
- onSave: () => void handleSave(),
440
- onPublish: () => void handlePublish(activeData),
441
- onRevert: () => void handleRevert(),
442
- showPublishButton: true
443
- }
444
- ),
445
- components: ({ children }) => /* @__PURE__ */ jsx3(ComponentsPanel, { children }),
446
- componentItem: ({ children, name }) => /* @__PURE__ */ jsx3(ComponentItemFilter, { name, children }),
447
- fields: ({ children, isLoading }) => /* @__PURE__ */ jsx3(VersionAwareFieldsPanel, { isLoading, children })
565
+ components: ({ children }) => /* @__PURE__ */ jsx4(ComponentsPanel, { children }),
566
+ componentItem: ({ children, name }) => /* @__PURE__ */ jsx4(ComponentItemFilter, { name, children }),
567
+ fields: ({ children, isLoading }) => /* @__PURE__ */ jsx4(VersionAwareFieldsPanel, { isLoading, children })
568
+ }
569
+ },
570
+ `${versionHistory.selectedVersionId ?? "current"}:${reloadNonce}`
571
+ ),
572
+ /* @__PURE__ */ jsx4(
573
+ CreateTemplateDialog,
574
+ {
575
+ isOpen: templateDialogOpen,
576
+ onOpenChange: (open) => setTemplateDialogOpen(open),
577
+ onConfirm: handleCreateTemplate,
578
+ saving: templateSaving
448
579
  }
449
- },
450
- versionHistory.selectedVersionId ?? "current"
451
- ) })
580
+ )
581
+ ] })
452
582
  }
453
583
  );
454
584
  };
@@ -457,19 +587,21 @@ var ContentEditor = ({
457
587
  projectKey,
458
588
  businessUnitKey,
459
589
  jwtToken,
590
+ locale,
460
591
  contentKey,
461
592
  config,
462
593
  onPublish,
463
594
  onSave,
464
595
  onError
465
- }) => /* @__PURE__ */ jsx3(EnsureIntlProvider, { children: /* @__PURE__ */ jsx3(
596
+ }) => /* @__PURE__ */ jsx4(EnsureNimbusProvider, { locale, children: /* @__PURE__ */ jsx4(EnsureIntlProvider, { children: /* @__PURE__ */ jsx4(
466
597
  PuckApiProvider2,
467
598
  {
468
599
  baseURL,
469
600
  projectKey,
470
601
  businessUnitKey,
471
602
  jwtToken,
472
- children: /* @__PURE__ */ jsx3(
603
+ locale,
604
+ children: /* @__PURE__ */ jsx4(
473
605
  ContentEditorInner,
474
606
  {
475
607
  contentKey,
@@ -480,7 +612,7 @@ var ContentEditor = ({
480
612
  }
481
613
  )
482
614
  }
483
- ) });
615
+ ) }) });
484
616
 
485
617
  // src/ContentManagerRouter.tsx
486
618
  import { useState as useState3, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2 } from "react";
@@ -498,9 +630,16 @@ import {
498
630
  ComponentSearchProvider as ComponentSearchProvider2,
499
631
  ComponentsPanel as ComponentsPanel2,
500
632
  ComponentItemFilter as ComponentItemFilter2,
633
+ CreateTemplateDialog as CreateTemplateDialog2,
501
634
  defaultPuckConfig,
502
- EditorToolbar as EditorToolbar2
635
+ EditorToolbar as EditorToolbar2,
636
+ nimbusFieldTypes as nimbusFieldTypes2,
637
+ PropertiesResizer,
638
+ stripPuckDataToTemplate as stripPuckDataToTemplate2,
639
+ UnsavedChangesDialog,
640
+ useDirtyState as useDirtyState2
503
641
  } from "@commercetools-demo/puck-editor";
642
+ import { PuckRenderer } from "@commercetools-demo/puck-renderer";
504
643
  import {
505
644
  VersionHistoryProvider as VersionHistoryProvider2,
506
645
  VersionHistoryButton as VersionHistoryButton2,
@@ -512,21 +651,33 @@ import {
512
651
  import {
513
652
  PuckApiProvider as PuckApiProvider3,
514
653
  usePuckContents as usePuckContents2,
515
- usePuckContent as usePuckContent2
654
+ usePuckContent as usePuckContent2,
655
+ usePuckTemplates as usePuckTemplates3
516
656
  } from "@commercetools-demo/puck-api";
517
- import DataTable2 from "@commercetools-uikit/data-table";
518
- import PrimaryButton2 from "@commercetools-uikit/primary-button";
519
- import SecondaryButton2 from "@commercetools-uikit/secondary-button";
520
- import FlatButton2 from "@commercetools-uikit/flat-button";
521
- import Card2 from "@commercetools-uikit/card";
522
- import Spacings3 from "@commercetools-uikit/spacings";
523
- import Text3 from "@commercetools-uikit/text";
524
- import LoadingSpinner3 from "@commercetools-uikit/loading-spinner";
525
- import TextInput2 from "@commercetools-uikit/text-input";
526
- import Label2 from "@commercetools-uikit/label";
527
- import Stamp2 from "@commercetools-uikit/stamp";
528
- import { PlusThinIcon as PlusThinIcon2, SearchIcon as SearchIcon2, AngleLeftIcon } from "@commercetools-uikit/icons";
529
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
657
+ import {
658
+ Badge as Badge2,
659
+ Button as Button2,
660
+ Card as Card2,
661
+ DataTable as DataTable2,
662
+ Dialog as Dialog2,
663
+ FormField as FormField2,
664
+ Icon as Icon2,
665
+ IconButton as IconButton2,
666
+ LoadingSpinner as LoadingSpinner3,
667
+ Select as Select2,
668
+ Stack as Stack3,
669
+ Text as Text3,
670
+ TextInput as TextInput2
671
+ } from "@commercetools/nimbus";
672
+ import {
673
+ Add as Add2,
674
+ ChevronLeft,
675
+ Close as Close2,
676
+ Delete as Delete2,
677
+ Edit as Edit2,
678
+ Visibility
679
+ } from "@commercetools/nimbus-icons";
680
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
530
681
  var DEFAULT_CONFIG = {
531
682
  ...defaultPuckConfig,
532
683
  components: { ...defaultPuckConfig.components }
@@ -539,28 +690,23 @@ var NAV_BAR_STYLE = {
539
690
  gap: "12px",
540
691
  padding: "8px 16px",
541
692
  background: "var(--color-surface, #fff)",
542
- borderBottom: "1px solid var(--color-neutral-90)",
693
+ borderBottom: "1px solid var(--color-neutral-90, #e0e0e0)",
543
694
  zIndex: 200,
544
695
  flexShrink: 0
545
696
  };
546
- var COLUMNS = [
547
- { key: "name", label: "Name" },
548
- { key: "contentType", label: "Content Type" },
549
- { key: "status", label: "Status" },
550
- { key: "updatedAt", label: "Updated" },
551
- { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
552
- ];
553
697
  var ContentListRoute = ({ defaultContentType, backButton }) => {
554
698
  const history = useHistory();
555
- const { contents, loading, error, fetchContents, createContent, deleteContent, refresh } = usePuckContents2(defaultContentType);
556
- const [filterType, setFilterType] = useState3(defaultContentType ?? "");
699
+ const { contents, loading, error, createContent, deleteContent } = usePuckContents2(defaultContentType);
700
+ const { templates } = usePuckTemplates3("content");
701
+ const [search, setSearch] = useState3("");
557
702
  const [showCreate, setShowCreate] = useState3(false);
558
703
  const [createName, setCreateName] = useState3("");
559
704
  const [createType, setCreateType] = useState3(defaultContentType ?? "");
705
+ const [templateKey, setTemplateKey] = useState3("");
560
706
  const [createError, setCreateError] = useState3(null);
561
707
  const [creating, setCreating] = useState3(false);
562
708
  const [deleting, setDeleting] = useState3(null);
563
- const handleFilter = () => void fetchContents(filterType || void 0);
709
+ const [pendingDelete, setPendingDelete] = useState3(null);
564
710
  const handleCreate = async () => {
565
711
  setCreateError(null);
566
712
  if (!createName.trim()) {
@@ -573,15 +719,17 @@ var ContentListRoute = ({ defaultContentType, backButton }) => {
573
719
  }
574
720
  setCreating(true);
575
721
  try {
722
+ const template = templateKey ? templates.find((t) => t.key === templateKey) : void 0;
576
723
  const input = {
577
724
  name: createName.trim(),
578
725
  contentType: createType.trim(),
579
- data: { content: [], root: { props: {} } }
726
+ data: template?.value.puckData ?? { content: [], root: { props: {} } }
580
727
  };
581
728
  const created = await createContent(input);
582
729
  setShowCreate(false);
583
730
  setCreateName("");
584
731
  setCreateType(defaultContentType ?? "");
732
+ setTemplateKey("");
585
733
  history.push(`/${created.key}`, { contentName: created.value.name });
586
734
  } catch (err) {
587
735
  setCreateError(err.message);
@@ -589,163 +737,227 @@ var ContentListRoute = ({ defaultContentType, backButton }) => {
589
737
  setCreating(false);
590
738
  }
591
739
  };
592
- const handleDelete = async (key) => {
593
- if (!confirm("Delete this content item and all its versions?")) return;
594
- setDeleting(key);
740
+ const handleDelete = async (item) => {
741
+ setDeleting(item.key);
595
742
  try {
596
- await deleteContent(key);
743
+ await deleteContent(item.key);
744
+ setPendingDelete(null);
597
745
  } finally {
598
746
  setDeleting(null);
599
747
  }
600
748
  };
601
- const rows = contents.map((c) => ({ ...c, id: c.key }));
602
- return /* @__PURE__ */ jsx4("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "l", children: [
603
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { justifyContent: "space-between", alignItems: "center", children: [
604
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "m", alignItems: "center", children: [
605
- backButton,
606
- /* @__PURE__ */ jsx4(Text3.Headline, { as: "h1", children: "Content Items" })
607
- ] }),
608
- /* @__PURE__ */ jsx4(
609
- PrimaryButton2,
749
+ const term = search.trim().toLowerCase();
750
+ const filteredContents = term ? contents.filter(
751
+ (c) => c.value.name.toLowerCase().includes(term) || c.value.contentType.toLowerCase().includes(term)
752
+ ) : contents;
753
+ const rows = filteredContents.map((c) => ({ ...c, id: c.key }));
754
+ const columns = [
755
+ {
756
+ id: "name",
757
+ header: "Name",
758
+ accessor: (row) => row.value.name,
759
+ render: ({ row }) => /* @__PURE__ */ jsx5(Text3, { fontWeight: "bold", children: row.value.name })
760
+ },
761
+ {
762
+ id: "contentType",
763
+ header: "Content Type",
764
+ accessor: (row) => row.value.contentType,
765
+ render: ({ row }) => /* @__PURE__ */ jsx5(
766
+ "code",
610
767
  {
611
- label: "New Content",
612
- iconLeft: /* @__PURE__ */ jsx4(PlusThinIcon2, {}),
613
- onClick: () => setShowCreate((v) => !v)
768
+ style: {
769
+ background: "#f4f4f4",
770
+ padding: "2px 6px",
771
+ borderRadius: "4px",
772
+ fontSize: "11px",
773
+ fontFamily: "monospace"
774
+ },
775
+ children: row.value.contentType
614
776
  }
615
777
  )
616
- ] }),
617
- showCreate && /* @__PURE__ */ jsx4(Card2, { insetScale: "l", children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "m", children: [
618
- /* @__PURE__ */ jsx4(Text3.Subheadline, { as: "h4", isBold: true, children: "Create Content Item" }),
619
- createError && /* @__PURE__ */ jsx4(Text3.Body, { tone: "negative", children: createError }),
620
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "m", children: [
621
- /* @__PURE__ */ jsx4("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "xs", children: [
622
- /* @__PURE__ */ jsx4(Label2, { htmlFor: "create-content-name", children: "Name" }),
623
- /* @__PURE__ */ jsx4(
624
- TextInput2,
625
- {
626
- id: "create-content-name",
627
- value: createName,
628
- onChange: (e) => setCreateName(e.target.value),
629
- placeholder: "e.g. Homepage Hero"
630
- }
631
- )
632
- ] }) }),
633
- /* @__PURE__ */ jsx4("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "xs", children: [
634
- /* @__PURE__ */ jsx4(Label2, { htmlFor: "create-content-type", children: "Content Type" }),
635
- /* @__PURE__ */ jsx4(
636
- TextInput2,
637
- {
638
- id: "create-content-type",
639
- value: createType,
640
- onChange: (e) => setCreateType(e.target.value),
641
- placeholder: "e.g. hero, banner"
642
- }
643
- )
644
- ] }) })
645
- ] }),
646
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", children: [
647
- /* @__PURE__ */ jsx4(
648
- PrimaryButton2,
778
+ },
779
+ {
780
+ id: "status",
781
+ header: "Status",
782
+ accessor: () => "",
783
+ isSortable: false,
784
+ render: ({ row }) => {
785
+ const hasDraft = !!row.states.draft;
786
+ const hasPublished = !!row.states.published;
787
+ return /* @__PURE__ */ jsxs3(Stack3, { direction: "row", gap: "100", wrap: "wrap", children: [
788
+ hasDraft && /* @__PURE__ */ jsx5(Badge2, { colorPalette: "warning", size: "xs", children: "Draft" }),
789
+ hasPublished && /* @__PURE__ */ jsx5(Badge2, { colorPalette: "positive", size: "xs", children: "Published" }),
790
+ !hasDraft && !hasPublished && /* @__PURE__ */ jsx5(Badge2, { colorPalette: "neutral", size: "xs", children: "No state" })
791
+ ] });
792
+ }
793
+ },
794
+ {
795
+ id: "updatedAt",
796
+ header: "Updated",
797
+ accessor: (row) => row.value.updatedAt,
798
+ render: ({ row }) => /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: new Date(row.value.updatedAt).toLocaleDateString() })
799
+ },
800
+ {
801
+ id: "actions",
802
+ header: "Actions",
803
+ accessor: () => "",
804
+ isSortable: false,
805
+ render: ({ row }) => /* @__PURE__ */ jsxs3(Stack3, { direction: "row", gap: "100", alignItems: "center", children: [
806
+ /* @__PURE__ */ jsx5(
807
+ IconButton2,
808
+ {
809
+ "aria-label": `Edit ${row.value.name}`,
810
+ variant: "ghost",
811
+ size: "xs",
812
+ onPress: () => history.push(`/${row.key}`, { contentName: row.value.name }),
813
+ children: /* @__PURE__ */ jsx5(Edit2, {})
814
+ }
815
+ ),
816
+ /* @__PURE__ */ jsx5(
817
+ IconButton2,
649
818
  {
650
- label: creating ? "Creating\u2026" : "Create",
651
- onClick: () => void handleCreate(),
652
- isDisabled: creating
819
+ "aria-label": `Preview ${row.value.name}`,
820
+ variant: "ghost",
821
+ size: "xs",
822
+ onPress: () => history.push(`/${row.key}/preview`, { contentName: row.value.name }),
823
+ children: /* @__PURE__ */ jsx5(Visibility, {})
653
824
  }
654
825
  ),
655
- /* @__PURE__ */ jsx4(SecondaryButton2, { label: "Cancel", onClick: () => setShowCreate(false) })
826
+ /* @__PURE__ */ jsx5(
827
+ IconButton2,
828
+ {
829
+ "aria-label": `Delete ${row.value.name}`,
830
+ variant: "ghost",
831
+ colorPalette: "critical",
832
+ size: "xs",
833
+ isDisabled: deleting === row.key,
834
+ onPress: () => setPendingDelete(row),
835
+ children: /* @__PURE__ */ jsx5(Delete2, {})
836
+ }
837
+ )
656
838
  ] })
657
- ] }) }),
658
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", alignItems: "center", children: [
659
- /* @__PURE__ */ jsx4("div", { style: { flex: 1, maxWidth: "280px" }, children: /* @__PURE__ */ jsx4(
839
+ }
840
+ ];
841
+ return /* @__PURE__ */ jsxs3("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: [
842
+ /* @__PURE__ */ jsxs3(Stack3, { direction: "column", gap: "600", children: [
843
+ /* @__PURE__ */ jsxs3(Stack3, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
844
+ /* @__PURE__ */ jsxs3(Stack3, { direction: "row", gap: "400", alignItems: "center", children: [
845
+ backButton,
846
+ /* @__PURE__ */ jsx5(Text3, { as: "h1", fontSize: "2xl", fontWeight: "700", children: "Content Items" })
847
+ ] }),
848
+ /* @__PURE__ */ jsxs3(Button2, { variant: "solid", onPress: () => setShowCreate((v) => !v), children: [
849
+ /* @__PURE__ */ jsx5(Icon2, { as: Add2 }),
850
+ " New Content"
851
+ ] })
852
+ ] }),
853
+ showCreate && /* @__PURE__ */ jsx5(Card2.Root, { variant: "outlined", children: /* @__PURE__ */ jsx5(Card2.Body, { children: /* @__PURE__ */ jsxs3(Stack3, { direction: "column", gap: "400", children: [
854
+ /* @__PURE__ */ jsx5(Text3, { as: "h4", fontSize: "xl", fontWeight: "700", children: "Create Content Item" }),
855
+ createError && /* @__PURE__ */ jsx5(Text3, { color: "critical.11", children: createError }),
856
+ /* @__PURE__ */ jsxs3(Stack3, { direction: "row", gap: "400", children: [
857
+ /* @__PURE__ */ jsx5("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(FormField2.Root, { children: [
858
+ /* @__PURE__ */ jsx5(FormField2.Label, { children: "Name" }),
859
+ /* @__PURE__ */ jsx5(FormField2.Input, { children: /* @__PURE__ */ jsx5(
860
+ TextInput2,
861
+ {
862
+ value: createName,
863
+ onChange: (v) => setCreateName(v),
864
+ placeholder: "e.g. Homepage Hero"
865
+ }
866
+ ) })
867
+ ] }) }),
868
+ /* @__PURE__ */ jsx5("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(FormField2.Root, { children: [
869
+ /* @__PURE__ */ jsx5(FormField2.Label, { children: "Content Type" }),
870
+ /* @__PURE__ */ jsx5(FormField2.Input, { children: /* @__PURE__ */ jsx5(
871
+ TextInput2,
872
+ {
873
+ value: createType,
874
+ onChange: (v) => setCreateType(v),
875
+ placeholder: "e.g. hero, banner"
876
+ }
877
+ ) })
878
+ ] }) })
879
+ ] }),
880
+ /* @__PURE__ */ jsxs3(FormField2.Root, { children: [
881
+ /* @__PURE__ */ jsx5(FormField2.Label, { children: "Template" }),
882
+ /* @__PURE__ */ jsx5(FormField2.Input, { children: /* @__PURE__ */ jsx5(
883
+ Select2.Root,
884
+ {
885
+ "aria-label": "Template",
886
+ selectedKey: templateKey || "empty",
887
+ onSelectionChange: (key) => setTemplateKey(key == null || key === "empty" ? "" : String(key)),
888
+ children: /* @__PURE__ */ jsxs3(Select2.Options, { children: [
889
+ /* @__PURE__ */ jsx5(Select2.Option, { id: "empty", children: "Empty" }),
890
+ templates.map((t) => /* @__PURE__ */ jsx5(Select2.Option, { id: t.key, children: t.value.name }, t.key))
891
+ ] })
892
+ }
893
+ ) })
894
+ ] }),
895
+ /* @__PURE__ */ jsxs3(Stack3, { direction: "row", gap: "200", children: [
896
+ /* @__PURE__ */ jsx5(Button2, { variant: "solid", onPress: () => void handleCreate(), isDisabled: creating, children: creating ? "Creating\u2026" : "Create" }),
897
+ /* @__PURE__ */ jsx5(Button2, { variant: "outline", onPress: () => setShowCreate(false), children: "Cancel" })
898
+ ] })
899
+ ] }) }) }),
900
+ /* @__PURE__ */ jsx5("div", { style: { maxWidth: 360 }, children: /* @__PURE__ */ jsx5(
660
901
  TextInput2,
661
902
  {
662
- value: filterType,
663
- onChange: (e) => setFilterType(e.target.value),
664
- placeholder: "Filter by content type\u2026"
903
+ "aria-label": "Search content",
904
+ placeholder: "Search by name or content type\u2026",
905
+ value: search,
906
+ onChange: (v) => setSearch(v),
907
+ width: "100%",
908
+ trailingElement: search !== "" ? /* @__PURE__ */ jsx5(
909
+ IconButton2,
910
+ {
911
+ "aria-label": "Clear search",
912
+ variant: "ghost",
913
+ colorPalette: "neutral",
914
+ size: "2xs",
915
+ onPress: () => setSearch(""),
916
+ children: /* @__PURE__ */ jsx5(Close2, {})
917
+ }
918
+ ) : void 0
665
919
  }
666
920
  ) }),
667
- /* @__PURE__ */ jsx4(
668
- SecondaryButton2,
669
- {
670
- label: "Filter",
671
- iconLeft: /* @__PURE__ */ jsx4(SearchIcon2, {}),
672
- onClick: handleFilter
673
- }
674
- ),
675
- /* @__PURE__ */ jsx4(
676
- FlatButton2,
677
- {
678
- label: "Clear",
679
- onClick: () => {
680
- setFilterType("");
681
- void fetchContents(void 0);
682
- }
683
- }
684
- ),
685
- /* @__PURE__ */ jsx4(FlatButton2, { label: "Refresh", onClick: () => void refresh() })
921
+ error && /* @__PURE__ */ jsx5(Text3, { color: "critical.11", children: error }),
922
+ loading ? /* @__PURE__ */ jsx5("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx5(LoadingSpinner3, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx5(Stack3, { direction: "column", gap: "400", alignItems: "center", children: /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: "No content items found." }) }) : /* @__PURE__ */ jsx5(DataTable2, { columns, rows, "aria-label": "Content items" })
686
923
  ] }),
687
- error && /* @__PURE__ */ jsx4(Text3.Body, { tone: "negative", children: error }),
688
- loading ? /* @__PURE__ */ jsx4("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx4(LoadingSpinner3, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx4(Spacings3.Stack, { scale: "m", alignItems: "center", children: /* @__PURE__ */ jsx4(Text3.Body, { tone: "secondary", children: "No content items found." }) }) : /* @__PURE__ */ jsx4(
689
- DataTable2,
924
+ /* @__PURE__ */ jsx5(
925
+ Dialog2.Root,
690
926
  {
691
- columns: COLUMNS,
692
- rows,
693
- itemRenderer: (row, column) => {
694
- switch (column.key) {
695
- case "name":
696
- return /* @__PURE__ */ jsx4(Text3.Body, { fontWeight: "bold", children: row.value.name });
697
- case "contentType":
698
- return /* @__PURE__ */ jsx4(
699
- "code",
700
- {
701
- style: {
702
- background: "var(--color-neutral-95)",
703
- padding: "2px 6px",
704
- borderRadius: "var(--border-radius-4)",
705
- fontSize: "var(--font-size-10)",
706
- fontFamily: "monospace"
707
- },
708
- children: row.value.contentType
709
- }
710
- );
711
- case "status": {
712
- const hasDraft = !!row.states.draft;
713
- const hasPublished = !!row.states.published;
714
- return /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", gap: "4px", flexWrap: "wrap" }, children: [
715
- hasDraft && /* @__PURE__ */ jsx4(Stamp2, { tone: "warning", label: "Draft", isCondensed: true }),
716
- hasPublished && /* @__PURE__ */ jsx4(Stamp2, { tone: "positive", label: "Published", isCondensed: true }),
717
- !hasDraft && !hasPublished && /* @__PURE__ */ jsx4(Stamp2, { tone: "secondary", label: "No state", isCondensed: true })
718
- ] });
719
- }
720
- case "updatedAt":
721
- return /* @__PURE__ */ jsx4(Text3.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleDateString() });
722
- case "actions":
723
- return /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", alignItems: "center", children: [
724
- /* @__PURE__ */ jsx4(
725
- PrimaryButton2,
726
- {
727
- label: "Edit",
728
- size: "20",
729
- onClick: () => history.push(`/${row.key}`, { contentName: row.value.name })
730
- }
731
- ),
732
- /* @__PURE__ */ jsx4(
733
- FlatButton2,
734
- {
735
- tone: "critical",
736
- label: deleting === row.key ? "\u2026" : "Delete",
737
- isDisabled: deleting === row.key,
738
- onClick: () => void handleDelete(row.key)
739
- }
740
- )
741
- ] });
742
- default:
743
- return null;
744
- }
745
- }
927
+ isOpen: pendingDelete !== null,
928
+ onOpenChange: (open) => {
929
+ if (!open) setPendingDelete(null);
930
+ },
931
+ children: /* @__PURE__ */ jsxs3(Dialog2.Content, { children: [
932
+ /* @__PURE__ */ jsxs3(Dialog2.Header, { children: [
933
+ /* @__PURE__ */ jsx5(Dialog2.Title, { children: "Delete content item?" }),
934
+ /* @__PURE__ */ jsx5(Dialog2.CloseTrigger, {})
935
+ ] }),
936
+ /* @__PURE__ */ jsx5(Dialog2.Body, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
937
+ "Are you sure you want to delete",
938
+ " ",
939
+ /* @__PURE__ */ jsx5(Text3, { as: "span", fontWeight: "700", children: pendingDelete?.value.name }),
940
+ " ",
941
+ "and all its versions? This cannot be undone."
942
+ ] }) }),
943
+ /* @__PURE__ */ jsxs3(Dialog2.Footer, { children: [
944
+ /* @__PURE__ */ jsx5(Button2, { slot: "close", variant: "outline", isDisabled: deleting !== null, children: "Cancel" }),
945
+ /* @__PURE__ */ jsx5(
946
+ Button2,
947
+ {
948
+ colorPalette: "critical",
949
+ isDisabled: deleting !== null,
950
+ onPress: () => {
951
+ if (pendingDelete) void handleDelete(pendingDelete);
952
+ },
953
+ children: deleting !== null ? "Deleting\u2026" : "Delete"
954
+ }
955
+ )
956
+ ] })
957
+ ] })
746
958
  }
747
959
  )
748
- ] }) });
960
+ ] });
749
961
  };
750
962
  var ContentEditorRoute = ({ config, backButton }) => {
751
963
  const { contentKey } = useParams();
@@ -764,9 +976,13 @@ var ContentEditorRoute = ({ config, backButton }) => {
764
976
  revertToPublished,
765
977
  loadVersions
766
978
  } = usePuckContent2(contentKey);
979
+ const { createTemplate } = usePuckTemplates3("content");
767
980
  const latestDataRef = useRef2(null);
768
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState3(false);
769
981
  const [isApplyingVersion, setIsApplyingVersion] = useState3(false);
982
+ const [reloadNonce, setReloadNonce] = useState3(0);
983
+ const [pendingNav, setPendingNav] = useState3(null);
984
+ const [templateDialogOpen, setTemplateDialogOpen] = useState3(false);
985
+ const [templateSaving, setTemplateSaving] = useState3(false);
770
986
  const currentData = states.draft?.data ?? content?.data ?? { content: [], root: { props: {} } };
771
987
  const versionHistory = useVersionHistoryPanel2({
772
988
  versions,
@@ -776,37 +992,52 @@ var ContentEditorRoute = ({ config, backButton }) => {
776
992
  const diff = useVersionDiff2(versionHistory.previewData, currentData);
777
993
  const isPreviewingRef = useRef2(false);
778
994
  isPreviewingRef.current = versionHistory.isPreviewingHistory;
779
- const handleChange = useCallback2((data) => {
780
- if (isPreviewingRef.current) return;
781
- latestDataRef.current = data;
782
- setHasUnsavedChanges(true);
783
- }, []);
995
+ const canvasKey = `${contentKey}:${versionHistory.selectedVersionId ?? "current"}:${reloadNonce}`;
996
+ const { isDirty: hasUnsavedChanges, markChange, markSaved } = useDirtyState2(canvasKey);
997
+ const guardedNavigate = useCallback2(
998
+ (navFn) => {
999
+ if (hasUnsavedChanges) {
1000
+ setPendingNav(() => navFn);
1001
+ } else {
1002
+ navFn();
1003
+ }
1004
+ },
1005
+ [hasUnsavedChanges]
1006
+ );
1007
+ const handleChange = useCallback2(
1008
+ (data) => {
1009
+ if (isPreviewingRef.current) return;
1010
+ latestDataRef.current = data;
1011
+ markChange(data);
1012
+ },
1013
+ [markChange]
1014
+ );
784
1015
  const handleSave = useCallback2(async () => {
785
1016
  const data = latestDataRef.current;
786
1017
  if (!data) return;
787
1018
  try {
788
1019
  await saveDraft(data);
789
- setHasUnsavedChanges(false);
1020
+ markSaved(data);
790
1021
  } catch (err) {
791
1022
  console.error("[ContentManagerRouter] save error:", err);
792
1023
  }
793
- }, [saveDraft]);
1024
+ }, [saveDraft, markSaved]);
794
1025
  const handlePublish = useCallback2(
795
1026
  async (data) => {
796
1027
  try {
797
1028
  await saveDraft(data);
798
- setHasUnsavedChanges(false);
1029
+ markSaved(data);
799
1030
  await publish(false);
800
1031
  } catch (err) {
801
1032
  console.error("[ContentManagerRouter] publish error:", err);
802
1033
  }
803
1034
  },
804
- [saveDraft, publish]
1035
+ [saveDraft, publish, markSaved]
805
1036
  );
806
1037
  const handleRevert = useCallback2(async () => {
807
1038
  try {
808
1039
  await revertToPublished();
809
- setHasUnsavedChanges(false);
1040
+ setReloadNonce((n) => n + 1);
810
1041
  } catch (err) {
811
1042
  console.error("[ContentManagerRouter] revert error:", err);
812
1043
  }
@@ -817,14 +1048,14 @@ var ContentEditorRoute = ({ config, backButton }) => {
817
1048
  setIsApplyingVersion(true);
818
1049
  try {
819
1050
  await saveDraft(versionData);
820
- setHasUnsavedChanges(false);
1051
+ markSaved(versionData);
821
1052
  versionHistory.clearSelection();
822
1053
  } catch (err) {
823
1054
  console.error("[ContentManagerRouter] apply version error:", err);
824
1055
  } finally {
825
1056
  setIsApplyingVersion(false);
826
1057
  }
827
- }, [versionHistory, saveDraft]);
1058
+ }, [versionHistory, saveDraft, markSaved]);
828
1059
  const contentConfig = useMemo2(() => {
829
1060
  const otherRootFields = Object.fromEntries(
830
1061
  Object.entries(config.root?.fields ?? {}).filter(([k]) => k !== "title")
@@ -846,22 +1077,38 @@ var ContentEditorRoute = ({ config, backButton }) => {
846
1077
  }
847
1078
  };
848
1079
  }, [config]);
1080
+ const handleCreateTemplate = useCallback2(
1081
+ async (name, withoutData) => {
1082
+ const source = latestDataRef.current ?? currentData;
1083
+ const puckData = withoutData ? stripPuckDataToTemplate2(source, contentConfig) : source;
1084
+ setTemplateSaving(true);
1085
+ try {
1086
+ await createTemplate({ name, kind: "content", puckData });
1087
+ setTemplateDialogOpen(false);
1088
+ } catch (err) {
1089
+ console.error("[ContentManagerRouter] create template error:", err);
1090
+ } finally {
1091
+ setTemplateSaving(false);
1092
+ }
1093
+ },
1094
+ [createTemplate, currentData, contentConfig]
1095
+ );
849
1096
  if (loading) {
850
- return /* @__PURE__ */ jsx4("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "m", alignItems: "center", children: [
851
- /* @__PURE__ */ jsx4(LoadingSpinner3, {}),
852
- /* @__PURE__ */ jsx4(Text3.Body, { tone: "secondary", children: "Loading editor\u2026" })
1097
+ return /* @__PURE__ */ jsx5("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }, children: /* @__PURE__ */ jsxs3(Stack3, { direction: "column", gap: "400", alignItems: "center", children: [
1098
+ /* @__PURE__ */ jsx5(LoadingSpinner3, {}),
1099
+ /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: "Loading editor\u2026" })
853
1100
  ] }) });
854
1101
  }
855
1102
  if (error) {
856
- return /* @__PURE__ */ jsx4("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs3(Text3.Body, { tone: "negative", children: [
857
- /* @__PURE__ */ jsx4("strong", { children: "Error loading content:" }),
1103
+ return /* @__PURE__ */ jsx5("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs3(Text3, { color: "critical.11", children: [
1104
+ /* @__PURE__ */ jsx5("strong", { children: "Error loading content:" }),
858
1105
  " ",
859
1106
  error
860
1107
  ] }) });
861
1108
  }
862
1109
  const activeData = versionHistory.previewData ?? currentData;
863
1110
  const toolbarStates = states;
864
- return /* @__PURE__ */ jsx4(
1111
+ return /* @__PURE__ */ jsxs3(
865
1112
  VersionHistoryProvider2,
866
1113
  {
867
1114
  diff,
@@ -874,82 +1121,164 @@ var ContentEditorRoute = ({ config, backButton }) => {
874
1121
  onApply: () => void handleApplyVersion(),
875
1122
  onDiscard: versionHistory.clearSelection,
876
1123
  onLoadVersions: versionHistory.openPanel,
877
- children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
878
- /* @__PURE__ */ jsxs3("div", { style: NAV_BAR_STYLE, children: [
879
- backButton,
880
- backButton && /* @__PURE__ */ jsx4(Text3.Body, { tone: "secondary", children: "/" }),
881
- /* @__PURE__ */ jsx4(
882
- FlatButton2,
883
- {
884
- label: "Content Items",
885
- icon: /* @__PURE__ */ jsx4(AngleLeftIcon, {}),
886
- iconPosition: "left",
887
- onClick: () => history.push("/")
888
- }
889
- ),
890
- /* @__PURE__ */ jsx4(Text3.Body, { tone: "secondary", children: "/" }),
891
- /* @__PURE__ */ jsx4(Text3.Body, { fontWeight: "bold", children: contentName })
1124
+ children: [
1125
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
1126
+ /* @__PURE__ */ jsxs3("div", { style: NAV_BAR_STYLE, children: [
1127
+ backButton,
1128
+ backButton && /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: "/" }),
1129
+ /* @__PURE__ */ jsxs3(
1130
+ Button2,
1131
+ {
1132
+ variant: "ghost",
1133
+ onPress: () => guardedNavigate(() => history.push("/")),
1134
+ children: [
1135
+ /* @__PURE__ */ jsx5(Icon2, { as: ChevronLeft }),
1136
+ " Content Items"
1137
+ ]
1138
+ }
1139
+ ),
1140
+ /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: "/" }),
1141
+ /* @__PURE__ */ jsx5(Text3, { fontWeight: "bold", children: contentName })
1142
+ ] }),
1143
+ /* @__PURE__ */ jsxs3("div", { className: "puck-editor-fill", style: { flex: 1, overflow: "hidden" }, children: [
1144
+ /* @__PURE__ */ jsx5(ComponentSearchProvider2, { children: /* @__PURE__ */ jsx5(
1145
+ Puck2,
1146
+ {
1147
+ config: contentConfig,
1148
+ data: activeData,
1149
+ onChange: handleChange,
1150
+ onPublish: handlePublish,
1151
+ overrides: {
1152
+ fieldTypes: nimbusFieldTypes2,
1153
+ header: () => versionHistory.isPreviewingHistory ? /* @__PURE__ */ jsxs3(
1154
+ Stack3,
1155
+ {
1156
+ gridArea: "header",
1157
+ direction: "row",
1158
+ gap: "200",
1159
+ alignItems: "center",
1160
+ justifyContent: "flex-end",
1161
+ padding: "200",
1162
+ children: [
1163
+ /* @__PURE__ */ jsx5(
1164
+ VersionPreviewBanner2,
1165
+ {
1166
+ timestamp: versionHistory.selectedVersion.timestamp,
1167
+ onApply: () => void handleApplyVersion(),
1168
+ onDiscard: versionHistory.clearSelection,
1169
+ isApplying: isApplyingVersion
1170
+ }
1171
+ ),
1172
+ /* @__PURE__ */ jsx5(VersionHistoryButton2, { disabled: isApplyingVersion })
1173
+ ]
1174
+ }
1175
+ ) : /* @__PURE__ */ jsx5(
1176
+ EditorToolbar2,
1177
+ {
1178
+ title: content?.name ?? contentName,
1179
+ saving,
1180
+ isDirty: hasUnsavedChanges,
1181
+ states: toolbarStates,
1182
+ onSave: () => void handleSave(),
1183
+ onPublish: () => void handlePublish(activeData),
1184
+ onRevert: () => void handleRevert(),
1185
+ onPreview: () => guardedNavigate(
1186
+ () => history.push(`/${contentKey}/preview`, { contentName })
1187
+ ),
1188
+ onCreateTemplate: () => setTemplateDialogOpen(true),
1189
+ createTemplateLabel: "Create a template from this content",
1190
+ showPublishButton: true
1191
+ }
1192
+ ),
1193
+ components: ({ children }) => /* @__PURE__ */ jsx5(ComponentsPanel2, { children }),
1194
+ componentItem: ({ children, name }) => /* @__PURE__ */ jsx5(ComponentItemFilter2, { name, children }),
1195
+ fields: ({ children, isLoading }) => /* @__PURE__ */ jsx5(VersionAwareFieldsPanel2, { isLoading, children })
1196
+ }
1197
+ },
1198
+ `${versionHistory.selectedVersionId ?? "current"}:${reloadNonce}`
1199
+ ) }),
1200
+ /* @__PURE__ */ jsx5(PropertiesResizer, {})
1201
+ ] })
892
1202
  ] }),
893
- /* @__PURE__ */ jsx4("div", { style: { flex: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsx4(ComponentSearchProvider2, { children: /* @__PURE__ */ jsx4(
894
- Puck2,
1203
+ /* @__PURE__ */ jsx5(
1204
+ UnsavedChangesDialog,
895
1205
  {
896
- config: contentConfig,
897
- data: activeData,
898
- onChange: handleChange,
899
- onPublish: handlePublish,
900
- overrides: {
901
- headerActions: () => versionHistory.isPreviewingHistory ? /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", alignItems: "center", children: [
902
- /* @__PURE__ */ jsx4(
903
- VersionPreviewBanner2,
904
- {
905
- timestamp: versionHistory.selectedVersion.timestamp,
906
- onApply: () => void handleApplyVersion(),
907
- onDiscard: versionHistory.clearSelection,
908
- isApplying: isApplyingVersion
909
- }
910
- ),
911
- /* @__PURE__ */ jsx4(VersionHistoryButton2, { disabled: isApplyingVersion })
912
- ] }) : /* @__PURE__ */ jsx4(
913
- EditorToolbar2,
914
- {
915
- saving,
916
- isDirty: hasUnsavedChanges,
917
- states: toolbarStates,
918
- onSave: () => void handleSave(),
919
- onPublish: () => void handlePublish(activeData),
920
- onRevert: () => void handleRevert(),
921
- showPublishButton: true
922
- }
923
- ),
924
- components: ({ children }) => /* @__PURE__ */ jsx4(ComponentsPanel2, { children }),
925
- componentItem: ({ children, name }) => /* @__PURE__ */ jsx4(ComponentItemFilter2, { name, children }),
926
- fields: ({ children, isLoading }) => /* @__PURE__ */ jsx4(VersionAwareFieldsPanel2, { isLoading, children })
927
- }
928
- },
929
- versionHistory.selectedVersionId ?? "current"
930
- ) }) })
931
- ] })
1206
+ isOpen: pendingNav !== null,
1207
+ onOpenChange: (open) => {
1208
+ if (!open) setPendingNav(null);
1209
+ },
1210
+ onConfirm: () => pendingNav?.()
1211
+ }
1212
+ ),
1213
+ /* @__PURE__ */ jsx5(
1214
+ CreateTemplateDialog2,
1215
+ {
1216
+ isOpen: templateDialogOpen,
1217
+ onOpenChange: (open) => setTemplateDialogOpen(open),
1218
+ onConfirm: handleCreateTemplate,
1219
+ saving: templateSaving
1220
+ }
1221
+ )
1222
+ ]
932
1223
  }
933
1224
  );
934
1225
  };
1226
+ var ContentPreviewRoute = ({ config, backButton }) => {
1227
+ const { contentKey } = useParams();
1228
+ const history = useHistory();
1229
+ const location = useLocation();
1230
+ const contentName = location.state?.contentName ?? contentKey ?? "Content";
1231
+ return /* @__PURE__ */ jsxs3("div", { children: [
1232
+ /* @__PURE__ */ jsxs3("div", { style: NAV_BAR_STYLE, children: [
1233
+ backButton,
1234
+ backButton && /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: "/" }),
1235
+ /* @__PURE__ */ jsxs3(Button2, { variant: "ghost", onPress: () => history.push("/"), children: [
1236
+ /* @__PURE__ */ jsx5(Icon2, { as: ChevronLeft }),
1237
+ " Content Items"
1238
+ ] }),
1239
+ /* @__PURE__ */ jsx5(Text3, { color: "neutral.11", children: "/" }),
1240
+ /* @__PURE__ */ jsx5(
1241
+ Button2,
1242
+ {
1243
+ variant: "ghost",
1244
+ onPress: () => history.push(`/${contentKey}`, { contentName }),
1245
+ children: contentName
1246
+ }
1247
+ ),
1248
+ /* @__PURE__ */ jsx5(Badge2, { colorPalette: "primary", size: "xs", children: "Preview" }),
1249
+ /* @__PURE__ */ jsx5("div", { style: { marginLeft: "auto" }, children: /* @__PURE__ */ jsxs3(Button2, { variant: "outline", size: "xs", onPress: () => history.goBack(), children: [
1250
+ /* @__PURE__ */ jsx5(Icon2, { as: Close2 }),
1251
+ " Close preview"
1252
+ ] }) })
1253
+ ] }),
1254
+ /* @__PURE__ */ jsx5(PuckRenderer, { type: "content", contentKey, mode: "preview", config })
1255
+ ] });
1256
+ };
935
1257
  var ContentManagerRouterInner = ({
936
1258
  config,
937
1259
  defaultContentType,
938
1260
  backButton
939
1261
  }) => /* @__PURE__ */ jsxs3(Switch, { children: [
940
- /* @__PURE__ */ jsx4(
1262
+ /* @__PURE__ */ jsx5(
941
1263
  Route,
942
1264
  {
943
1265
  exact: true,
944
1266
  path: "/",
945
- render: () => /* @__PURE__ */ jsx4(ContentListRoute, { defaultContentType, backButton })
1267
+ render: () => /* @__PURE__ */ jsx5(ContentListRoute, { defaultContentType, backButton })
1268
+ }
1269
+ ),
1270
+ /* @__PURE__ */ jsx5(
1271
+ Route,
1272
+ {
1273
+ path: "/:contentKey/preview",
1274
+ render: () => /* @__PURE__ */ jsx5(ContentPreviewRoute, { config, backButton })
946
1275
  }
947
1276
  ),
948
- /* @__PURE__ */ jsx4(
1277
+ /* @__PURE__ */ jsx5(
949
1278
  Route,
950
1279
  {
951
1280
  path: "/:contentKey",
952
- render: () => /* @__PURE__ */ jsx4(ContentEditorRoute, { config, backButton })
1281
+ render: () => /* @__PURE__ */ jsx5(ContentEditorRoute, { config, backButton })
953
1282
  }
954
1283
  )
955
1284
  ] });
@@ -959,17 +1288,19 @@ var ContentManager = ({
959
1288
  projectKey,
960
1289
  businessUnitKey,
961
1290
  jwtToken,
1291
+ locale,
962
1292
  config = DEFAULT_CONFIG,
963
1293
  defaultContentType,
964
1294
  backButton
965
- }) => /* @__PURE__ */ jsx4(EnsureIntlProvider, { children: /* @__PURE__ */ jsx4(
1295
+ }) => /* @__PURE__ */ jsx5(EnsureNimbusProvider, { locale, children: /* @__PURE__ */ jsx5(EnsureIntlProvider, { children: /* @__PURE__ */ jsx5(
966
1296
  PuckApiProvider3,
967
1297
  {
968
1298
  baseURL,
969
1299
  projectKey,
970
1300
  businessUnitKey,
971
1301
  jwtToken,
972
- children: /* @__PURE__ */ jsx4(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx4(
1302
+ locale,
1303
+ children: /* @__PURE__ */ jsx5(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx5(
973
1304
  ContentManagerRouterInner,
974
1305
  {
975
1306
  config,
@@ -978,7 +1309,7 @@ var ContentManager = ({
978
1309
  }
979
1310
  ) })
980
1311
  }
981
- ) });
1312
+ ) }) });
982
1313
  export {
983
1314
  ContentEditor,
984
1315
  ContentManager,