@commercetools-demo/puck-content-manager 0.5.1 → 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,36 +1,73 @@
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";
16
- import { jsx, jsxs } from "react/jsx-runtime";
17
- var columns = [
18
- { key: "name", label: "Name" },
19
- { key: "contentType", label: "Content Type" },
20
- { key: "status", label: "Status" },
21
- { key: "updatedAt", label: "Updated" },
22
- { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
23
- ];
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";
24
+
25
+ // src/EnsureIntlProvider.tsx
26
+ import { useContext } from "react";
27
+ import { IntlContext, IntlProvider, ReactIntlErrorCode } from "react-intl";
28
+ import { Fragment, jsx } from "react/jsx-runtime";
29
+ var EnsureIntlProvider = ({ children }) => {
30
+ const intl = useContext(IntlContext);
31
+ if (intl) {
32
+ return /* @__PURE__ */ jsx(Fragment, { children });
33
+ }
34
+ return /* @__PURE__ */ jsx(
35
+ IntlProvider,
36
+ {
37
+ locale: "en",
38
+ defaultLocale: "en",
39
+ messages: {},
40
+ onError: (err) => {
41
+ if (err.code === ReactIntlErrorCode.MISSING_TRANSLATION) return;
42
+ console.error(err);
43
+ },
44
+ children
45
+ }
46
+ );
47
+ };
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
+
57
+ // src/ContentManager.tsx
58
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
24
59
  var ContentList = ({ defaultContentType, onEdit }) => {
25
- const { contents, loading, error, fetchContents, createContent, deleteContent, refresh } = usePuckContents(defaultContentType);
26
- const [filterType, setFilterType] = useState(defaultContentType ?? "");
60
+ const { contents, loading, error, createContent, deleteContent } = usePuckContents(defaultContentType);
61
+ const { templates } = usePuckTemplates("content");
62
+ const [search, setSearch] = useState("");
27
63
  const [showCreate, setShowCreate] = useState(false);
28
64
  const [createName, setCreateName] = useState("");
29
65
  const [createType, setCreateType] = useState(defaultContentType ?? "");
66
+ const [templateKey, setTemplateKey] = useState("");
30
67
  const [createError, setCreateError] = useState(null);
31
68
  const [creating, setCreating] = useState(false);
32
69
  const [deleting, setDeleting] = useState(null);
33
- const handleFilter = () => void fetchContents(filterType || void 0);
70
+ const [pendingDelete, setPendingDelete] = useState(null);
34
71
  const handleCreate = async () => {
35
72
  setCreateError(null);
36
73
  if (!createName.trim()) {
@@ -43,168 +80,231 @@ var ContentList = ({ defaultContentType, onEdit }) => {
43
80
  }
44
81
  setCreating(true);
45
82
  try {
83
+ const template = templateKey ? templates.find((t) => t.key === templateKey) : void 0;
46
84
  const input = {
47
85
  name: createName.trim(),
48
86
  contentType: createType.trim(),
49
- data: { content: [], root: { props: {} } }
87
+ data: template?.value.puckData ?? { content: [], root: { props: {} } }
50
88
  };
51
89
  await createContent(input);
52
90
  setShowCreate(false);
53
91
  setCreateName("");
54
92
  setCreateType(defaultContentType ?? "");
93
+ setTemplateKey("");
55
94
  } catch (err) {
56
95
  setCreateError(err.message);
57
96
  } finally {
58
97
  setCreating(false);
59
98
  }
60
99
  };
61
- const handleDelete = async (key) => {
62
- if (!confirm("Delete this content item and all its versions?")) return;
63
- setDeleting(key);
100
+ const handleDelete = async (item) => {
101
+ setDeleting(item.key);
64
102
  try {
65
- await deleteContent(key);
103
+ await deleteContent(item.key);
104
+ setPendingDelete(null);
66
105
  } finally {
67
106
  setDeleting(null);
68
107
  }
69
108
  };
70
- const rows = contents.map((c) => ({ ...c, id: c.key }));
71
- return /* @__PURE__ */ jsx("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "l", children: [
72
- /* @__PURE__ */ jsxs(Spacings.Inline, { justifyContent: "space-between", alignItems: "center", children: [
73
- /* @__PURE__ */ jsx(Text.Headline, { as: "h1", children: "Content Items" }),
74
- /* @__PURE__ */ jsx(
75
- 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",
76
127
  {
77
- label: "New Content",
78
- iconLeft: /* @__PURE__ */ jsx(PlusThinIcon, {}),
79
- 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
80
136
  }
81
137
  )
82
- ] }),
83
- showCreate && /* @__PURE__ */ jsx(Card, { insetScale: "l", children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "m", children: [
84
- /* @__PURE__ */ jsx(Text.Subheadline, { as: "h4", isBold: true, children: "Create Content Item" }),
85
- createError && /* @__PURE__ */ jsx(Text.Body, { tone: "negative", children: createError }),
86
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "m", children: [
87
- /* @__PURE__ */ jsx("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
88
- /* @__PURE__ */ jsx(Label, { htmlFor: "create-name", children: "Name" }),
89
- /* @__PURE__ */ jsx(
90
- TextInput,
91
- {
92
- id: "create-name",
93
- value: createName,
94
- onChange: (e) => setCreateName(e.target.value),
95
- placeholder: "e.g. Homepage Hero"
96
- }
97
- )
98
- ] }) }),
99
- /* @__PURE__ */ jsx("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
100
- /* @__PURE__ */ jsx(Label, { htmlFor: "create-type", children: "Content Type" }),
101
- /* @__PURE__ */ jsx(
102
- TextInput,
103
- {
104
- id: "create-type",
105
- value: createType,
106
- onChange: (e) => setCreateType(e.target.value),
107
- placeholder: "e.g. hero, banner"
108
- }
109
- )
110
- ] }) })
111
- ] }),
112
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", children: [
113
- /* @__PURE__ */ jsx(
114
- 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,
115
168
  {
116
- label: creating ? "Creating\u2026" : "Create",
117
- onClick: () => void handleCreate(),
118
- isDisabled: creating
169
+ "aria-label": `Edit ${row.value.name}`,
170
+ variant: "ghost",
171
+ size: "xs",
172
+ onPress: () => onEdit(row),
173
+ children: /* @__PURE__ */ jsx3(Edit, {})
119
174
  }
120
175
  ),
