@commercetools-demo/puck-page-manager 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/PageManager.tsx
2
- import { useState } from "react";
2
+ import { useCallback, useState } from "react";
3
3
  import {
4
4
  BrowserRouter,
5
5
  Switch,
@@ -11,9 +11,15 @@ import {
11
11
  import {
12
12
  PuckApiProvider,
13
13
  usePuckPages,
14
+ usePuckTemplates,
14
15
  usePuckApiContext
15
16
  } from "@commercetools-demo/puck-api";
16
- import { PuckEditor, defaultPuckConfig } from "@commercetools-demo/puck-editor";
17
+ import {
18
+ PropertiesResizer,
19
+ PuckEditor,
20
+ UnsavedChangesDialog,
21
+ defaultPuckConfig
22
+ } from "@commercetools-demo/puck-editor";
17
23
  import { PuckRenderer } from "@commercetools-demo/puck-renderer";
18
24
 
19
25
  // src/EnsureIntlProvider.tsx
@@ -40,42 +46,51 @@ var EnsureIntlProvider = ({ children }) => {
40
46
  );
41
47
  };
42
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
+
43
57
  // src/PageManager.tsx
44
- import DataTable from "@commercetools-uikit/data-table";
45
- import PrimaryButton from "@commercetools-uikit/primary-button";
46
- import SecondaryButton from "@commercetools-uikit/secondary-button";
47
- import FlatButton from "@commercetools-uikit/flat-button";
48
- import Card from "@commercetools-uikit/card";
49
- import Spacings from "@commercetools-uikit/spacings";
50
- import Text from "@commercetools-uikit/text";
51
- import LoadingSpinner from "@commercetools-uikit/loading-spinner";
52
- import TextInput from "@commercetools-uikit/text-input";
53
- import Label from "@commercetools-uikit/label";
54
- import { PlusThinIcon, AngleLeftIcon } from "@commercetools-uikit/icons";
55
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
58
+ import {
59
+ Badge,
60
+ Button,
61
+ Card,
62
+ DataTable,
63
+ Dialog,
64
+ FormField,
65
+ Icon,
66
+ IconButton,
67
+ LoadingSpinner,
68
+ Select,
69
+ Stack,
70
+ Text,
71
+ TextInput
72
+ } from "@commercetools/nimbus";
73
+ import {
74
+ Add,
75
+ ChevronLeft,
76
+ Close,
77
+ Delete,
78
+ Edit,
79
+ Visibility
80
+ } from "@commercetools/nimbus-icons";
81
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
56
82
  var DEFAULT_CONFIG = {
57
83
  ...defaultPuckConfig,
58
84
  components: { ...defaultPuckConfig.components }
59
85
  };
86
+ var STATUS_BADGE = {
87
+ draft: { colorPalette: "warning", label: "Draft" },
88
+ published: { colorPalette: "positive", label: "Published" },
89
+ none: { colorPalette: "neutral", label: "No state" }
90
+ };
60
91
  var StatusBadge = ({ variant }) => {
61
- const styles = variant === "published" ? { background: "var(--color-success-95)", color: "var(--color-success-40)", border: "1px solid var(--color-success-85)" } : variant === "draft" ? { background: "var(--color-warning-95)", color: "var(--color-warning-40)", border: "1px solid var(--color-warning-85)" } : { background: "var(--color-neutral-95)", color: "var(--color-neutral-50)", border: "1px solid var(--color-neutral-85)" };
62
- return /* @__PURE__ */ jsx2(
63
- "span",
64
- {
65
- style: {
66
- ...styles,
67
- display: "inline-flex",
68
- alignItems: "center",
69
- padding: "2px 8px",
70
- borderRadius: "var(--border-radius-20)",
71
- fontSize: "var(--font-size-10)",
72
- fontWeight: "var(--font-weight-600)",
73
- marginRight: "4px",
74
- whiteSpace: "nowrap"
75
- },
76
- children: variant === "published" ? "Published" : variant === "draft" ? "Draft" : "No state"
77
- }
78
- );
92
+ const meta = STATUS_BADGE[variant];
93
+ return /* @__PURE__ */ jsx3(Badge, { colorPalette: meta.colorPalette, size: "xs", children: meta.label });
79
94
  };
80
95
  var NAV_BAR_STYLE = {
81
96
  position: "sticky",
@@ -85,26 +100,23 @@ var NAV_BAR_STYLE = {
85
100
  gap: "12px",
86
101
  padding: "8px 16px",
87
102
  background: "var(--color-surface, #fff)",
88
- borderBottom: "1px solid var(--color-neutral-90)",
103
+ borderBottom: "1px solid var(--color-neutral-90, #e0e0e0)",
89
104
  zIndex: 200,
90
105
  flexShrink: 0
91
106
  };
92
- var COLUMNS = [
93
- { key: "name", label: "Name" },
94
- { key: "slug", label: "Slug" },
95
- { key: "status", label: "Status" },
96
- { key: "updatedAt", label: "Updated" },
97
- { key: "actions", label: "Actions", shouldIgnoreRowClick: true }
98
- ];
99
107
  var PageList = ({ backButton }) => {
100
108
  const history = useHistory();
101
109
  const { pages, loading, error, createPage, deletePage, refresh } = usePuckPages();
110
+ const { templates } = usePuckTemplates("page");
102
111
  const [creating, setCreating] = useState(false);
103
112
  const [newName, setNewName] = useState("");
104
113
  const [newSlug, setNewSlug] = useState("");
114
+ const [templateKey, setTemplateKey] = useState("");
105
115
  const [formError, setFormError] = useState("");
106
116
  const [submitting, setSubmitting] = useState(false);
107
117
  const [deleting, setDeleting] = useState(null);
118
+ const [pendingDelete, setPendingDelete] = useState(null);
119
+ const [search, setSearch] = useState("");
108
120
  const handleCreate = async () => {
109
121
  if (!newName.trim()) {
110
122
  setFormError("Name is required");
@@ -121,10 +133,15 @@ var PageList = ({ backButton }) => {
121
133
  name: newName.trim(),
122
134
  slug: newSlug.trim().startsWith("/") ? newSlug.trim() : `/${newSlug.trim()}`
123
135
  };
136
+ if (templateKey) {
137
+ const template = templates.find((t) => t.key === templateKey);
138
+ if (template) input.puckData = template.value.puckData;
139
+ }
124
140
  const created = await createPage(input);
125
141
  setCreating(false);
126
142
  setNewName("");
127
143
  setNewSlug("");
144
+ setTemplateKey("");
128
145
  history.push(`/${created.key}/edit`, { pageName: created.value.name });
129
146
  } catch (err) {
130
147
  setFormError(err.message);
@@ -133,206 +150,323 @@ var PageList = ({ backButton }) => {
133
150
  }
134
151
  };
135
152
  const handleDelete = async (page) => {
136
- if (!confirm(`Delete "${page.value.name}"? This cannot be undone.`)) return;
137
153
  setDeleting(page.key);
138
154
  try {
139
155
  await deletePage(page.key);
140
156
  await refresh();
157
+ setPendingDelete(null);
141
158
  } finally {
142
159
  setDeleting(null);
143
160
  }
144
161
  };
145
162
  if (loading) {
146
- return /* @__PURE__ */ jsx2("div", { style: { padding: "64px", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx2(LoadingSpinner, {}) });
163
+ return /* @__PURE__ */ jsx3("div", { style: { padding: "64px", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx3(LoadingSpinner, {}) });
147
164
  }
148
165
  if (error) {
149
- return /* @__PURE__ */ jsx2("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs(Text.Body, { tone: "negative", children: [
166
+ return /* @__PURE__ */ jsx3("div", { style: { padding: "32px" }, children: /* @__PURE__ */ jsxs(Text, { color: "critical.11", children: [
150
167
  "Error: ",
151
168
  error
152
169
  ] }) });
153
170
  }
154
- const rows = pages.map((p) => ({ ...p, id: p.key }));
155
- return /* @__PURE__ */ jsx2("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "l", children: [
156
- /* @__PURE__ */ jsxs(Spacings.Inline, { justifyContent: "space-between", alignItems: "center", children: [
157
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "m", alignItems: "center", children: [
158
- backButton,
159
- /* @__PURE__ */ jsx2(Text.Headline, { as: "h1", children: "Puck Pages" })
160
- ] }),
161
- /* @__PURE__ */ jsx2(
162
- PrimaryButton,
171
+ const term = search.trim().toLowerCase();
172
+ const filteredPages = term ? pages.filter(
173
+ (p) => p.value.name.toLowerCase().includes(term) || p.value.slug.toLowerCase().includes(term)
174
+ ) : pages;
175
+ const rows = filteredPages.map((p) => ({ ...p, id: p.key }));
176
+ const columns = [
177
+ {
178
+ id: "name",
179
+ header: "Name",
180
+ accessor: (row) => row.value.name,
181
+ render: ({ row }) => /* @__PURE__ */ jsx3(Text, { fontWeight: "bold", children: row.value.name })
182
+ },
183
+ {
184
+ id: "slug",
185
+ header: "Slug",
186
+ accessor: (row) => row.value.slug,
187
+ render: ({ row }) => /* @__PURE__ */ jsx3(
188
+ "code",
163
189
  {
164
- label: "New Page",
165
- iconLeft: /* @__PURE__ */ jsx2(PlusThinIcon, {}),
166
- onClick: () => setCreating(true)
190
+ style: {
191
+ background: "#f4f4f4",
192
+ padding: "2px 6px",
193
+ borderRadius: "4px",
194
+ fontSize: "11px",
195
+ fontFamily: "monospace"
196
+ },
197
+ children: row.value.slug
167
198
  }
168
199
  )
169
- ] }),
170
- creating && /* @__PURE__ */ jsx2(Card, { insetScale: "l", children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "m", children: [
171
- /* @__PURE__ */ jsx2(Text.Subheadline, { as: "h4", isBold: true, children: "Create New Page" }),
172
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "m", children: [
173
- /* @__PURE__ */ jsx2("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
174
- /* @__PURE__ */ jsx2(Label, { htmlFor: "new-page-name", children: "Name *" }),
175
- /* @__PURE__ */ jsx2(
176
- TextInput,
177
- {
178
- id: "new-page-name",
179
- value: newName,
180
- onChange: (e) => setNewName(e.target.value),
181
- placeholder: "Home Page"
182
- }
183
- )
184
- ] }) }),
185
- /* @__PURE__ */ jsx2("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
186
- /* @__PURE__ */ jsx2(Label, { htmlFor: "new-page-slug", children: "Slug *" }),
187
- /* @__PURE__ */ jsx2(
188
- TextInput,
189
- {
190
- id: "new-page-slug",
191
- value: newSlug,
192
- onChange: (e) => setNewSlug(e.target.value),
193
- placeholder: "/home"
194
- }
195
- )
196
- ] }) })
197
- ] }),
198
- formError && /* @__PURE__ */ jsx2(Text.Body, { tone: "negative", children: formError }),
199
- /* @__PURE__ */ jsxs(Spacings.Inline, { scale: "s", children: [
200
- /* @__PURE__ */ jsx2(
201
- PrimaryButton,
200
+ },
201
+ {
202
+ id: "status",
203
+ header: "Status",
204
+ accessor: () => "",
205
+ isSortable: false,
206
+ render: ({ row }) => /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "100", wrap: "wrap", children: [
207
+ row.states.draft && /* @__PURE__ */ jsx3(StatusBadge, { variant: "draft" }),
208
+ row.states.published && /* @__PURE__ */ jsx3(StatusBadge, { variant: "published" }),
209
+ !row.states.draft && !row.states.published && /* @__PURE__ */ jsx3(StatusBadge, { variant: "none" })
210
+ ] })
211
+ },
212
+ {
213
+ id: "updatedAt",
214
+ header: "Updated",
215
+ accessor: (row) => row.value.updatedAt,
216
+ render: ({ row }) => /* @__PURE__ */ jsx3(Text, { fontSize: "xs", color: "neutral.11", children: new Date(row.value.updatedAt).toLocaleString() })
217
+ },
218
+ {
219
+ id: "actions",
220
+ header: "Actions",
221
+ accessor: () => "",
222
+ isSortable: false,
223
+ render: ({ row }) => /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "100", alignItems: "center", children: [
224
+ /* @__PURE__ */ jsx3(
225
+ IconButton,
202
226
  {
203
- label: submitting ? "Creating\u2026" : "Create",
204
- onClick: () => void handleCreate(),
205
- isDisabled: submitting
227
+ "aria-label": `Edit ${row.value.name}`,
228
+ variant: "ghost",
229
+ size: "xs",
230
+ onPress: () => history.push(`/${row.key}/edit`, { pageName: row.value.name }),
231
+ children: /* @__PURE__ */ jsx3(Edit, {})
206
232
  }
207
233
  ),
208
- /* @__PURE__ */ jsx2(
209
- SecondaryButton,
234
+ /* @__PURE__ */ jsx3(
235
+ IconButton,
210
236
  {
211
- label: "Cancel",
212
- onClick: () => {
213
- setCreating(false);
214
- setFormError("");
215
- }
237
+ "aria-label": `Preview ${row.value.name}`,
238
+ variant: "ghost",
239
+ size: "xs",
240
+ onPress: () => history.push(`/${row.key}/preview`, { pageName: row.value.name }),
241
+ children: /* @__PURE__ */ jsx3(Visibility, {})
242
+ }
243
+ ),
244
+ /* @__PURE__ */ jsx3(
245
+ IconButton,
246
+ {
247
+ "aria-label": `Delete ${row.value.name}`,
248
+ variant: "ghost",
249
+ colorPalette: "critical",
250
+ size: "xs",
251
+ isDisabled: deleting === row.key,
252
+ onPress: () => setPendingDelete(row),
253
+ children: /* @__PURE__ */ jsx3(Delete, {})
216
254
  }
217
255
  )
218
256
  ] })
219
- ] }) }),
220
- pages.length === 0 && !creating ? /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "m", alignItems: "center", children: [
221
- /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: "No pages yet." }),
222
- /* @__PURE__ */ jsx2(
223
- PrimaryButton,
224
- {
225
- label: "Create first page",
226
- iconLeft: /* @__PURE__ */ jsx2(PlusThinIcon, {}),
227
- onClick: () => setCreating(true)
228
- }
229
- )
230
- ] }) : pages.length > 0 ? /* @__PURE__ */ jsx2(
231
- DataTable,
232
- {
233
- columns: COLUMNS,
234
- rows,
235
- itemRenderer: (row, column) => {
236
- switch (column.key) {
237
- case "name":
238
- return /* @__PURE__ */ jsxs(Spacings.Stack, { scale: "xs", children: [
239
- /* @__PURE__ */ jsx2(Text.Body, { isBold: true, children: row.value.name }),
240
- /* @__PURE__ */ jsx2(Text.Detail, { tone: "secondary", children: row.key })
241
- ] });
242
- case "slug":
243
- return /* @__PURE__ */ jsx2(
244
- "code",
245
- {
246
- style: {
247
- background: "var(--color-neutral-95)",
248
- padding: "2px 6px",
249
- borderRadius: "var(--border-radius-4)",
250
- fontSize: "var(--font-size-10)",
251
- fontFamily: "monospace"
252
- },
253
- children: row.value.slug
254
- }
255
- );
256
- case "status":
257
- return /* @__PURE__ */ jsxs("span", { children: [
258
- row.states.draft && /* @__PURE__ */ jsx2(StatusBadge, { variant: "draft" }),
259
- row.states.published && /* @__PURE__ */ jsx2(StatusBadge, { variant: "published" }),
260
- !row.states.draft && !row.states.published && /* @__PURE__ */ jsx2(StatusBadge, { variant: "none" })
261
- ] });
262
- case "updatedAt":
263
- return /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: new Date(row.value.updatedAt).toLocaleString() });
264
- case "actions":
265
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
266
- /* @__PURE__ */ jsx2(
267
- PrimaryButton,
268
- {
269
- label: "Edit",
270
- size: "20",
271
- onClick: () => history.push(`/${row.key}/edit`, { pageName: row.value.name })
272
- }
273
- ),
274
- /* @__PURE__ */ jsx2(
275
- SecondaryButton,
276
- {
277
- label: "Preview",
278
- size: "20",
279
- onClick: () => history.push(`/${row.key}/preview`, { pageName: row.value.name })
280
- }
281
- ),
282
- /* @__PURE__ */ jsx2(
283
- FlatButton,
284
- {
285
- tone: "critical",
286
- label: deleting === row.key ? "\u2026" : "Delete",
287
- isDisabled: deleting === row.key,
288
- onClick: () => void handleDelete(row)
289
- }
290
- )
291
- ] });
292
- default:
293
- return null;
257
+ }
258
+ ];
259
+ return /* @__PURE__ */ jsxs("div", { style: { maxWidth: "1200px", margin: "0 auto", padding: "32px 24px" }, children: [
260
+ /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: "600", children: [
261
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", children: [
262
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "400", alignItems: "center", children: [
263
+ backButton,
264
+ /* @__PURE__ */ jsx3(Text, { as: "h1", fontSize: "2xl", fontWeight: "700", children: "Pages" })
265
+ ] }),
266
+ /* @__PURE__ */ jsxs(Button, { variant: "solid", onPress: () => setCreating(true), children: [
267
+ /* @__PURE__ */ jsx3(Icon, { as: Add }),
268
+ " New Page"
269
+ ] })
270
+ ] }),
271
+ creating && /* @__PURE__ */ jsx3(Card.Root, { variant: "outlined", children: /* @__PURE__ */ jsx3(Card.Body, { children: /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: "400", children: [
272
+ /* @__PURE__ */ jsx3(Text, { as: "h4", fontSize: "xl", fontWeight: "700", children: "Create New Page" }),
273
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "400", children: [
274
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(FormField.Root, { isRequired: true, children: [
275
+ /* @__PURE__ */ jsx3(FormField.Label, { children: "Name" }),
276
+ /* @__PURE__ */ jsx3(FormField.Input, { children: /* @__PURE__ */ jsx3(
277
+ TextInput,
278
+ {
279
+ value: newName,
280
+ onChange: (v) => setNewName(v),
281
+ placeholder: "Home Page"
282
+ }
283
+ ) })
284
+ ] }) }),
285
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxs(FormField.Root, { isRequired: true, children: [
286
+ /* @__PURE__ */ jsx3(FormField.Label, { children: "Slug" }),
287
+ /* @__PURE__ */ jsx3(FormField.Input, { children: /* @__PURE__ */ jsx3(
288
+ TextInput,
289
+ {
290
+ value: newSlug,
291
+ onChange: (v) => setNewSlug(v),
292
+ placeholder: "/home"
293
+ }
294
+ ) })
295
+ ] }) })
296
+ ] }),
297
+ /* @__PURE__ */ jsxs(FormField.Root, { children: [
298
+ /* @__PURE__ */ jsx3(FormField.Label, { children: "Template" }),
299
+ /* @__PURE__ */ jsx3(FormField.Input, { children: /* @__PURE__ */ jsx3(
300
+ Select.Root,
301
+ {
302
+ "aria-label": "Template",
303
+ selectedKey: templateKey || "empty",
304
+ onSelectionChange: (key) => setTemplateKey(key == null || key === "empty" ? "" : String(key)),
305
+ children: /* @__PURE__ */ jsxs(Select.Options, { children: [
306
+ /* @__PURE__ */ jsx3(Select.Option, { id: "empty", children: "Empty" }),
307
+ templates.map((t) => /* @__PURE__ */ jsx3(Select.Option, { id: t.key, children: t.value.name }, t.key))
308
+ ] })
309
+ }
310
+ ) })
311
+ ] }),
312
+ formError && /* @__PURE__ */ jsx3(Text, { color: "critical.11", children: formError }),
313
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "200", children: [
314
+ /* @__PURE__ */ jsx3(Button, { variant: "solid", onPress: () => void handleCreate(), isDisabled: submitting, children: submitting ? "Creating\u2026" : "Create" }),
315
+ /* @__PURE__ */ jsx3(
316
+ Button,
317
+ {
318
+ variant: "outline",
319
+ onPress: () => {
320
+ setCreating(false);
321
+ setFormError("");
322
+ },
323
+ children: "Cancel"
324
+ }
325
+ )
326
+ ] })
327
+ ] }) }) }),
328
+ pages.length === 0 && !creating ? /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: "400", alignItems: "center", children: [
329
+ /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: "No pages yet." }),
330
+ /* @__PURE__ */ jsxs(Button, { variant: "solid", onPress: () => setCreating(true), children: [
331
+ /* @__PURE__ */ jsx3(Icon, { as: Add }),
332
+ " Create first page"
333
+ ] })
334
+ ] }) : pages.length > 0 ? /* @__PURE__ */ jsxs(Stack, { direction: "column", gap: "400", children: [
335
+ /* @__PURE__ */ jsx3("div", { style: { maxWidth: 360 }, children: /* @__PURE__ */ jsx3(
336
+ TextInput,
337
+ {
338
+ "aria-label": "Search pages",
339
+ placeholder: "Search by name or path\u2026",
340
+ value: search,
341
+ onChange: (v) => setSearch(v),
342
+ width: "100%",
343
+ trailingElement: search !== "" ? /* @__PURE__ */ jsx3(
344
+ IconButton,
345
+ {
346
+ "aria-label": "Clear search",
347
+ variant: "ghost",
348
+ colorPalette: "neutral",
349
+ size: "2xs",
350
+ onPress: () => setSearch(""),
351
+ children: /* @__PURE__ */ jsx3(Close, {})
352
+ }
353
+ ) : void 0
294
354
  }
295
- }
355
+ ) }),
356
+ /* @__PURE__ */ jsxs("div", { className: "puck-page-list", children: [
357
+ /* @__PURE__ */ jsx3("style", { children: `
358
+ .puck-page-list .pin-rows-column-header,
359
+ .puck-page-list [data-slot="pin-row-cell"] { display: none !important; }
360
+ ` }),
361
+ /* @__PURE__ */ jsx3(DataTable, { columns, rows, "aria-label": "Pages" })
362
+ ] })
363
+ ] }) : null
364
+ ] }),
365
+ /* @__PURE__ */ jsx3(
366
+ Dialog.Root,
367
+ {
368
+ isOpen: pendingDelete !== null,
369
+ onOpenChange: (open) => {
370
+ if (!open) setPendingDelete(null);
371
+ },
372
+ children: /* @__PURE__ */ jsxs(Dialog.Content, { children: [
373
+ /* @__PURE__ */ jsxs(Dialog.Header, { children: [
374
+ /* @__PURE__ */ jsx3(Dialog.Title, { children: "Delete page?" }),
375
+ /* @__PURE__ */ jsx3(Dialog.CloseTrigger, {})
376
+ ] }),
377
+ /* @__PURE__ */ jsx3(Dialog.Body, { children: /* @__PURE__ */ jsxs(Text, { children: [
378
+ "Are you sure you want to delete",
379
+ " ",
380
+ /* @__PURE__ */ jsx3(Text, { as: "span", fontWeight: "700", children: pendingDelete?.value.name }),
381
+ "? This cannot be undone."
382
+ ] }) }),
383
+ /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
384
+ /* @__PURE__ */ jsx3(Button, { slot: "close", variant: "outline", isDisabled: deleting !== null, children: "Cancel" }),
385
+ /* @__PURE__ */ jsx3(
386
+ Button,
387
+ {
388
+ colorPalette: "critical",
389
+ isDisabled: deleting !== null,
390
+ onPress: () => {
391
+ if (pendingDelete) void handleDelete(pendingDelete);
392
+ },
393
+ children: deleting !== null ? "Deleting\u2026" : "Delete"
394
+ }
395
+ )
396
+ ] })
397
+ ] })
296
398
  }
