@cardinali-studio/adminkit 2.2.20 → 2.2.21

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/app/App.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import "../theme.css";
2
2
  import "./app.css";
3
+ import "./next.css";
3
4
  export declare function App(): import("react").JSX.Element;
package/dist/app/App.js CHANGED
@@ -19,6 +19,7 @@ import { useEffect, useMemo, useState } from "react";
19
19
  * i default del kit, poi l'identità di questo sito che li sovrascrive, poi l'impaginazione. */
20
20
  import "../theme.css";
21
21
  import "./app.css";
22
+ import "./next.css";
22
23
  import { MediaFolders, MediaGrid, ProsePages, Trash, useMediaLibrary, usePicker } from "../form/index.js";
23
24
  import { Preview, isLocalPreview } from "../preview.js";
24
25
  import { contentPersistenceMode, site } from "./site.js";
@@ -31,7 +32,8 @@ import { Overview } from "./routes/Overview";
31
32
  import { PagesList } from "./routes/PagesList";
32
33
  import { PageEditor, PageHeader } from "./routes/PageEditor";
33
34
  import { PageSeoEditor } from "./routes/PageSeoEditor";
34
- import { SiteSettingsEditor, WebsiteSeoSettingsEditor } from "./routes/SiteSettingsEditor";
35
+ import { SiteSettingsEditor, WebsiteSeoSettingsEditor, } from "./routes/SiteSettingsEditor";
36
+ import { SiteSettingsUnavailable } from "./routes/AdminInterfaceSettings";
35
37
  import { MediaSettingsEditor } from "./routes/MediaSettingsEditor";
36
38
  import { HomeEditor } from "./routes/HomeEditor";
37
39
  import { RequestsList, RequestDetail } from "./routes/Requests";
@@ -46,7 +48,16 @@ import { NewPageButton } from "./routes/PageActions";
46
48
  import { BookingAgenda, BookingDetail, BookingsList, BookingSettingsHub, } from "./routes/Bookings";
47
49
  import { Icon } from "./ui/icons";
48
50
  import { useTheme } from "./lib/theme";