121
- /* @__PURE__ */ jsx(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
+ )
122
188
  ] })
123
- ] }) }),
124
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", alignItems: "center", children: [
125
- /* @__PURE__ */ jsx("div", { style: { flex: 1, maxWidth: "280px" }, children: /* @__PURE__ */ jsx(
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(
126
248
  TextInput,
127
249
  {
128
- value: filterType,
129
- onChange: (e) => setFilterType(e.target.value),
130
- 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
131
266
  }
132
267
  ) }),
133
- /* @__PURE__ */ jsx(
134
- SecondaryButton,
135
- {
136
- label: "Filter",
137
- iconLeft: /* @__PURE__ */ jsx(SearchIcon, {}),
138
- onClick: handleFilter
139
- }
140
- ),
141
- /* @__PURE__ */ jsx(
142
- FlatButton,
143
- {
144
- label: "Clear",
145
- onClick: () => {
146
- setFilterType("");
147
- void fetchContents(void 0);
148
- }
149
- }
150
- ),
151
- /* @__PURE__ */ jsx(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" })
152
270
  ] }),
153
- error && /* @__PURE__ */ jsx(Text.Body, { tone: "negative", children: error }),
154
- loading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx(LoadingSpinner, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx(Spacings.Stack, { scale: "m", alignItems: "center", children: /* @__PURE__ */ jsx(Text.Body, { tone: "secondary", children: "No content items found." }) }) : /* @__PURE__ */ jsx(
155
- DataTable,
271
+ /* @__PURE__ */ jsx3(
272
+ Dialog.Root,
156
273
  {
157
- columns,
158
- rows,
159
- itemRenderer: (row, column) => {
160
- switch (column.key) {
161
- case "name":
162
- return /* @__PURE__ */ jsx(Text.Body, { fontWeight: "bold", children: row.value.name });
163
- case "contentType":
164
- return /* @__PURE__ */ jsx(
165
- "code",
166
- {
167
- style: {
168
- background: "var(--color-neutral-95)",
169
- padding: "2px 6px",
170
- borderRadius: "var(--border-radius-4)",
171
- fontSize: "var(--font-size-10)",
172
- fontFamily: "monospace"
173
- },
174
- children: row.value.contentType
175
- }
176
- );
177
- case "status": {
178
- const hasDraft = !!row.states.draft;
179
- const hasPublished = !!row.states.published;
180
- return /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", gap: "4px", flexWrap: "wrap" }, children: [
181
- hasDraft && /* @__PURE__ */ jsx(Stamp, { tone: "warning", label: "Draft", isCondensed: true }),
182
- hasPublished && /* @__PURE__ */ jsx(Stamp, { tone: "positive", label: "Published", isCondensed: true }),
183
- !hasDraft && !hasPublished && /* @__PURE__ */ jsx(Stamp, { tone: "secondary", label: "No state", isCondensed: true })
184
- ] });
185
- }
186
- case "updatedAt":
187
- return /* @__PURE__ */ jsx(Text.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleDateString() });
188
- case "actions":
189
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
190
- /* @__PURE__ */ jsx(PrimaryButton, { label: "Edit", size: "20", onClick: () => onEdit(row) }),
191
- /* @__PURE__ */ jsx(
192
- FlatButton,
193
- {
194
- tone: "critical",
195
- label: deleting === row.key ? "\u2026" : "Delete",
196
- isDisabled: deleting === row.key,
197
- onClick: () => void handleDelete(row.key)
198
- }
199
- )
200
- ] });
201
- default:
202
- return null;
203
- }
204
- }
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
+ ] })
205
305
  }
206
306
  )
207
- ] }) });
307
+ ] });
208
308
  };
209
309
  var ContentManagerList = ({
210
310
  baseURL,
@@ -213,27 +313,35 @@ var ContentManagerList = ({
213
313
  jwtToken,
214
314
  defaultContentType,
215
315
  onEdit
216
- }) => /* @__PURE__ */ jsx(
316
+ }) => /* @__PURE__ */ jsx3(EnsureNimbusProvider, { children: /* @__PURE__ */ jsx3(EnsureIntlProvider, { children: /* @__PURE__ */ jsx3(
217
317
  PuckApiProvider,
218
318
  {
219
319
  baseURL,
220
320
  projectKey,
221
321
  businessUnitKey,
222
322
  jwtToken,
223
- children: /* @__PURE__ */ jsx(ContentList, { defaultContentType, onEdit })
323
+ children: /* @__PURE__ */ jsx3(ContentList, { defaultContentType, onEdit })
224
324
  }
225
- );
325
+ ) }) });
226
326
 