297
- ) : null
298
- ] }) });
399
+ )
400
+ ] });
299
401
  };
300
402
  var PageEditorRoute = ({ config, backButton }) => {
301
403
  const { pageKey } = useParams();
302
404
  const history = useHistory();
303
405
  const location = useLocation();
304
- const { baseURL, projectKey, businessUnitKey, jwtToken } = usePuckApiContext();
406
+ const { baseURL, projectKey, businessUnitKey, jwtToken, locale } = usePuckApiContext();
305
407
  const pageName = location.state?.pageName ?? pageKey ?? "Page";
408
+ const [isDirty, setIsDirty] = useState(false);
409
+ const [pendingNav, setPendingNav] = useState(null);
410
+ const guardedNavigate = useCallback(
411
+ (navFn) => {
412
+ if (isDirty) {
413
+ setPendingNav(() => navFn);
414
+ } else {
415
+ navFn();
416
+ }
417
+ },
418
+ [isDirty]
419
+ );
306
420
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
307
421
  /* @__PURE__ */ jsxs("div", { style: NAV_BAR_STYLE, children: [
308
422
  backButton,
309
- backButton && /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: "/" }),
310
- /* @__PURE__ */ jsx2(
311
- FlatButton,
423
+ backButton && /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: "/" }),
424
+ /* @__PURE__ */ jsxs(
425
+ Button,
312
426
  {
313
- label: "Pages",
314
- icon: /* @__PURE__ */ jsx2(AngleLeftIcon, {}),
315
- iconPosition: "left",
316
- onClick: () => history.push("/")
427
+ variant: "ghost",
428
+ onPress: () => guardedNavigate(() => history.push("/")),
429
+ children: [
430
+ /* @__PURE__ */ jsx3(Icon, { as: ChevronLeft }),
431
+ " Pages"
432
+ ]
317
433
  }
318
434
  ),
319
- /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: "/" }),
320
- /* @__PURE__ */ jsx2(Text.Body, { isBold: true, children: pageName })
435
+ /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: "/" }),
436
+ /* @__PURE__ */ jsx3(Text, { fontWeight: "bold", children: pageName })
321
437
  ] }),
