@commercetools-demo/puck-content-manager 0.5.2 → 0.6.1

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