227
327
  // src/ContentEditor.tsx
228
328
  import { useCallback, useMemo, useRef, useState as useState2 } from "react";
229
329
  import { Puck } from "@measured/puck";
230
330
  import "@measured/puck/puck.css";
231
- 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";
232
336
  import {
233
337
  ComponentSearchProvider,
234
338
  ComponentsPanel,
235
339
  ComponentItemFilter,
236
- EditorToolbar
340
+ CreateTemplateDialog,
341
+ EditorToolbar,
342
+ nimbusFieldTypes,
343
+ stripPuckDataToTemplate,
344
+ useDirtyState
237
345
  } from "@commercetools-demo/puck-editor";
238
346
  import {
239
347
  VersionHistoryProvider,
@@ -243,10 +351,8 @@ import {
243
351
  useVersionHistoryPanel,
244
352
  useVersionDiff
245
353
  } from "@commercetools-demo/puck-version-history";
246
- import LoadingSpinner2 from "@commercetools-uikit/loading-spinner";
247
- import Text2 from "@commercetools-uikit/text";
248
- import Spacings2 from "@commercetools-uikit/spacings";
249
- import { jsx as jsx2, 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";
250
356
  var ContentEditorInner = ({
251
357
  contentKey,
252
358
  config,
@@ -266,9 +372,12 @@ var ContentEditorInner = ({
266
372
  revertToPublished,
267
373
  loadVersions
268
374
  } = usePuckContent(contentKey);
375
+ const { createTemplate } = usePuckTemplates2("content");
269
376
  const latestDataRef = useRef(null);
270
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState2(false);
271
377
  const [isApplyingVersion, setIsApplyingVersion] = useState2(false);
378
+ const [reloadNonce, setReloadNonce] = useState2(0);
379
+ const [templateDialogOpen, setTemplateDialogOpen] = useState2(false);
380
+ const [templateSaving, setTemplateSaving] = useState2(false);
272
381
  const currentData = states.draft?.data ?? content?.data ?? {
273
382
  content: [],
274
383
  root: { props: {} }
@@ -284,39 +393,44 @@ var ContentEditorInner = ({
284
393
  );
285
394
  const isPreviewingRef = useRef(false);
286
395
  isPreviewingRef.current = versionHistory.isPreviewingHistory;
287
- const handleChange = useCallback((data) => {
288
- if (isPreviewingRef.current) return;
289
- latestDataRef.current = data;
290
- setHasUnsavedChanges(true);
291
- }, []);
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
+ );
292
406
  const handleSave = useCallback(async () => {
293
407
  const data = latestDataRef.current;
294
408
  if (!data) return;
295
409
  try {
296
410
  await saveDraft(data);
297
- setHasUnsavedChanges(false);
411
+ markSaved(data);
298
412
  onSave?.(data);
299
413
  } catch (err) {
300
414
  onError?.(err);
301
415
  }
302
- }, [saveDraft, onSave, onError]);
416
+ }, [saveDraft, onSave, onError, markSaved]);
303
417
  const handlePublish = useCallback(
304
418
  async (data) => {
305
419
  try {
306
420
  await saveDraft(data);
307
- setHasUnsavedChanges(false);
421
+ markSaved(data);
308
422
  await publish(false);
309
423
  onPublish?.(data);
310
424
  } catch (err) {
311
425
  onError?.(err);
312
426
  }
313
427
  },
314
- [saveDraft, publish, onPublish, onError]
428
+ [saveDraft, publish, onPublish, onError, markSaved]
315
429
  );
316
430
  const handleRevert = useCallback(async () => {
317
431
  try {
318
432
  await revertToPublished();
319
- setHasUnsavedChanges(false);
433
+ setReloadNonce((n) => n + 1);
320
434
  } catch (err) {
321
435
  onError?.(err);
322
436
  }
@@ -327,7 +441,7 @@ var ContentEditorInner = ({
327
441
  setIsApplyingVersion(true);
328
442
  try {
329
443
  await saveDraft(versionData);
330
- setHasUnsavedChanges(false);
444
+ markSaved(versionData);
331
445
  onSave?.(versionData);
332
446
  versionHistory.clearSelection();
333
447
  } catch (err) {
@@ -335,7 +449,7 @@ var ContentEditorInner = ({
335
449
  } finally {
336
450
  setIsApplyingVersion(false);
337
451
  }
338
- }, [versionHistory, saveDraft, onSave, onError]);
452
+ }, [versionHistory, saveDraft, onSave, onError, markSaved]);
339
453
  const contentConfig = useMemo(() => {
340
454
  const otherRootFields = Object.fromEntries(
341
455
  Object.entries(config.root?.fields ?? {}).filter(([k]) => k !== "title")
@@ -357,22 +471,38 @@ var ContentEditorInner = ({
357
471
  }
358
472
  };
359
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
+ );
360
490
  if (loading) {
361
- return /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }, children: /* @__PURE__ */ jsxs2(Spacings2.Stack, { scale: "m", alignItems: "center", children: [
362
- /* @__PURE__ */ jsx2(LoadingSpinner2, {}),
363
- /* @__PURE__ */ jsx2(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" })
364
494
  ] }) });
365
495
  }
366
496
  if (error) {
367
- return /* @__PURE__ */ jsx2("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs2(Text2.Body, { tone: "negative", children: [
368
- /* @__PURE__ */ jsx2("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:" }),
369
499
  " ",
370
500
  error
371
501
  ] }) });
372
502
  }
373
503
  const activeData = versionHistory.previewData ?? currentData;
374
504
  const toolbarStates = states;
375
- return /* @__PURE__ */ jsx2(
505
+ return /* @__PURE__ */ jsx4(
376
506
  VersionHistoryProvider,
377
507
  {
378
508
  diff,
@@ -385,44 +515,70 @@ var ContentEditorInner = ({
385
515
  onApply: () => void handleApplyVersion(),
386
516
  onDiscard: versionHistory.clearSelection,
387
517
  onLoadVersions: versionHistory.openPanel,
388
- children: /* @__PURE__ */ jsx2(ComponentSearchProvider, { children: /* @__PURE__ */ jsx2(
389
- Puck,
390
- {
391
- config: contentConfig,
392
- data: activeData,
393
- onChange: handleChange,
394
- onPublish: handlePublish,
395
- overrides: {
396
- headerActions: () => versionHistory.isPreviewingHistory ? /* @__PURE__ */ jsxs2(Spacings2.Inline, { scale: "s", alignItems: "center", children: [
397
- /* @__PURE__ */ jsx2(
398
- 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,
399
530
  {
400
- timestamp: versionHistory.selectedVersion.timestamp,
401
- onApply: () => void handleApplyVersion(),
402
- onDiscard: versionHistory.clearSelection,
403
- isApplying: isApplyingVersion
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,
552
+ {
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
404
563
  }
405
564
  ),
406
- /* @__PURE__ */ jsx2(VersionHistoryButton, { disabled: isApplyingVersion })
407
- ] }) : /* @__PURE__ */ jsx2(
408
- EditorToolbar,
409
- {
410
- saving,
411
- isDirty: hasUnsavedChanges,
412
- states: toolbarStates,
413
- onSave: () => void handleSave(),
414
- onPublish: () => void handlePublish(activeData),
415
- onRevert: () => void handleRevert(),
416
- showPublishButton: true
417
- }
418
- ),
419
- components: ({ children }) => /* @__PURE__ */ jsx2(ComponentsPanel, { children }),
420
- componentItem: ({ children, name }) => /* @__PURE__ */ jsx2(ComponentItemFilter, { name, children }),
421
- fields: ({ children, isLoading }) => /* @__PURE__ */ jsx2(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
422
579
  }
423
- },
424
- versionHistory.selectedVersionId ?? "current"
425
- ) })
580
+ )
581
+ ] })
426
582
  }
427
583
  );
428
584
  };
@@ -431,19 +587,21 @@ var ContentEditor = ({
431
587
  projectKey,
432
588
  businessUnitKey,
433
589
  jwtToken,
590
+ locale,
434
591
  contentKey,
435
592
  config,
436
593
  onPublish,
437
594
  onSave,
438
595
  onError
439
- }) => /* @__PURE__ */ jsx2(
596
+ }) => /* @__PURE__ */ jsx4(EnsureNimbusProvider, { locale, children: /* @__PURE__ */ jsx4(EnsureIntlProvider, { children: /* @__PURE__ */ jsx4(
440
597
  PuckApiProvider2,
441
598
  {
442
599
  baseURL,
443
600
  projectKey,
444
601
  businessUnitKey,
445
602
  jwtToken,
446
- children: /* @__PURE__ */ jsx2(
603
+ locale,
604
+ children: /* @__PURE__ */ jsx4(
447
605
  ContentEditorInner,
448
606
  {
449
607
  contentKey,
@@ -454,7 +612,7 @@ var ContentEditor = ({
454
612
  }
455
613
  )
456
614
  }
457
- );
615
+ ) }) });
458
616
 
459
617
  // src/ContentManagerRouter.tsx
460
618
  import { useState as useState3, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2 } from "react";
@@ -472,9 +630,16 @@ import {
472
630
  ComponentSearchProvider as ComponentSearchProvider2,
473
631
  ComponentsPanel as ComponentsPanel2,
474
632
  ComponentItemFilter as ComponentItemFilter2,
633
+ CreateTemplateDialog as CreateTemplateDialog2,
475
634
  defaultPuckConfig,
476
- EditorToolbar as EditorToolbar2
635
+ EditorToolbar as EditorToolbar2,
636
+ nimbusFieldTypes as nimbusFieldTypes2,
637
+ PropertiesResizer,
638
+ stripPuckDataToTemplate as stripPuckDataToTemplate2,
639
+ UnsavedChangesDialog,
640
+ useDirtyState as useDirtyState2
477
641
  } from "@commercetools-demo/puck-editor";
642
+ import { PuckRenderer } from "@commercetools-demo/puck-renderer";
478
643
  import {
479
644
  VersionHistoryProvider as VersionHistoryProvider2,
480
645
  VersionHistoryButton as VersionHistoryButton2,
@@ -486,21 +651,33 @@ import {
486
651
  import {
487
652
  PuckApiProvider as PuckApiProvider3,
488
653
  usePuckContents as usePuckContents2,
489
- usePuckContent as usePuckContent2
654
+ usePuckContent as usePuckContent2,
655
+ usePuckTemplates as usePuckTemplates3
490
656
  } from "@commercetools-demo/puck-api";
491
- import DataTable2 from "@commercetools-uikit/data-table";
492
- import PrimaryButton2 from "@commercetools-uikit/primary-button";
493
- import SecondaryButton2 from "@commercetools-uikit/secondary-button";
494
- import FlatButton2 from "@commercetools-uikit/flat-button";
495
- import Card2 from "@commercetools-uikit/card";
496
- import Spacings3 from "@commercetools-uikit/spacings";
497
- import Text3 from "@commercetools-uikit/text";
498
- import LoadingSpinner3 from "@commercetools-uikit/loading-spinner";
499
- import TextInput2 from "@commercetools-uikit/text-input";
500
- import Label2 from "@commercetools-uikit/label";
501
- import Stamp2 from "@commercetools-uikit/stamp";
502
- import { PlusThinIcon as PlusThinIcon2, SearchIcon as SearchIcon2, AngleLeftIcon } from "@commercetools-uikit/icons";
503
- import { jsx as jsx3, 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";
504
681
  var DEFAULT_CONFIG = {
505
682
  ...defaultPuckConfig,
506
683
  components: { ...defaultPuckConfig.components }
@@ -513,28 +690,23 @@ var NAV_BAR_STYLE = {
513
690
  gap: "12px",
514
691
  padding: "8px 16px",
515
692
  background: "var(--color-surface, #fff)",
516
- borderBottom: "1px solid var(--color-neutral-90)",
693
+ borderBottom: "1px solid var(--color-neutral-90, #e0e0e0)",
517
694
  zIndex: 200,
518
695
  flexShrink: 0
519
696
  };
520
- var COLUMNS = [
521
- { key: "name", label: "Name" },
522
- { key: "contentType", label: "Content Type" },
523
- { key: "status", label: "Status" },
524
- { key: "updatedAt", label: "Updated" },
525
- { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
526
- ];
527
697
  var ContentListRoute = ({ defaultContentType, backButton }) => {
528
698
  const history = useHistory();
529
- const { contents, loading, error, fetchContents, createContent, deleteContent, refresh } = usePuckContents2(defaultContentType);
530
- const [filterType, setFilterType] = useState3(defaultContentType ?? "");
699
+ const { contents, loading, error, createContent, deleteContent } = usePuckContents2(defaultContentType);
700
+ const { templates } = usePuckTemplates3("content");
701
+ const [search, setSearch] = useState3("");
531
702
  const [showCreate, setShowCreate] = useState3(false);
532
703
  const [createName, setCreateName] = useState3("");
533
704
  const [createType, setCreateType] = useState3(defaultContentType ?? "");
705
+ const [templateKey, setTemplateKey] = useState3("");
534
706
  const [createError, setCreateError] = useState3(null);
535
707
  const [creating, setCreating] = useState3(false);
536
708
  const [deleting, setDeleting] = useState3(null);
537
- const handleFilter = () => void fetchContents(filterType || void 0);
709
+ const [pendingDelete, setPendingDelete] = useState3(null);
538
710
  const handleCreate = async () => {
539
711
  setCreateError(null);
540
712
  if (!createName.trim()) {
@@ -547,15 +719,17 @@ var ContentListRoute = ({ defaultContentType, backButton }) => {
547
719
  }
548
720
  setCreating(true);
549
721
  try {
722
+ const template = templateKey ? templates.find((t) => t.key === templateKey) : void 0;
550
723
  const input = {
551
724
  name: createName.trim(),
552
725
  contentType: createType.trim(),
553
- data: { content: [], root: { props: {} } }
726
+ data: template?.value.puckData ?? { content: [], root: { props: {} } }
554
727
  };
555
728
  const created = await createContent(input);
556
729
  setShowCreate(false);
557
730
  setCreateName("");
558
731
  setCreateType(defaultContentType ?? "");
732
+ setTemplateKey("");
559
733
  history.push(`/${created.key}`, { contentName: created.value.name });
560
734
  } catch (err) {
561
735
  setCreateError(err.message);
@@ -563,163 +737,227 @@ var ContentListRoute = ({ defaultContentType, backButton }) => {
563
737
  setCreating(false);
564
738
  }
565
739
  };
566
- const handleDelete = async (key) => {
567
- if (!confirm("Delete this content item and all its versions?")) return;
568
- setDeleting(key);
740
+ const handleDelete = async (item) => {
741
+ setDeleting(item.key);
569
742
  try {
570
- await deleteContent(key);
743
+ await deleteContent(item.key);
744
+ setPendingDelete(null);
571
745
  } finally {
572
746
  setDeleting(null);
573
747
  }
574
748
  };
575
- const rows = contents.map((c) => ({ ...c, id: c.key }));
576
- return /* @__PURE__ */ jsx3("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "l", children: [
577
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { justifyContent: "space-between", alignItems: "center", children: [
578
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "m", alignItems: "center", children: [
579
- backButton,
580
- /* @__PURE__ */ jsx3(Text3.Headline, { as: "h1", children: "Content Items" })
581
- ] }),
582
- /* @__PURE__ */ jsx3(
583
- 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",
584
767
  {
585
- label: "New Content",
586
- iconLeft: /* @__PURE__ */ jsx3(PlusThinIcon2, {}),
587
- 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
588
776
  }
589
777
  )
590
- ] }),
591
- showCreate && /* @__PURE__ */ jsx3(Card2, { insetScale: "l", children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "m", children: [
592
- /* @__PURE__ */ jsx3(Text3.Subheadline, { as: "h4", isBold: true, children: "Create Content Item" }),
593
- createError && /* @__PURE__ */ jsx3(Text3.Body, { tone: "negative", children: createError }),
594
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "m", children: [
595
- /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "xs", children: [
596
- /* @__PURE__ */ jsx3(Label2, { htmlFor: "create-content-name", children: "Name" }),
597
- /* @__PURE__ */ jsx3(
598
- TextInput2,
599
- {
600
- id: "create-content-name",
601
- value: createName,
602
- onChange: (e) => setCreateName(e.target.value),
603
- placeholder: "e.g. Homepage Hero"
604
- }
605
- )
606
- ] }) }),
607
- /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "xs", children: [
608
- /* @__PURE__ */ jsx3(Label2, { htmlFor: "create-content-type", children: "Content Type" }),
609
- /* @__PURE__ */ jsx3(
610
- TextInput2,
611
- {
612
- id: "create-content-type",
613
- value: createType,
614
- onChange: (e) => setCreateType(e.target.value),
615
- placeholder: "e.g. hero, banner"
616
- }
617
- )
618
- ] }) })
619
- ] }),
620
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", children: [
621
- /* @__PURE__ */ jsx3(
622
- 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,
623
818
  {
624
- label: creating ? "Creating\u2026" : "Create",
625
- onClick: () => void handleCreate(),
626
- 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, {})
627
824
  }
628
825
  ),
629
- /* @__PURE__ */ jsx3(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
+ )
630
838
  ] })