322
- /* @__PURE__ */ jsx2("div", { style: { flex: 1, overflow: "hidden" }, children: /* @__PURE__ */ jsx2(
323
- PuckEditor,
324
- {
325
- baseURL,
326
- projectKey,
327
- businessUnitKey,
328
- jwtToken: jwtToken ?? "",
329
- pageKey,
330
- config,
331
- onError: (err) => {
332
- console.error("[PageManager] editor error:", err);
438
+ /* @__PURE__ */ jsxs("div", { className: "puck-editor-fill", style: { flex: 1, overflow: "hidden" }, children: [
439
+ /* @__PURE__ */ jsx3(
440
+ PuckEditor,
441
+ {
442
+ baseURL,
443
+ projectKey,
444
+ businessUnitKey,
445
+ jwtToken: jwtToken ?? "",
446
+ locale,
447
+ pageKey,
448
+ config,
449
+ onDirtyChange: setIsDirty,
450
+ onPreview: () => guardedNavigate(
451
+ () => history.push(`/${pageKey}/preview`, { pageName })
452
+ ),
453
+ onError: (err) => {
454
+ console.error("[PageManager] editor error:", err);
455
+ }
333
456
  }
457
+ ),
458
+ /* @__PURE__ */ jsx3(PropertiesResizer, {})
459
+ ] }),
460
+ /* @__PURE__ */ jsx3(
461
+ UnsavedChangesDialog,
462
+ {
463
+ isOpen: pendingNav !== null,
464
+ onOpenChange: (open) => {
465
+ if (!open) setPendingNav(null);
466
+ },
467
+ onConfirm: () => pendingNav?.()
334
468
  }
335
- ) })
469
+ )
336
470
  ] });