51
+ import { useAdminInterface } from "./lib/admin-interface";
52
+ import { NextAdminApp } from "./NextAdminApp";
49
53
  export function App() {
54
+ const { adminInterface, setAdminInterface } = useAdminInterface();
55
+ const current = (_jsx(CurrentAdminApp, { adminInterface: adminInterface, setAdminInterface: setAdminInterface }));
56
+ return adminInterface === "next"
57
+ ? _jsx(NextAdminApp, { current: current, adminInterface: adminInterface, setAdminInterface: setAdminInterface })
58
+ : current;
59
+ }
60
+ function CurrentAdminApp({ adminInterface, setAdminInterface, }) {
50
61
  const [route, go] = useHashRoute();
51
62
  const { theme, toggle: toggleTheme } = useTheme();
52
63
  /* Si entra dalla Panoramica, non dalle Pagine: aprendo il pannello la domanda è quasi sempre
@@ -176,7 +187,7 @@ export function App() {
176
187
  * Il badge invece sparisce: un contatore su una funzione spenta conterebbe un elenco che il
177
188
  * server non serve nemmeno (le sue rotte rispondono 404). */
178
189
  { title: "Attività", items: [
179
- { id: "deliveries", label: "Consegne", icon: Icon.delivery, badge: moduleOn("delivery") ? deliveries.expiring : 0 },
190
+ { id: "deliveries", label: "Transfer", icon: Icon.delivery, badge: moduleOn("delivery") ? deliveries.expiring : 0 },
180
191
  { id: "bookings", label: "Prenotazioni", icon: Icon.calendar, badge: moduleOn("booking") ? bookings.pending : 0 },
181
192
  { id: "requests", label: "Richieste", icon: Icon.mail, badge: moduleOn("requests") ? requests.pending : 0 },
182
193
  ...(local ? [{ id: "preview", label: "Anteprima", icon: Icon.play }] : []),
@@ -221,7 +232,7 @@ export function App() {
221
232
  /* Consegne non ripete il titolo qui: il nome del transfer sta nella sua schermata, dove
222
233
  si modifica, e si aggiorna mentre lo si scrive. Averlo in due posti significa averne
223
234
  uno dei due sempre vecchio. */
224
- : section === "deliveries" ? (deliverySettingsOpen ? "Impostazioni" : "Consegne")
235
+ : section === "deliveries" ? (deliverySettingsOpen ? "Impostazioni" : "Transfer")
225
236
  : section === "bookings" ? (bookingSettingsOpen ? "Impostazioni prenotazioni" : openBooking ? openBooking.visitorName || openBooking.referenceCode : "Prenotazioni")
226
237
  : section === "requests" ? (openReq ? openReq.name : "Richieste")
227
238
  : section === "settings" ? (settingsView === "seo" ? "SEO" : "Sito")
@@ -237,7 +248,17 @@ export function App() {
237
248
  const p = all.slice(0, i + 1).join("/") + "/";
238
249
  return { prefix: p, label: media.labelOf(p) };
239
250
  });
240
- return (_jsx(ProsePages.Provider, { value: pageLinks, children: _jsxs("div", { className: "app" + (hasList(section) && !offModule ? "" : " is-two"), children: [_jsxs("nav", { className: "rail", children: [_jsxs("div", { className: "rail-brand", children: [_jsx("span", { className: "rail-brand-name", children: site().name }), _jsx("span", { className: "rail-brand-role", children: "pannello" })] }), _jsx("div", { className: "rail-nav", children: navGroups.map((g) => (_jsxs("div", { className: "rail-group", children: [g.title && _jsx("span", { className: "rail-group-title", children: g.title }), g.items.map((n) => (_jsxs("button", { className: "rail-item" + (section === n.id ? " is-on" : ""), "aria-label": n.label, disabled: section === "media" && media.mutating, onClick: () => go(`/${n.id}`), children: [_jsx(n.icon, {}), _jsx("span", { children: n.label }), "badge" in n && n.badge ? _jsx("span", { className: "rail-badge", children: n.badge }) : null] }, n.id)))] }, g.title ?? "top"))) }), _jsxs("div", { className: "rail-foot", children: [_jsxs("a", { className: "rail-item", href: "/", target: "_blank", rel: "noreferrer", "aria-label": "Vedi il sito", children: [_jsx(Icon.out, {}), _jsx("span", { children: "Vedi il sito" })] }), _jsxs("button", { className: "rail-item", onClick: toggleTheme, "aria-label": theme === "dark" ? "Tema chiaro" : "Tema scuro", children: [theme === "dark" ? _jsx(Icon.sun, {}) : _jsx(Icon.moon, {}), _jsx("span", { children: theme === "dark" ? "Tema chiaro" : "Tema scuro" })] }), _jsxs("button", { className: "rail-item" + (section === "settings" ? " is-on" : ""), "aria-label": "Impostazioni", disabled: section === "media" && media.mutating, onClick: () => go("/settings/site"), children: [_jsx(Icon.settings, {}), _jsx("span", { children: "Impostazioni" })] })] })] }), hasList(section) && !offModule && (_jsxs("div", { className: "col", children: [section === "pages" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: list && _jsx(NewPageButton, { pages: list, onCreate: (p) => pages.update([...list, p]) }) }), _jsx("div", { className: "col-body", children: pages.error ? _jsx("p", { className: "ak-error", children: pages.error })
251
+ /* La testata non ripete il contesto già interamente leggibile nella colonna sinistra.
252
+ * Resta invece quando porta informazione ulteriore: controlli della pagina, antenati di una
253
+ * cartella, oppure il nome di un elemento che nell'elenco può essere troncato. */
254
+ const redundantPaneContext = (section === "pages" && !editing)
255
+ || section === "home"
256
+ || section === "deliveries"
257
+ || section === "settings"
258
+ || (section === "requests" && !openReq)
259
+ || (section === "bookings" && !openBooking)
260
+ || (section === "media" && (view === "trash" || mediaSettingsOpen || mediaCrumbs.length === 0));
261
+ return (_jsx(ProsePages.Provider, { value: pageLinks, children: _jsxs("div", { className: "app" + (hasList(section) && !offModule ? "" : " is-two"), children: [_jsxs("nav", { className: "rail", children: [_jsxs("div", { className: "rail-brand", children: [_jsx("span", { className: "rail-brand-name", children: site().name }), _jsx("span", { className: "rail-brand-role", children: "pannello" })] }), _jsx("div", { className: "rail-nav", children: navGroups.map((g) => (_jsxs("div", { className: "rail-group", children: [g.title && _jsx("span", { className: "rail-group-title", children: g.title }), g.items.map((n) => (_jsxs("button", { className: "rail-item" + (section === n.id ? " is-on" : ""), "aria-label": n.label, disabled: section === "media" && media.mutating, onClick: () => go(`/${n.id}`), children: [_jsx(n.icon, {}), _jsx("span", { children: n.label }), "badge" in n && n.badge ? _jsx("span", { className: "rail-badge", children: n.badge }) : null] }, n.id)))] }, g.title ?? "top"))) }), _jsxs("div", { className: "rail-foot", children: [_jsxs("a", { className: "rail-item", href: "/", target: "_blank", rel: "noreferrer", "aria-label": "Vedi il sito", children: [_jsx(Icon.out, {}), _jsx("span", { children: "Vedi il sito" })] }), _jsxs("button", { className: "rail-item", onClick: toggleTheme, "aria-label": theme === "dark" ? "Tema chiaro" : "Tema scuro", children: [theme === "dark" ? _jsx(Icon.sun, {}) : _jsx(Icon.moon, {}), _jsx("span", { children: theme === "dark" ? "Tema chiaro" : "Tema scuro" })] }), _jsxs("button", { className: "rail-item" + (section === "settings" ? " is-on" : ""), "aria-label": "Impostazioni", disabled: section === "media" && media.mutating, onClick: () => go("/settings/site"), children: [_jsx(Icon.settings, {}), _jsx("span", { children: "Impostazioni" })] })] })] }), hasList(section) && !offModule && (_jsxs("div", { className: "col", children: [section === "pages" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "col-head col-head-actions", children: [_jsx("span", { className: "col-title", children: "Pagine" }), list && _jsx(NewPageButton, { pages: list, onCreate: (p) => pages.update([...list, p]) })] }), _jsx("div", { className: "col-body", children: pages.error ? _jsx("p", { className: "ak-error", children: pages.error })
241
262
  : !list ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
242
263
  : _jsx(PagesList, { pages: list, current: editing?.slug, onEdit: (slug) => go(`/pages/${slug}`), languages: languages, onChange: pages.update, onRenamed: (from, to) => {
243
264
  if (editing?.slug === from)
@@ -253,16 +274,16 @@ export function App() {
253
274
  else
254
275
  next.add(id);
255
276
  return next;
256
- }) }) })] })), section === "deliveries" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx(NewDeliveryButton, { onCreate: () => {
257
- setQuickSelectedId(undefined);
258
- setQuickKey((value) => value + 1);
259
- go("/deliveries");
260
- } }) }), _jsx("div", { className: "col-body", children: deliveries.error ? _jsx("p", { className: "ak-error", children: deliveries.error })
277
+ }) }) })] })), section === "deliveries" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "col-head col-head-actions", children: [_jsx("span", { className: "col-title", children: "Transfer" }), _jsx(NewDeliveryButton, { onCreate: () => {
278
+ setQuickSelectedId(undefined);
279
+ setQuickKey((value) => value + 1);
280
+ go("/deliveries");
281
+ } })] }), _jsx("div", { className: "col-body", children: deliveries.error ? _jsx("p", { className: "ak-error", children: deliveries.error })
261
282
  : !deliveries.rows ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
262
283
  : deliveries.rows.length === 0 ? _jsx("p", { className: "ak-hint", children: "Nessun transfer, per ora." })
263
284
  : _jsx(DeliveriesList, { rows: deliveries.rows, current: activeDeliveryId || quickSelectedId, onOpen: (id) => { setQuickSelectedId(undefined); go(`/deliveries/${id}`); } }) }), _jsxs("div", { className: "dl-col-foot", children: [deliveries.rows && _jsx(DeliveriesTotal, { rows: deliveries.rows }), _jsxs("button", { className: "col-item" + (deliverySettingsOpen ? " is-on" : ""), onClick: () => { setQuickSelectedId(undefined); go("/deliveries/settings"); }, children: [_jsx(Icon.settings, {}), _jsx("span", { children: "Impostazioni" })] })] })] })), section === "bookings" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx("span", { className: "col-title", children: bookings.rows ? `${bookings.rows.length} appuntamenti` : "Prenotazioni" }) }), _jsx("div", { className: "col-body", children: bookings.error ? _jsx("p", { className: "ak-error", children: bookings.error })
264
285
  : !bookings.rows ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
265
- : _jsx(BookingsList, { rows: bookings.rows, current: bookingSettingsOpen ? "settings" : openBooking?.id, onOpen: (id) => go(`/bookings/${id}`), onAgenda: () => go("/bookings"), onSettings: () => go("/bookings/settings") }) })] })), section === "home" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx("span", { className: "col-title", children: "Sezioni" }) }), _jsx("div", { className: "col-body", children: _jsx("ul", { className: "col-list", children: homeGroups.map((g) => (_jsx("li", { children: _jsx("button", { className: "col-item" + (g === homeGroup ? " is-on" : ""), onClick: () => go(`/home/${groupSlug(g)}`), children: g || "Generale" }) }, g))) }) })] })), section === "settings" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx("span", { className: "col-title", children: "Impostazioni" }) }), _jsx("div", { className: "col-body", children: _jsxs("ul", { className: "col-list settings-list", children: [_jsx("li", { children: _jsxs("button", { className: "col-item settings-list-item" + (settingsView === "site" ? " is-on" : ""), onClick: () => go("/settings/site"), children: [_jsx("strong", { children: "Sito" }), _jsx("span", { children: "Favicon, identit\u00E0 e accesso generale." })] }) }), _jsx("li", { children: _jsxs("button", { className: "col-item settings-list-item" + (settingsView === "seo" ? " is-on" : ""), onClick: () => go("/settings/seo"), children: [_jsx("strong", { children: "SEO" }), _jsx("span", { children: "Motori di ricerca e crawler AI." })] }) })] }) })] }))] })), _jsxs("main", { className: "pane", children: [_jsxs("header", { className: "pane-head", children: [editing ? (_jsx(PageHeader, { page: editing, pages: list ?? [], onChange: replace, seoActive: seoEditing, onSeoToggle: () => go(`/pages/${editing.slug}${seoEditing ? "" : "/seo"}`), languages: languages, activeLanguage: pageLanguage, onLanguageChange: setPageLanguage })) : view === "media" && !mediaSettingsOpen ? (_jsxs("nav", { className: "pane-crumbs", "aria-label": "Percorso", children: [_jsx("button", { className: "pane-crumb" + (mediaCrumbs.length ? "" : " is-current"), disabled: media.mutating, onClick: () => media.go(""), children: "Senza cartella" }), mediaCrumbs.map((c, i) => (_jsxs("span", { className: "pane-crumb-part", children: [_jsx("span", { className: "pane-crumb-sep", "aria-hidden": "true", children: "\u203A" }), _jsx("button", { className: "pane-crumb" + (i === mediaCrumbs.length - 1 ? " is-current" : ""), disabled: media.mutating, onClick: () => media.go(c.prefix), children: c.label })] }, c.prefix)))] })) : (_jsx("h1", { className: "pane-title", children: heading })), view === "media" && !mediaSettingsOpen && _jsx("span", { className: "pane-count", children: media.shown.length }), _jsx("span", { className: "pane-spacer" }), view === "media" && !mediaSettingsOpen && (_jsx("button", { className: "ak-btn ak-btn-primary", disabled: media.busy || media.mutating, onClick: () => media.openUpload(), children: "Carica immagini" })), saver?.notice && (_jsx("button", { className: "pane-notice", onClick: () => saver.setNotice(null), children: saver.notice })), section === "bookings" && bookings.notice && (_jsx("button", { className: "pane-notice", onClick: bookings.clearNotice, children: bookings.notice })), publish.notice && (_jsx("button", { className: "pane-notice", onClick: publish.clearNotice, children: publish.notice })), !publish.configured && publish.error && (_jsx("span", { className: "pane-notice", title: publish.error, children: publish.error })), saver && (_jsxs(_Fragment, { children: [saver.dirty && _jsx("span", { className: "pane-dot", title: "modifiche non salvate" }), _jsx("button", { className: "ak-btn", disabled: !saver.dirty || saver.saving,
286
+ : _jsx(BookingsList, { rows: bookings.rows, current: bookingSettingsOpen ? "settings" : openBooking?.id, onOpen: (id) => go(`/bookings/${id}`), onAgenda: () => go("/bookings"), onSettings: () => go("/bookings/settings") }) })] })), section === "home" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx("span", { className: "col-title", children: "Sezioni" }) }), _jsx("div", { className: "col-body", children: _jsx("ul", { className: "col-list", children: homeGroups.map((g) => (_jsx("li", { children: _jsx("button", { className: "col-item" + (g === homeGroup ? " is-on" : ""), onClick: () => go(`/home/${groupSlug(g)}`), children: g || "Generale" }) }, g))) }) })] })), section === "settings" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx("span", { className: "col-title", children: "Impostazioni" }) }), _jsx("div", { className: "col-body", children: _jsxs("ul", { className: "col-list settings-list", children: [_jsx("li", { children: _jsxs("button", { className: "col-item settings-list-item" + (settingsView === "site" ? " is-on" : ""), onClick: () => go("/settings/site"), children: [_jsx("strong", { children: "Sito" }), _jsx("span", { children: "Favicon, identit\u00E0 e accesso generale." })] }) }), _jsx("li", { children: _jsxs("button", { className: "col-item settings-list-item" + (settingsView === "seo" ? " is-on" : ""), onClick: () => go("/settings/seo"), children: [_jsx("strong", { children: "SEO" }), _jsx("span", { children: "Motori di ricerca e crawler AI." })] }) })] }) })] }))] })), _jsxs("main", { className: "pane", children: [_jsxs("header", { className: "pane-head", children: [editing ? (_jsx(PageHeader, { page: editing, pages: list ?? [], onChange: replace, seoActive: seoEditing, onSeoToggle: () => go(`/pages/${editing.slug}${seoEditing ? "" : "/seo"}`), languages: languages, activeLanguage: pageLanguage, onLanguageChange: setPageLanguage })) : view === "media" && !mediaSettingsOpen && mediaCrumbs.length > 0 ? (_jsxs("nav", { className: "pane-crumbs", "aria-label": "Percorso", children: [_jsx("button", { className: "pane-crumb" + (mediaCrumbs.length ? "" : " is-current"), disabled: media.mutating, onClick: () => media.go(""), children: "Senza cartella" }), mediaCrumbs.map((c, i) => (_jsxs("span", { className: "pane-crumb-part", children: [_jsx("span", { className: "pane-crumb-sep", "aria-hidden": "true", children: "\u203A" }), _jsx("button", { className: "pane-crumb" + (i === mediaCrumbs.length - 1 ? " is-current" : ""), disabled: media.mutating, onClick: () => media.go(c.prefix), children: c.label })] }, c.prefix)))] })) : !redundantPaneContext ? (_jsx("h1", { className: "pane-title", children: heading })) : null, view === "media" && !mediaSettingsOpen && _jsx("span", { className: "pane-count", children: media.shown.length }), _jsx("span", { className: "pane-spacer" }), view === "media" && !mediaSettingsOpen && (_jsx("button", { className: "ak-btn ak-btn-primary", disabled: media.busy || media.mutating, onClick: () => media.openUpload(), children: "Carica immagini" })), saver?.notice && (_jsx("button", { className: "pane-notice", onClick: () => saver.setNotice(null), children: saver.notice })), section === "bookings" && bookings.notice && (_jsx("button", { className: "pane-notice", onClick: bookings.clearNotice, children: bookings.notice })), publish.notice && (_jsx("button", { className: "pane-notice", onClick: publish.clearNotice, children: publish.notice })), !publish.configured && publish.error && (_jsx("span", { className: "pane-notice", title: publish.error, children: publish.error })), saver && (_jsxs(_Fragment, { children: [saver.dirty && _jsx("span", { className: "pane-dot", title: "modifiche non salvate" }), _jsx("button", { className: "ak-btn", disabled: !saver.dirty || saver.saving,
266
287
  /* Dopo un salvataggio il conteggio delle modifiche in attesa è cambiato: senza
267
288
  rileggerlo, «Pubblica» comparirebbe solo al ricaricamento della pagina — cioè
268
289
  il pannello direbbe che non c'è niente da pubblicare subito dopo aver salvato
@@ -283,9 +304,9 @@ export function App() {
283
304
  se non ricaricando il pannello a mano. Le altre sezioni il bottone ce l'hanno. */
284
305
  home.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: home.error }), _jsx("button", { className: "ak-btn", onClick: () => void home.load(), children: "Riprova" })] }))
285
306
  : home.data ? _jsx(HomeEditorView, { data: home.data, group: homeGroup, onChange: home.change, pickMedia: pickMedia })
286
- : _jsx("p", { className: "ak-hint", children: "Carico\u2026" })), section === "settings" && (settings.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: settings.error }), _jsx("button", { className: "ak-btn", onClick: () => void settings.load(), children: "Riprova" })] })) : settings.data ? (settingsView === "seo"
307
+ : _jsx("p", { className: "ak-hint", children: "Carico\u2026" })), section === "settings" && (settings.error ? (settingsView === "site" ? (_jsx(SiteSettingsUnavailable, { error: settings.error, onRetry: () => void settings.load(), adminInterface: adminInterface, onAdminInterfaceChange: setAdminInterface })) : (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: settings.error }), _jsx("button", { className: "ak-btn", onClick: () => void settings.load(), children: "Riprova" })] }))) : settings.data ? (settingsView === "seo"
287
308
  ? _jsx(WebsiteSeoSettingsEditor, { value: settings.data, onChange: settings.change })
288
- : _jsx(SiteSettingsEditor, { value: settings.data, onChange: settings.change, pickMediaFiles: pickMediaFiles, focus: route[1], passwordDraftPending: settings.accessDraftPending, onDiscardPasswordDraft: () => void settings.discardSiteAccess().then(() => publish.reload()) })) : _jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })), view === "media" && (mediaSettingsOpen
309
+ : _jsx(SiteSettingsEditor, { value: settings.data, onChange: settings.change, pickMediaFiles: pickMediaFiles, adminInterface: adminInterface, onAdminInterfaceChange: setAdminInterface, focus: route[1], passwordDraftPending: settings.accessDraftPending, onDiscardPasswordDraft: () => void settings.discardSiteAccess().then(() => publish.reload()) })) : _jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })), view === "media" && (mediaSettingsOpen
289
310
  ? _jsx(MediaSettingsEditor, { lib: media })
290
311
  : _jsx(MediaGrid, { lib: media })), view === "trash" && _jsx(Trash, {}), section === "requests" && (requests.error ? _jsx("p", { className: "ak-error", children: requests.error })
291
312
  : openReq ? (_jsx(RequestDetail, { row: openReq, onMark: (h) => void requests.mark([openReq.id], h), onRemove: () => { void requests.remove([openReq.id]); go("/requests"); } }))
@@ -317,10 +338,10 @@ function hasList(section) {
317
338
  const OPTIONAL_SECTIONS = {
318
339
  deliveries: {
319
340
  module: "delivery",
320
- title: "Consegne",
341
+ title: "Transfer",
321
342
  lead: "Manda le fotografie a un cliente con un link privato, senza allegati e senza servizi di terze parti.",
322
343
  points: [
323
- "Un indirizzo riservato per ogni consegna, che scade da solo",
344
+ "Un indirizzo riservato per ogni Transfer, che scade da solo",
324
345
  "Il cliente scarica tutto in una volta, o solo le foto che gli servono",
325
346
  "Vedi quante volte è stato scaricato, e da quando",
326
347
  ],
@@ -341,7 +362,7 @@ const OPTIONAL_SECTIONS = {
341
362
  lead: "Le richieste che arrivano dal modulo di contatto del sito, raccolte qui invece che in una casella di posta.",
342
363
  points: [
343
364
  "Nessuna richiesta persa fra le altre email",
344
- "Segni quali hai già gestito",
365
+ "Segni quali hai già letto",
345
366
  "Rispondi con un click, dall'indirizzo di sempre",
346
367
  ],
347
368
  },
@@ -0,0 +1,8 @@
1
+ import type { Dispatch, ReactNode, SetStateAction } from "react";
2
+ import type { AdminInterface } from "./lib/admin-interface.js";
3
+ export interface NextAdminAppProps {
4
+ current: ReactNode;
5
+ adminInterface: AdminInterface;
6
+ setAdminInterface: Dispatch<SetStateAction<AdminInterface>>;
7
+ }
8
+ export declare function NextAdminApp({ current }: NextAdminAppProps): ReactNode;
@@ -0,0 +1,3 @@
1
+ export function NextAdminApp({ current }) {
2
+ return current;
3
+ }
@@ -0,0 +1,11 @@
1
+ export type AdminInterface = "current" | "next";
2
+ type StorageReader = Pick<Storage, "getItem">;
3
+ type StorageWriter = Pick<Storage, "setItem">;
4
+ export declare function parseAdminInterface(value: string | null): AdminInterface;
5
+ export declare function readAdminInterface(storage?: StorageReader): AdminInterface;
6
+ export declare function saveAdminInterface(value: AdminInterface, storage?: StorageWriter): void;
7
+ export declare function useAdminInterface(): {
8
+ adminInterface: AdminInterface;
9
+ setAdminInterface: import("react").Dispatch<import("react").SetStateAction<AdminInterface>>;
10
+ };
11
+ export {};
@@ -0,0 +1,45 @@
1
+ /* La nuova interfaccia nasce dietro una preferenza LOCALE al browser.
2
+ *
3
+ * Non è un'impostazione editoriale del sito: salvarla in D1 o in data/site.json la farebbe
4
+ * passare dalla pubblicazione e la accenderebbe anche agli altri amministratori. Qui invece
5
+ * il sito pubblico non riceve nulla. La nuova esperienza è il default, ma ogni browser può
6
+ * tornare all'interfaccia precedente senza cambiare ciò che vedono gli altri amministratori.
7
+ */
8
+ import { useEffect, useState } from "react";
9
+ import { storageKey } from "../../index.js";
10
+ export function parseAdminInterface(value) {
11
+ return value === "current" ? "current" : "next";
12
+ }
13
+ /* La chiave si calcola al momento della lettura, dopo che mountAdmin ha impostato il prefisso
14
+ * del sito. Calcolarla a livello di modulo la bloccherebbe sul prefisso neutro `adminkit`. */
15
+ export function readAdminInterface(storage) {
16
+ try {
17
+ const target = storage ?? (typeof localStorage !== "undefined" ? localStorage : undefined);
18
+ return parseAdminInterface(target?.getItem(storageKey("adminInterface")) ?? null);
19
+ }
20
+ catch {
21
+ return "next";
22
+ }
23
+ }
24
+ export function saveAdminInterface(value, storage) {
25
+ try {
26
+ const target = storage ?? (typeof localStorage !== "undefined" ? localStorage : undefined);
27
+ target?.setItem(storageKey("adminInterface"), value);
28
+ }
29
+ catch {
30
+ /* Se localStorage non è disponibile, la scelta resta valida per la sessione corrente. */
31
+ }
32
+ }
33
+ function applyAdminInterface(value) {
34
+ if (typeof document !== "undefined") {
35
+ document.documentElement.setAttribute("data-admin-interface", value);
36
+ }
37
+ }
38
+ export function useAdminInterface() {
39
+ const [adminInterface, setAdminInterface] = useState(readAdminInterface);
40
+ useEffect(() => {
41
+ applyAdminInterface(adminInterface);
42
+ saveAdminInterface(adminInterface);
43
+ }, [adminInterface]);
44
+ return { adminInterface, setAdminInterface };
45
+ }