631
- ] }) }),
632
- /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", alignItems: "center", children: [
633
- /* @__PURE__ */ jsx3("div", { style: { flex: 1, maxWidth: "280px" }, children: /* @__PURE__ */ jsx3(
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(
634
901
  TextInput2,
635
902
  {
636
- value: filterType,
637
- onChange: (e) => setFilterType(e.target.value),
638
- 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
639
919
  }
640
920
  ) }),
641
- /* @__PURE__ */ jsx3(
642
- SecondaryButton2,
643
- {
644
- label: "Filter",
645
- iconLeft: /* @__PURE__ */ jsx3(SearchIcon2, {}),
646
- onClick: handleFilter
647
- }
648
- ),
649
- /* @__PURE__ */ jsx3(
650
- FlatButton2,
651
- {
652
- label: "Clear",
653
- onClick: () => {
654
- setFilterType("");
655
- void fetchContents(void 0);
656
- }
657
- }
658
- ),
659
- /* @__PURE__ */ jsx3(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" })
660
923
  ] }),
661
- error && /* @__PURE__ */ jsx3(Text3.Body, { tone: "negative", children: error }),
662
- loading ? /* @__PURE__ */ jsx3("div", { style: { display: "flex", justifyContent: "center", padding: "48px" }, children: /* @__PURE__ */ jsx3(LoadingSpinner3, {}) }) : contents.length === 0 ? /* @__PURE__ */ jsx3(Spacings3.Stack, { scale: "m", alignItems: "center", children: /* @__PURE__ */ jsx3(Text3.Body, { tone: "secondary", children: "No content items found." }) }) : /* @__PURE__ */ jsx3(
663
- DataTable2,
924
+ /* @__PURE__ */ jsx5(
925
+ Dialog2.Root,
664
926
  {
665
- columns: COLUMNS,
666
- rows,
667
- itemRenderer: (row, column) => {
668
- switch (column.key) {
669
- case "name":
670
- return /* @__PURE__ */ jsx3(Text3.Body, { fontWeight: "bold", children: row.value.name });
671
- case "contentType":
672
- return /* @__PURE__ */ jsx3(
673
- "code",
674
- {
675
- style: {
676
- background: "var(--color-neutral-95)",
677
- padding: "2px 6px",
678
- borderRadius: "var(--border-radius-4)",
679
- fontSize: "var(--font-size-10)",
680
- fontFamily: "monospace"
681
- },
682
- children: row.value.contentType
683
- }
684
- );
685
- case "status": {
686
- const hasDraft = !!row.states.draft;
687
- const hasPublished = !!row.states.published;
688
- return /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", gap: "4px", flexWrap: "wrap" }, children: [
689
- hasDraft && /* @__PURE__ */ jsx3(Stamp2, { tone: "warning", label: "Draft", isCondensed: true }),
690
- hasPublished && /* @__PURE__ */ jsx3(Stamp2, { tone: "positive", label: "Published", isCondensed: true }),
691
- !hasDraft && !hasPublished && /* @__PURE__ */ jsx3(Stamp2, { tone: "secondary", label: "No state", isCondensed: true })
692
- ] });
693
- }
694
- case "updatedAt":
695
- return /* @__PURE__ */ jsx3(Text3.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleDateString() });
696
- case "actions":
697
- return /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", alignItems: "center", children: [
698
- /* @__PURE__ */ jsx3(
699
- PrimaryButton2,
700
- {
701
- label: "Edit",
702
- size: "20",
703
- onClick: () => history.push(`/${row.key}`, { contentName: row.value.name })
704
- }
705
- ),
706
- /* @__PURE__ */ jsx3(
707
- FlatButton2,
708
- {
709
- tone: "critical",
710
- label: deleting === row.key ? "\u2026" : "Delete",
711
- isDisabled: deleting === row.key,
712
- onClick: () => void handleDelete(row.key)
713
- }
714
- )
715
- ] });
716
- default:
717
- return null;
718
- }
719
- }
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
+ ] })
720
958
  }