337
471
  };
338
472
  var PagePreviewRoute = ({ config, backButton }) => {
@@ -343,53 +477,36 @@ var PagePreviewRoute = ({ config, backButton }) => {
343
477
  return /* @__PURE__ */ jsxs("div", { children: [
344
478
  /* @__PURE__ */ jsxs("div", { style: NAV_BAR_STYLE, children: [
345
479
  backButton,
346
- backButton && /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: "/" }),
347
- /* @__PURE__ */ jsx2(
348
- FlatButton,
349
- {
350
- label: "Pages",
351
- icon: /* @__PURE__ */ jsx2(AngleLeftIcon, {}),
352
- iconPosition: "left",
353
- onClick: () => history.push("/")
354
- }
355
- ),
356
- /* @__PURE__ */ jsx2(Text.Body, { tone: "secondary", children: "/" }),
357
- /* @__PURE__ */ jsx2(Text.Body, { isBold: true, children: pageName }),
358
- /* @__PURE__ */ jsx2(
359
- "span",
360
- {
361
- style: {
362
- background: "var(--color-primary-95)",
363
- color: "var(--color-primary-25)",
364
- border: "1px solid var(--color-primary-85)",
365
- display: "inline-flex",
366
- alignItems: "center",
367
- padding: "2px 10px",
368
- borderRadius: "var(--border-radius-20)",
369
- fontSize: "var(--font-size-10)",
370
- fontWeight: "var(--font-weight-600)"
371
- },
372
- children: "Preview"
373
- }
374
- )
480
+ backButton && /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: "/" }),
481
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", onPress: () => history.push("/"), children: [
482
+ /* @__PURE__ */ jsx3(Icon, { as: ChevronLeft }),
483
+ " Pages"
484
+ ] }),
485
+ /* @__PURE__ */ jsx3(Text, { color: "neutral.11", children: "/" }),
486
+ /* @__PURE__ */ jsx3(Text, { fontWeight: "bold", children: pageName }),
487
+ /* @__PURE__ */ jsx3(Badge, { colorPalette: "primary", size: "xs", children: "Preview" }),
488
+ /* @__PURE__ */ jsx3("div", { style: { marginLeft: "auto" }, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "xs", onPress: () => history.goBack(), children: [
489
+ /* @__PURE__ */ jsx3(Icon, { as: Close }),
490
+ " Close preview"
491
+ ] }) })
375
492
  ] }),