721
959
  )
722
- ] }) });
960
+ ] });
723
961
  };
724
962
  var ContentEditorRoute = ({ config, backButton }) => {
725
963
  const { contentKey } = useParams();
@@ -738,9 +976,13 @@ var ContentEditorRoute = ({ config, backButton }) => {
738
976
  revertToPublished,
739
977
  loadVersions
740
978
  } = usePuckContent2(contentKey);
979
+ const { createTemplate } = usePuckTemplates3("content");
741
980
  const latestDataRef = useRef2(null);
742
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState3(false);
743
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);
744
986
  const currentData = states.draft?.data ?? content?.data ?? { content: [], root: { props: {} } };
745
987
  const versionHistory = useVersionHistoryPanel2({
746
988
  versions,
@@ -750,37 +992,52 @@ var ContentEditorRoute = ({ config, backButton }) => {
750
992
  const diff = useVersionDiff2(versionHistory.previewData, currentData);
751
993
  const isPreviewingRef = useRef2(false);
752
994
  isPreviewingRef.current = versionHistory.isPreviewingHistory;
753
- const handleChange = useCallback2((data) => {
754
- if (isPreviewingRef.current) return;
755
- latestDataRef.current = data;
756
- setHasUnsavedChanges(true);
757
- }, []);
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
+ );
758
1015
  const handleSave = useCallback2(async () => {
759
1016
  const data = latestDataRef.current;
760
1017
  if (!data) return;
761
1018
  try {
762
1019
  await saveDraft(data);
763
- setHasUnsavedChanges(false);
1020
+ markSaved(data);
764
1021
  } catch (err) {
765
1022
  console.error("[ContentManagerRouter] save error:", err);
766
1023
  }
767
- }, [saveDraft]);
1024
+ }, [saveDraft, markSaved]);
768
1025
  const handlePublish = useCallback2(
769
1026
  async (data) => {
770
1027
  try {
771
1028
  await saveDraft(data);
772
- setHasUnsavedChanges(false);
1029
+ markSaved(data);
773
1030
  await publish(false);
774
1031
  } catch (err) {
775
1032
  console.error("[ContentManagerRouter] publish error:", err);
776
1033
  }
777
1034
  },
778
- [saveDraft, publish]
1035
+ [saveDraft, publish, markSaved]
779
1036
  );
780
1037
  const handleRevert = useCallback2(async () => {
781
1038
  try {
782
1039
  await revertToPublished();
783
- setHasUnsavedChanges(false);
1040
+ setReloadNonce((n) => n + 1);
784
1041
  } catch (err) {
785
1042
  console.error("[ContentManagerRouter] revert error:", err);
786
1043
  }
@@ -791,14 +1048,14 @@ var ContentEditorRoute = ({ config, backButton }) => {
791
1048
  setIsApplyingVersion(true);
792
1049
  try {
793
1050
  await saveDraft(versionData);
794
- setHasUnsavedChanges(false);
1051
+ markSaved(versionData);
795
1052
  versionHistory.clearSelection();
796
1053
  } catch (err) {
797
1054
  console.error("[ContentManagerRouter] apply version error:", err);
798
1055
  } finally {
799
1056
  setIsApplyingVersion(false);
800
1057
  }
801
- }, [versionHistory, saveDraft]);
1058
+ }, [versionHistory, saveDraft, markSaved]);
802
1059
  const contentConfig = useMemo2(() => {
803
1060
  const otherRootFields = Object.fromEntries(
804
1061
  Object.entries(config.root?.fields ?? {}).filter(([k]) => k !== "title")
@@ -820,22 +1077,38 @@ var ContentEditorRoute = ({ config, backButton }) => {
820
1077
  }
821
1078
  };
822
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
+ );
823
1096
  if (loading) {
824
- return /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100vh" }, children: /* @__PURE__ */ jsxs3(Spacings3.Stack, { scale: "m", alignItems: "center", children: [
825
- /* @__PURE__ */ jsx3(LoadingSpinner3, {}),
826
- /* @__PURE__ */ jsx3(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" })
827
1100
  ] }) });