376
- /* @__PURE__ */ jsx2(PuckRenderer, { pageKey, mode: "preview", config })
493
+ /* @__PURE__ */ jsx3(PuckRenderer, { pageKey, mode: "preview", config })
377
494
  ] });
378
495
  };
379
496
  var PageManagerInner = ({ config, backButton }) => /* @__PURE__ */ jsxs(Switch, { children: [
380
- /* @__PURE__ */ jsx2(Route, { exact: true, path: "/", render: () => /* @__PURE__ */ jsx2(PageList, { backButton }) }),
381
- /* @__PURE__ */ jsx2(
497
+ /* @__PURE__ */ jsx3(Route, { exact: true, path: "/", render: () => /* @__PURE__ */ jsx3(PageList, { backButton }) }),
498
+ /* @__PURE__ */ jsx3(
382
499
  Route,
383
500
  {
384
501
  path: "/:pageKey/edit",
385
- render: () => /* @__PURE__ */ jsx2(PageEditorRoute, { config, backButton })
502
+ render: () => /* @__PURE__ */ jsx3(PageEditorRoute, { config, backButton })
386
503
  }
387
504
  ),
388
- /* @__PURE__ */ jsx2(
505
+ /* @__PURE__ */ jsx3(
389
506
  Route,
390
507
  {
391
508
  path: "/:pageKey/preview",
392
- render: () => /* @__PURE__ */ jsx2(PagePreviewRoute, { config, backButton })
509
+ render: () => /* @__PURE__ */ jsx3(PagePreviewRoute, { config, backButton })
393
510
  }
394
511
  )
395
512
  ] });
@@ -399,18 +516,20 @@ var PageManager = ({
399
516
  projectKey,
400
517
  businessUnitKey,
401
518
  jwtToken,
519
+ locale,
402
520
  config = DEFAULT_CONFIG,
403
521
  backButton
404
- }) => /* @__PURE__ */ jsx2(EnsureIntlProvider, { children: /* @__PURE__ */ jsx2(
522
+ }) => /* @__PURE__ */ jsx3(EnsureNimbusProvider, { locale, children: /* @__PURE__ */ jsx3(EnsureIntlProvider, { children: /* @__PURE__ */ jsx3(
405
523
  PuckApiProvider,
406
524
  {
407
525
  baseURL,
408
526
  projectKey,
409
527
  businessUnitKey,
410
528
  jwtToken,
411
- children: /* @__PURE__ */ jsx2(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx2(PageManagerInner, { config, backButton }) })
529
+ locale,
530
+ children: /* @__PURE__ */ jsx3(BrowserRouter, { basename: parentUrl, children: /* @__PURE__ */ jsx3(PageManagerInner, { config, backButton }) })
412
531
  }
413
- ) });
532
+ ) }) });
414
533
  export {
415
534
  PageManager
416
535
  };