828
1101
  }
829
1102
  if (error) {
830
- return /* @__PURE__ */ jsx3("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs3(Text3.Body, { tone: "negative", children: [
831
- /* @__PURE__ */ jsx3("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:" }),
832
1105
  " ",
833
1106
  error
834
1107
  ] }) });
835
1108
  }
836
1109
  const activeData = versionHistory.previewData ?? currentData;
837
1110
  const toolbarStates = states;
838
- return /* @__PURE__ */ jsx3(
1111
+ return /* @__PURE__ */ jsxs3(
839
1112
  VersionHistoryProvider2,
840
1113
  {
841
1114
  diff,
@@ -848,82 +1121,164 @@ var ContentEditorRoute = ({ config, backButton }) => {
848
1121
  onApply: () => void handleApplyVersion(),
849
1122
  onDiscard: versionHistory.clearSelection,
850
1123
  onLoadVersions: versionHistory.openPanel,
851
- children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
852
- /* @__PURE__ */ jsxs3("div", { style: NAV_BAR_STYLE, children: [
853
- backButton,
854
- backButton && /* @__PURE__ */ jsx3(Text3.Body, { tone: "secondary", children: "/" }),
855
- /* @__PURE__ */ jsx3(
856
- FlatButton2,
857
- {
858
- label: "Content Items",
859
- icon: /* @__PURE__ */ jsx3(AngleLeftIcon, {}),
860
- iconPosition: "left",
861
- onClick: () => history.push("/")
862
- }
863
- ),
864
- /* @__PURE__ */ jsx3(Text3.Body, { tone: "secondary", children: "/" }),
865
- /* @__PURE__ */ jsx3(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
+ ] })
866
1202
  ] }),
867
- /* @__PURE__ */ jsx3("div", { style: { flex: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsx3(ComponentSearchProvider2, { children: /* @__PURE__ */ jsx3(
868
- Puck2,
1203
+ /* @__PURE__ */ jsx5(
1204
+ UnsavedChangesDialog,
869
1205
  {
870
- config: contentConfig,
871
- data: activeData,
872
- onChange: handleChange,
873
- onPublish: handlePublish,
874
- overrides: {
875
- headerActions: () => versionHistory.isPreviewingHistory ? /* @__PURE__ */ jsxs3(Spacings3.Inline, { scale: "s", alignItems: "center", children: [
876
- /* @__PURE__ */ jsx3(
877
- VersionPreviewBanner2,
878
- {
879
- timestamp: versionHistory.selectedVersion.timestamp,
880
- onApply: () => void handleApplyVersion(),
881
- onDiscard: versionHistory.clearSelection,
882
- isApplying: isApplyingVersion
883
- }
884
- ),
885
- /* @__PURE__ */ jsx3(VersionHistoryButton2, { disabled: isApplyingVersion })
886
- ] }) : /* @__PURE__ */ jsx3(
887
- EditorToolbar2,
888
- {
889
- saving,
890
- isDirty: hasUnsavedChanges,
891
- states: toolbarStates,
892
- onSave: () => void handleSave(),
893
- onPublish: () => void handlePublish(activeData),
894
- onRevert: () => void handleRevert(),
895
- showPublishButton: true
896
- }
897
- ),
898
- components: ({ children }) => /* @__PURE__ */ jsx3(ComponentsPanel2, { children }),
899
- componentItem: ({ children, name }) => /* @__PURE__ */ jsx3(ComponentItemFilter2, { name, children }),
900
- fields: ({ children, isLoading }) => /* @__PURE__ */ jsx3(VersionAwareFieldsPanel2, { isLoading, children })
901
- }
902
- },
903
- versionHistory.selectedVersionId ?? "current"
904
- ) }) })
905
- ] })
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
+ ]
906
1223
  }
907
1224
  );
908
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
+ };
909
1257
  var ContentManagerRouterInner = ({
910
1258
  config,
911
1259
  defaultContentType,
912
1260
  backButton
913
1261
  }) => /* @__PURE__ */ jsxs3(Switch, { children: [
914
- /* @__PURE__ */ jsx3(
1262
+ /* @__PURE__ */ jsx5(
915
1263
  Route,
916
1264
  {
917
1265
  exact: true,
918
1266
  path: "/",
919
- render: () => /* @__PURE__ */ jsx3(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 })
920
1275
  }
921
1276
  ),
922
- /* @__PURE__ */ jsx3(
1277
+ /* @__PURE__ */ jsx5(
923
1278
  Route,
924
1279
  {
925
1280
  path: "/:contentKey",
926
- render: () => /* @__PURE__ */ jsx3(ContentEditorRoute, { config, backButton })
1281
+ render: () => /* @__PURE__ */ jsx5(ContentEditorRoute, { config, backButton })
927
1282
  }
928
1283
  )
929
1284
  ] });
@@ -933,17 +1288,19 @@ var ContentManager = ({
933
1288
  projectKey,
934
1289
  businessUnitKey,
935
1290
  jwtToken,
1291
+ locale,
936
1292
  config = DEFAULT_CONFIG,
937
1293
  defaultContentType,
938
1294
  backButton
939
- }) => /* @__PURE__ */ jsx3(
1295
+ }) => /* @__PURE__ */ jsx5(EnsureNimbusProvider, { locale, children: /* @__PURE__ */ jsx5(EnsureIntlProvider, { children: /* @__PURE__ */ jsx5(
940
1296
  PuckApiProvider3,
941
1297
  {
942
1298
  baseURL,
943
1299
  projectKey,
944
1300
  businessUnitKey,
945
1301
  jwtToken,
946
- children: /* @__PURE__ */ jsx3(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx3(
1302
+ locale,
1303
+ children: /* @__PURE__ */ jsx5(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx5(
947
1304
  ContentManagerRouterInner,
948
1305
  {
949
1306
  config,
@@ -952,7 +1309,7 @@ var ContentManager = ({
952
1309
  }
953
1310
  ) })
954
1311
  }
955
- );
1312
+ ) }) });
956
1313
  export {
957
1314
  ContentEditor,
958
1315
  ContentManager,