@cardinali-studio/adminkit 1.0.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/api.d.ts +195 -0
- package/dist/api.js +118 -0
- package/dist/app/App.d.ts +3 -0
- package/dist/app/App.js +320 -0
- package/dist/app/app.css +2068 -0
- package/dist/app/index.d.ts +18 -0
- package/dist/app/index.js +33 -0
- package/dist/app/lib/bookings.d.ts +132 -0
- package/dist/app/lib/bookings.js +79 -0
- package/dist/app/lib/deliveries.d.ts +95 -0
- package/dist/app/lib/deliveries.js +104 -0
- package/dist/app/lib/delivery-drop.d.ts +1 -0
- package/dist/app/lib/delivery-drop.js +72 -0
- package/dist/app/lib/delivery-upload.d.ts +78 -0
- package/dist/app/lib/delivery-upload.js +585 -0
- package/dist/app/lib/groups.d.ts +9 -0
- package/dist/app/lib/groups.js +32 -0
- package/dist/app/lib/home.d.ts +11 -0
- package/dist/app/lib/home.js +67 -0
- package/dist/app/lib/overview.d.ts +82 -0
- package/dist/app/lib/overview.js +68 -0
- package/dist/app/lib/pages.d.ts +24 -0
- package/dist/app/lib/pages.js +156 -0
- package/dist/app/lib/publish.d.ts +18 -0
- package/dist/app/lib/publish.js +63 -0
- package/dist/app/lib/reorder.d.ts +3 -0
- package/dist/app/lib/reorder.js +31 -0
- package/dist/app/lib/requests.d.ts +10 -0
- package/dist/app/lib/requests.js +67 -0
- package/dist/app/lib/router.d.ts +1 -0
- package/dist/app/lib/router.js +14 -0
- package/dist/app/lib/settings.d.ts +29 -0
- package/dist/app/lib/settings.js +82 -0
- package/dist/app/lib/slug.d.ts +3 -0
- package/dist/app/lib/slug.js +23 -0
- package/dist/app/lib/text.d.ts +1 -0
- package/dist/app/lib/text.js +13 -0
- package/dist/app/lib/theme.d.ts +7 -0
- package/dist/app/lib/theme.js +43 -0
- package/dist/app/lib/upload-queue.d.ts +35 -0
- package/dist/app/lib/upload-queue.js +167 -0
- package/dist/app/routes/AboutEditor.d.ts +15 -0
- package/dist/app/routes/AboutEditor.js +122 -0
- package/dist/app/routes/Bookings.d.ts +21 -0
- package/dist/app/routes/Bookings.js +134 -0
- package/dist/app/routes/Deliveries.d.ts +37 -0
- package/dist/app/routes/Deliveries.js +563 -0
- package/dist/app/routes/GallerySectionsEditor.d.ts +12 -0
- package/dist/app/routes/GallerySectionsEditor.js +107 -0
- package/dist/app/routes/HomeEditor.d.ts +10 -0
- package/dist/app/routes/HomeEditor.js +188 -0
- package/dist/app/routes/Overview.d.ts +17 -0
- package/dist/app/routes/Overview.js +318 -0
- package/dist/app/routes/PageActions.d.ts +12 -0
- package/dist/app/routes/PageActions.js +93 -0
- package/dist/app/routes/PageEditor.d.ts +23 -0
- package/dist/app/routes/PageEditor.js +233 -0
- package/dist/app/routes/PageSeoEditor.d.ts +13 -0
- package/dist/app/routes/PageSeoEditor.js +102 -0
- package/dist/app/routes/PagesList.d.ts +10 -0
- package/dist/app/routes/PagesList.js +76 -0
- package/dist/app/routes/PortfolioWorksEditor.d.ts +12 -0
- package/dist/app/routes/PortfolioWorksEditor.js +139 -0
- package/dist/app/routes/Requests.d.ts +13 -0
- package/dist/app/routes/Requests.js +30 -0
- package/dist/app/routes/SiteSettingsEditor.d.ts +21 -0
- package/dist/app/routes/SiteSettingsEditor.js +66 -0
- package/dist/app/routes/UploadDock.d.ts +1 -0
- package/dist/app/routes/UploadDock.js +66 -0
- package/dist/app/routes/WorkshopEditionsEditor.d.ts +12 -0
- package/dist/app/routes/WorkshopEditionsEditor.js +101 -0
- package/dist/app/site.d.ts +37 -0
- package/dist/app/site.js +18 -0
- package/dist/app/ui/MediaSlot.d.ts +10 -0
- package/dist/app/ui/MediaSlot.js +20 -0
- package/dist/app/ui/OpenCard.d.ts +18 -0
- package/dist/app/ui/OpenCard.js +20 -0
- package/dist/app/ui/ParagraphEditor.d.ts +8 -0
- package/dist/app/ui/ParagraphEditor.js +37 -0
- package/dist/app/ui/dnd.d.ts +1 -0
- package/dist/app/ui/dnd.js +11 -0
- package/dist/app/ui/icons.d.ts +26 -0
- package/dist/app/ui/icons.js +45 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +26 -0
- package/dist/form/MediaBrowser.d.ts +13 -0
- package/dist/form/MediaBrowser.js +24 -0
- package/dist/form/MediaPicker.d.ts +10 -0
- package/dist/form/MediaPicker.js +20 -0
- package/dist/form/SchemaForm.d.ts +21 -0
- package/dist/form/SchemaForm.js +67 -0
- package/dist/form/Trash.d.ts +1 -0
- package/dist/form/Trash.js +42 -0
- package/dist/form/fields/index.d.ts +1 -0
- package/dist/form/fields/index.js +110 -0
- package/dist/form/fields/objectList.d.ts +1 -0
- package/dist/form/fields/objectList.js +99 -0
- package/dist/form/index.d.ts +8 -0
- package/dist/form/index.js +8 -0
- package/dist/form/media/Lightbox.d.ts +4 -0
- package/dist/form/media/Lightbox.js +70 -0
- package/dist/form/media/MediaFolders.d.ts +20 -0
- package/dist/form/media/MediaFolders.js +116 -0
- package/dist/form/media/MediaGrid.d.ts +8 -0
- package/dist/form/media/MediaGrid.js +139 -0
- package/dist/form/media/UploadDialog.d.ts +4 -0
- package/dist/form/media/UploadDialog.js +37 -0
- package/dist/form/media/useMediaLibrary.d.ts +55 -0
- package/dist/form/media/useMediaLibrary.js +495 -0
- package/dist/form/prose/ProseBar.d.ts +21 -0
- package/dist/form/prose/ProseBar.js +111 -0
- package/dist/form/prose/ProseField.d.ts +11 -0
- package/dist/form/prose/ProseField.js +152 -0
- package/dist/form/prose/pages.d.ts +6 -0
- package/dist/form/prose/pages.js +13 -0
- package/dist/form/registry.d.ts +20 -0
- package/dist/form/registry.js +15 -0
- package/dist/frontmatter.d.ts +20 -0
- package/dist/frontmatter.js +202 -0
- package/dist/functions/_middleware.js +101 -0
- package/dist/functions/_shared/analytics-ga4.js +248 -0
- package/dist/functions/_shared/analytics-umami.js +119 -0
- package/dist/functions/_shared/assets.js +90 -0
- package/dist/functions/_shared/b2.js +300 -0
- package/dist/functions/_shared/delivery-crypto.js +99 -0
- package/dist/functions/_shared/delivery-purge.js +222 -0
- package/dist/functions/_shared/edge-cache.js +37 -0
- package/dist/functions/_shared/exif.js +53 -0
- package/dist/functions/_shared/github.js +138 -0
- package/dist/functions/_shared/imagesize.js +101 -0
- package/dist/functions/_shared/media-content.js +200 -0
- package/dist/functions/_shared/pending-media.js +98 -0
- package/dist/functions/_shared/refs.js +155 -0
- package/dist/functions/api/analytics.js +106 -0
- package/dist/functions/api/assets/delete.js +241 -0
- package/dist/functions/api/assets/folder.js +43 -0
- package/dist/functions/api/assets/folders.js +32 -0
- package/dist/functions/api/assets/move.js +253 -0
- package/dist/functions/api/assets/preview.js +17 -0
- package/dist/functions/api/assets/trash/delete.js +13 -0
- package/dist/functions/api/assets/trash/empty.js +16 -0
- package/dist/functions/api/assets/trash/restore.js +43 -0
- package/dist/functions/api/assets/trash.js +28 -0
- package/dist/functions/api/assets/upload.js +54 -0
- package/dist/functions/api/assets/usage.js +41 -0
- package/dist/functions/api/assets.js +94 -0
- package/dist/functions/api/deliveries.js +629 -0
- package/dist/functions/api/delivery/hero.js +21 -0
- package/dist/functions/api/delivery/image.js +35 -0
- package/dist/functions/api/delivery/multipart.js +261 -0
- package/dist/functions/api/pages/get.js +26 -0
- package/dist/functions/api/pages.js +354 -0
- package/dist/functions/api/publish.js +134 -0
- package/dist/functions/api/requests.js +57 -0
- package/dist/functions/api/settings.js +236 -0
- package/dist/functions/api/singleton.js +105 -0
- package/dist/functions/api/storage.js +125 -0
- package/dist/functions/index.js +124 -0
- package/dist/functions/site.js +38 -0
- package/dist/gallery.d.ts +28 -0
- package/dist/gallery.js +18 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +47 -0
- package/dist/normalize.d.ts +50 -0
- package/dist/normalize.js +120 -0
- package/dist/preview.d.ts +3 -0
- package/dist/preview.js +82 -0
- package/dist/prose.d.ts +17 -0
- package/dist/prose.js +205 -0
- package/dist/registries.d.ts +43 -0
- package/dist/registries.js +29 -0
- package/dist/serialize.d.ts +26 -0
- package/dist/serialize.js +136 -0
- package/dist/testing.d.ts +26 -0
- package/dist/testing.js +66 -0
- package/dist/theme.css +636 -0
- package/dist/yaml.d.ts +8 -0
- package/dist/yaml.js +90 -0
- package/package.json +97 -0
package/dist/app/App.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/* Il guscio del pannello: tre colonne fisse.
|
|
3
|
+
*
|
|
4
|
+
* 1. dove sono la navigazione principale — non cambia mai
|
|
5
|
+
* 2. cosa c'è dentro l'elenco della sezione: le pagine, le sezioni della home, i media
|
|
6
|
+
* 3. cosa sto facendo la modifica di UNA cosa sola
|
|
7
|
+
*
|
|
8
|
+
* Il motivo per cui non è più una barra di schede: con le schede, aprire una pagina sostituiva
|
|
9
|
+
* tutto lo schermo e l'elenco spariva. Qui l'elenco resta sotto gli occhi mentre si modifica, e
|
|
10
|
+
* passare da una pagina all'altra è un click invece di due — che è tutto il senso della seconda
|
|
11
|
+
* colonna, non un vezzo grafico.
|
|
12
|
+
*
|
|
13
|
+
* Il salvataggio sta in ALTO A DESTRA, uno solo, e sa a chi parlare: pagine e home hanno endpoint
|
|
14
|
+
* separati (una collezione e un singleton), ma da qui dentro sono lo stesso gesto e non c'è
|
|
15
|
+
* ragione di farli sembrare due funzioni diverse.
|
|
16
|
+
*/
|
|
17
|
+
import { useEffect, useMemo, useState } from "react";
|
|
18
|
+
/* L'ordine di questi tre import È la separazione motore/cliente, e non va scambiato:
|
|
19
|
+
* i default del kit, poi l'identità di questo sito che li sovrascrive, poi l'impaginazione. */
|
|
20
|
+
import "../theme.css";
|
|
21
|
+
import "./app.css";
|
|
22
|
+
import { MediaFolders, MediaGrid, ProsePages, Trash, useMediaLibrary, usePicker } from "../form/index.js";
|
|
23
|
+
import { Preview, isLocalPreview } from "../preview.js";
|
|
24
|
+
import { site } from "./site.js";
|
|
25
|
+
import { useHashRoute } from "./lib/router";
|
|
26
|
+
import { usePages, legacyDraft, legacyDraftTitles, discardLegacyDraft } from "./lib/pages";
|
|
27
|
+
import { useHome } from "./lib/home";
|
|
28
|
+
import { useSiteSettings } from "./lib/settings";
|
|
29
|
+
import { groupsOf, groupSlug } from "./lib/groups";
|
|
30
|
+
import { Overview } from "./routes/Overview";
|
|
31
|
+
import { PagesList } from "./routes/PagesList";
|
|
32
|
+
import { PageEditor, PageHeader } from "./routes/PageEditor";
|
|
33
|
+
import { PageSeoEditor } from "./routes/PageSeoEditor";
|
|
34
|
+
import { SiteSettingsEditor, WebsiteSeoSettingsEditor } from "./routes/SiteSettingsEditor";
|
|
35
|
+
import { HomeEditor } from "./routes/HomeEditor";
|
|
36
|
+
import { RequestsList, RequestDetail } from "./routes/Requests";
|
|
37
|
+
import { useRequests } from "./lib/requests";
|
|
38
|
+
import { usePublish } from "./lib/publish";
|
|
39
|
+
import { useDeliveries } from "./lib/deliveries";
|
|
40
|
+
import { useBookings } from "./lib/bookings";
|
|
41
|
+
import { useUploadCompletions } from "./lib/upload-queue";
|
|
42
|
+
import { UploadDock } from "./routes/UploadDock";
|
|
43
|
+
import { DeliveriesList, DeliveriesTotal, DeliverySettings, DeliveryWorkspace, NewDeliveryButton, } from "./routes/Deliveries";
|
|
44
|
+
import { NewPageButton } from "./routes/PageActions";
|
|
45
|
+
import { BookingAgenda, BookingDetail, BookingsList, BookingSettingsHub, } from "./routes/Bookings";
|
|
46
|
+
import { Icon } from "./ui/icons";
|
|
47
|
+
import { useTheme } from "./lib/theme";
|
|
48
|
+
export function App() {
|
|
49
|
+
const [route, go] = useHashRoute();
|
|
50
|
+
const { theme, toggle: toggleTheme } = useTheme();
|
|
51
|
+
/* Si entra dalla Panoramica, non dalle Pagine: aprendo il pannello la domanda è quasi sempre
|
|
52
|
+
«è successo qualcosa?», e la risposta stava sparsa in tre sezioni da visitare una per una. */
|
|
53
|
+
const view = route[0] || "overview";
|
|
54
|
+
// Il cestino non è una voce di primo livello: è un ripostiglio dei media, e vive nella loro
|
|
55
|
+
// seconda colonna. Nella prima resta accesa "Media", altrimenti sembrerebbe di essere altrove.
|
|
56
|
+
const section = view === "trash" ? "media" : view;
|
|
57
|
+
// In produzione la voce non esiste: non nascosta col CSS, proprio non emessa.
|
|
58
|
+
const local = isLocalPreview();
|
|
59
|
+
const pages = usePages();
|
|
60
|
+
const home = useHome(section === "home");
|
|
61
|
+
/* Anche la Panoramica: le sue card devono sapere quali funzioni aggiuntive sono attive, e
|
|
62
|
+
lo stato è quello — non una seconda copia. È una lettura sola, e la Panoramica è la
|
|
63
|
+
schermata da cui si entra, quindi arriva prima che serva. */
|
|
64
|
+
const settings = useSiteSettings(section === "settings" || section === "overview" || (section === "pages" && route[2] === "seo"));
|
|
65
|
+
/* La libreria media vive QUI, non dentro un componente: le cartelle stanno nella seconda
|
|
66
|
+
colonna e la griglia nella terza, e sono due punti dello schermo lontani. Uno stato per
|
|
67
|
+
ciascuno vorrebbe dire due idee diverse di quale cartella si sta guardando. */
|
|
68
|
+
const media = useMediaLibrary({ mode: "manage", active: section === "media" });
|
|
69
|
+
/* Le richieste si caricano SUBITO, non entrando nella sezione: il numero «da gestire» nella
|
|
70
|
+
prima colonna è il motivo per cui uno ci entra, e un badge che compare solo dopo essere
|
|
71
|
+
andati a guardare non serve a niente. */
|
|
72
|
+
const requests = useRequests();
|
|
73
|
+
const publish = usePublish();
|
|
74
|
+
const deliverySettingsOpen = section === "deliveries" && route[1] === "settings";
|
|
75
|
+
const activeDeliveryId = section === "deliveries" && !deliverySettingsOpen ? route[1] : undefined;
|
|
76
|
+
/* Come Richieste, Consegne carica subito l'elenco: il badge "in scadenza" deve
|
|
77
|
+
esistere prima di entrare nella sezione. Il dettaglio resta pigro. */
|
|
78
|
+
const deliveries = useDeliveries(activeDeliveryId);
|
|
79
|
+
const bookings = useBookings();
|
|
80
|
+
/* Un caricamento che finisce cambia l'elenco (dimensione, stato, «in scadenza»), e può
|
|
81
|
+
finire mentre si sta guardando tutt'altro: qui ci si abbona SOLO al fatto che ne sia
|
|
82
|
+
finito uno, non all'avanzamento — altrimenti il pannello si ridisegnerebbe due volte al
|
|
83
|
+
secondo per tutta la durata dell'upload. */
|
|
84
|
+
const uploadCompletions = useUploadCompletions();
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (section === "deliveries")
|
|
87
|
+
void deliveries.load();
|
|
88
|
+
}, [uploadCompletions]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
89
|
+
const [quickKey, setQuickKey] = useState(0);
|
|
90
|
+
const [quickSelectedId, setQuickSelectedId] = useState();
|
|
91
|
+
const [pickedReq, setPickedReq] = useState(new Set());
|
|
92
|
+
const { pickMedia, pickMediaFiles, pickerElement } = usePicker();
|
|
93
|
+
// Stato e non solo lettura: l'avviso deve poter sparire, o resta lì per sempre.
|
|
94
|
+
const [draft, setDraft] = useState(legacyDraft);
|
|
95
|
+
const [draftOpen, setDraftOpen] = useState(false);
|
|
96
|
+
/* Un file trascinato per sbaglio FUORI da una zona di rilascio fa navigare il browser su
|
|
97
|
+
quel file: si esce dal pannello e si perdono le modifiche non salvate. Qui si annulla il
|
|
98
|
+
comportamento predefinito a livello di finestra — le zone vere hanno già gestito l'evento
|
|
99
|
+
prima, perché risale da loro. */
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const stop = (e) => e.preventDefault();
|
|
102
|
+
addEventListener("dragover", stop);
|
|
103
|
+
addEventListener("drop", stop);
|
|
104
|
+
return () => { removeEventListener("dragover", stop); removeEventListener("drop", stop); };
|
|
105
|
+
}, []);
|
|
106
|
+
const list = pages.pages;
|
|
107
|
+
const editing = section === "pages" && route[1] && list
|
|
108
|
+
? list.find((p) => p.slug === route[1])
|
|
109
|
+
: undefined;
|
|
110
|
+
const seoEditing = !!editing && route[2] === "seo";
|
|
111
|
+
/* Le destinazioni interne del dialogo «Collegamento», dalle pagine già caricate: la barra di
|
|
112
|
+
formattazione compare dentro widget annidati anche di tre livelli, e le riceve da un
|
|
113
|
+
contesto invece che da una catena di prop (vedi adminkit form/prose/pages.ts). Le pagine
|
|
114
|
+
offline restano fuori: un link a una pagina non pubblicata è un 404 con un altro nome. */
|
|
115
|
+
const pageLinks = useMemo(() => (list ?? [])
|
|
116
|
+
.filter((page) => page.published !== false)
|
|
117
|
+
.map((page) => ({
|
|
118
|
+
label: String(page.title ?? page.slug),
|
|
119
|
+
href: typeof page.url === "string" && page.url ? page.url : `/${page.slug}/`,
|
|
120
|
+
})), [list]);
|
|
121
|
+
const settingsView = section === "settings" && route[1] === "seo" ? "seo" : "site";
|
|
122
|
+
const replace = (next) => {
|
|
123
|
+
if (!list || !editing)
|
|
124
|
+
return;
|
|
125
|
+
const previousSlug = editing.slug;
|
|
126
|
+
pages.update(list.map((p) => (p.slug === previousSlug ? next : p)));
|
|
127
|
+
if (next.slug !== previousSlug)
|
|
128
|
+
go(`/pages/${next.slug}${seoEditing ? "/seo" : ""}`);
|
|
129
|
+
};
|
|
130
|
+
const openReq = section === "requests" && route[1] && requests.rows
|
|
131
|
+
? requests.rows.find((r) => String(r.id) === route[1])
|
|
132
|
+
: undefined;
|
|
133
|
+
const bookingSettingsOpen = section === "bookings" && route[1] === "settings";
|
|
134
|
+
const openBooking = section === "bookings" && route[1] && !bookingSettingsOpen && bookings.rows
|
|
135
|
+
? bookings.rows.find((row) => row.id === route[1])
|
|
136
|
+
: undefined;
|
|
137
|
+
// Le sezioni della home sono i `group` del suo schema: nessun elenco da tenere allineato.
|
|
138
|
+
const homeGroups = useMemo(() => groupsOf(site().home), []);
|
|
139
|
+
const homeGroup = homeGroups.find((g) => groupSlug(g) === route[1]) ?? homeGroups[0] ?? "";
|
|
140
|
+
/* Due gruppi, non di più: con sette-otto voci un terzo aggiungerebbe rumore invece di
|
|
141
|
+
* struttura. Panoramica resta isolata in cima — è il punto d'ingresso, non un contenuto fra
|
|
142
|
+
* gli altri — e Anteprima (solo in locale) chiude Attività invece di avere un gruppo suo. */
|
|
143
|
+
/* Un modulo è acceso? Gemella di `moduleEnabled` lato Worker, e deve rispondere allo stesso
|
|
144
|
+
* modo: se le due divergessero, il pannello mostrerebbe una funzione che gli endpoint negano
|
|
145
|
+
* (o la nasconderebbe mentre esiste). */
|
|
146
|
+
const moduleOn = (name) => site().modules?.[name] === true;
|
|
147
|
+
/* La sezione che si sta guardando appartiene a un modulo SPENTO? In quel caso al posto del
|
|
148
|
+
* contenuto va la sua presentazione. `null` quando la sezione non è di un modulo o è accesa. */
|
|
149
|
+
const offModule = OPTIONAL_SECTIONS[section] && !moduleOn(OPTIONAL_SECTIONS[section].module)
|
|
150
|
+
? OPTIONAL_SECTIONS[section]
|
|
151
|
+
: null;
|
|
152
|
+
const navGroups = [
|
|
153
|
+
{ title: null, items: [
|
|
154
|
+
{ id: "overview", label: "Panoramica", icon: Icon.grid },
|
|
155
|
+
] },
|
|
156
|
+
{ title: "Contenuti", items: [
|
|
157
|
+
{ id: "home", label: "Home", icon: Icon.home },
|
|
158
|
+
{ id: "pages", label: "Pagine", icon: Icon.pages },
|
|
159
|
+
{ id: "media", label: "Media", icon: Icon.media },
|
|
160
|
+
] },
|
|
161
|
+
/* Le voci di «Attività» sono MODULI, e ci sono ANCHE quando sono spenti.
|
|
162
|
+
*
|
|
163
|
+
* Nasconderle sarebbe la scelta ovvia e sarebbe sbagliata: un cliente non può chiedere una
|
|
164
|
+
* funzione che non sa di poter avere. Chi preme una voce spenta trova una schermata che
|
|
165
|
+
* spiega cosa fa e a chi scrivere per accenderla — il pannello è anche il posto in cui quelle
|
|
166
|
+
* funzioni si scoprono.
|
|
167
|
+
*
|
|
168
|
+
* Il badge invece sparisce: un contatore su una funzione spenta conterebbe un elenco che il
|
|
169
|
+
* server non serve nemmeno (le sue rotte rispondono 404). */
|
|
170
|
+
{ title: "Attività", items: [
|
|
171
|
+
{ id: "deliveries", label: "Consegne", icon: Icon.delivery, badge: moduleOn("delivery") ? deliveries.expiring : 0 },
|
|
172
|
+
{ id: "bookings", label: "Prenotazioni", icon: Icon.calendar, badge: moduleOn("booking") ? bookings.pending : 0 },
|
|
173
|
+
{ id: "requests", label: "Richieste", icon: Icon.mail, badge: moduleOn("requests") ? requests.pending : 0 },
|
|
174
|
+
...(local ? [{ id: "preview", label: "Anteprima", icon: Icon.play }] : []),
|
|
175
|
+
] },
|
|
176
|
+
];
|
|
177
|
+
/* Chi risponde del «Salva»: le pagine, la home, o nessuno (media e richieste si scrivono nel
|
|
178
|
+
* momento in cui si fa il gesto — un bottone di salvataggio lì sarebbe una promessa falsa).
|
|
179
|
+
*
|
|
180
|
+
* «Pubblica» invece NON passa di qui: è globale. Salvare scrive sulla bozza, e pubblicare
|
|
181
|
+
* manda online tutto ciò che c'è sulla bozza — pagine, home e impostazioni insieme — non la
|
|
182
|
+
* sezione che si sta guardando. Metterlo dentro `saver` avrebbe voluto dire tre pulsanti che
|
|
183
|
+
* fanno la stessa identica cosa. */
|
|
184
|
+
const saver = section === "pages" ? pages
|
|
185
|
+
: section === "home" ? home
|
|
186
|
+
: section === "settings" ? settings
|
|
187
|
+
: null;
|
|
188
|
+
const heading = section === "overview" ? "Panoramica"
|
|
189
|
+
: section === "pages" ? (editing ? String(editing.title ?? editing.slug) : "Pagine")
|
|
190
|
+
: section === "home" ? (homeGroup || "Home")
|
|
191
|
+
: view === "trash" ? "Cestino"
|
|
192
|
+
: section === "media" ? (media.prefix ? media.labelOf(media.prefix) : "Senza cartella")
|
|
193
|
+
/* Consegne non ripete il titolo qui: il nome del transfer sta nella sua schermata, dove
|
|
194
|
+
si modifica, e si aggiorna mentre lo si scrive. Averlo in due posti significa averne
|
|
195
|
+
uno dei due sempre vecchio. */
|
|
196
|
+
: section === "deliveries" ? (deliverySettingsOpen ? "Impostazioni" : "Consegne")
|
|
197
|
+
: section === "bookings" ? (bookingSettingsOpen ? "Impostazioni prenotazioni" : openBooking ? openBooking.visitorName || openBooking.referenceCode : "Prenotazioni")
|
|
198
|
+
: section === "requests" ? (openReq ? openReq.name : "Richieste")
|
|
199
|
+
: section === "settings" ? (settingsView === "seo" ? "SEO" : "Sito")
|
|
200
|
+
: "Anteprima";
|
|
201
|
+
const wide = section === "media" || editing?.type === "gallery" ||
|
|
202
|
+
editing?.type === "portfolio" || editing?.type === "about" ||
|
|
203
|
+
editing?.type === "workshop" || seoEditing ||
|
|
204
|
+
section === "settings" || section === "bookings";
|
|
205
|
+
/* Il percorso della cartella aperta, un pezzo per volta e ognuno cliccabile. Con il solo nome
|
|
206
|
+
dell'ultima cartella non si capisce dove si è finiti né come tornare indietro: sotto
|
|
207
|
+
«Piatti 2024» c'è food, ma quel «food» non era scritto da nessuna parte. */
|
|
208
|
+
const mediaCrumbs = media.prefix.split("/").filter(Boolean).map((_seg, i, all) => {
|
|
209
|
+
const p = all.slice(0, i + 1).join("/") + "/";
|
|
210
|
+
return { prefix: p, label: media.labelOf(p) };
|
|
211
|
+
});
|
|
212
|
+
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" : ""), 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", children: [_jsx(Icon.out, {}), _jsx("span", { children: "Vedi il sito" })] }), _jsxs("button", { className: "rail-item", onClick: toggleTheme, 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" : ""), 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 })
|
|
213
|
+
: !list ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
|
|
214
|
+
: _jsx(PagesList, { pages: list, current: editing?.slug, onEdit: (slug) => go(`/pages/${slug}`), onChange: pages.update, onRenamed: (from, to) => {
|
|
215
|
+
if (editing?.slug === from)
|
|
216
|
+
go(`/pages/${to}${seoEditing ? "/seo" : ""}`);
|
|
217
|
+
} }) })] })), section === "media" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx("span", { className: "col-title", children: "Cartelle" }) }), _jsx("div", { className: "col-body", children: _jsx(MediaFolders, { lib: media, trash: { active: view === "trash", open: () => go("/trash"), leave: () => go("/media") } }) })] })), section === "requests" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: pickedReq.size === 0 ? (_jsx("span", { className: "col-title", children: requests.rows ? `${requests.rows.length} richieste` : "Richieste" })) : (_jsxs("span", { className: "rq-bulk", children: [_jsxs("span", { className: "col-title", children: [pickedReq.size, " scelte"] }), _jsx("button", { className: "mg-act", onClick: () => { void requests.mark([...pickedReq], true); setPickedReq(new Set()); }, children: "Gestite" }), _jsx("button", { className: "mg-act is-danger", onClick: () => { void requests.remove([...pickedReq]); setPickedReq(new Set()); }, children: "Elimina" }), _jsx("button", { className: "mg-act", onClick: () => setPickedReq(new Set()), children: "\u00D7" })] })) }), _jsx("div", { className: "col-body", children: requests.error ? _jsx("p", { className: "ak-error", children: requests.error })
|
|
218
|
+
: !requests.rows ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
|
|
219
|
+
: requests.rows.length === 0 ? _jsx("p", { className: "ak-hint", children: "Nessuna richiesta, per ora." })
|
|
220
|
+
: _jsx(RequestsList, { rows: requests.rows, current: openReq?.id, picked: pickedReq, onOpen: (id) => go(`/requests/${id}`), onPick: (id) => setPickedReq((prev) => {
|
|
221
|
+
// Funzionale: due click ravvicinati leggerebbero lo stesso stato vecchio.
|
|
222
|
+
const next = new Set(prev);
|
|
223
|
+
if (next.has(id))
|
|
224
|
+
next.delete(id);
|
|
225
|
+
else
|
|
226
|
+
next.add(id);
|
|
227
|
+
return next;
|
|
228
|
+
}) }) })] })), section === "deliveries" && (_jsxs(_Fragment, { children: [_jsx("div", { className: "col-head", children: _jsx(NewDeliveryButton, { onCreate: () => {
|
|
229
|
+
setQuickSelectedId(undefined);
|
|
230
|
+
setQuickKey((value) => value + 1);
|
|
231
|
+
go("/deliveries");
|
|
232
|
+
} }) }), _jsx("div", { className: "col-body", children: deliveries.error ? _jsx("p", { className: "ak-error", children: deliveries.error })
|
|
233
|
+
: !deliveries.rows ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
|
|
234
|
+
: deliveries.rows.length === 0 ? _jsx("p", { className: "ak-hint", children: "Nessun transfer, per ora." })
|
|
235
|
+
: _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 })
|
|
236
|
+
: !bookings.rows ? _jsx("p", { className: "ak-hint", children: "Carico\u2026" })
|
|
237
|
+
: _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"}`) })) : view === "media" ? (_jsxs("nav", { className: "pane-crumbs", "aria-label": "Percorso", children: [_jsx("button", { className: "pane-crumb" + (mediaCrumbs.length ? "" : " is-current"), 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" : ""), onClick: () => media.go(c.prefix), children: c.label })] }, c.prefix)))] })) : (_jsx("h1", { className: "pane-title", children: heading })), view === "media" && _jsx("span", { className: "pane-count", children: media.shown.length }), _jsx("span", { className: "pane-spacer" }), view === "media" && (_jsx("button", { className: "ak-btn ak-btn-primary", onClick: () => media.setUploadOpen(true), 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,
|
|
238
|
+
/* Dopo un salvataggio il conteggio delle modifiche in attesa è cambiato: senza
|
|
239
|
+
rileggerlo, «Pubblica» comparirebbe solo al ricaricamento della pagina — cioè
|
|
240
|
+
il pannello direbbe che non c'è niente da pubblicare subito dopo aver salvato
|
|
241
|
+
qualcosa da pubblicare. */
|
|
242
|
+
onClick: () => void saver.save().then(() => publish.reload()), children: saver.saving ? "Salvo…" : "Salva" })] })), publish.ahead > 0 && (_jsx("button", { className: "ak-btn ak-btn-primary", disabled: publish.publishing, onClick: () => void publish.publish(), title: publish.commits.map((c) => c.message).join("\n"), children: publish.publishing ? "Pubblico…"
|
|
243
|
+
: publish.ahead === 1 ? "Pubblica (1 modifica)"
|
|
244
|
+
: `Pubblica (${publish.ahead} modifiche)` }))] }), offModule ? _jsx(FeatureOff, { info: offModule }) : _jsxs(_Fragment, { children: [draft !== null && (_jsxs("div", { className: "app-legacy", children: [_jsxs("p", { className: "app-legacy-row", children: [_jsxs("span", { children: ["Questo browser conserva una bozza del ", _jsx("strong", { children: "vecchio" }), " pannello (", draft, " pagine). Non viene usata: quello che si vede qui \u00E8 il sito pubblicato."] }), _jsx("button", { className: "ak-btn ak-btn-quiet", onClick: () => setDraftOpen((v) => !v), children: draftOpen ? "Nascondi" : "Cosa contiene" }), _jsx("button", { className: "ak-btn ak-btn-quiet", onClick: () => { discardLegacyDraft(); setDraft(null); }, children: "Butta" })] }), draftOpen && _jsx("p", { className: "app-legacy-list", children: legacyDraftTitles().join(" · ") })] })), _jsxs("div", { className: "pane-body" + (wide ? " is-wide" : ""), children: [section === "overview" && (
|
|
245
|
+
/* Transfer e richieste arrivano da QUI, già caricati per i badge della prima
|
|
246
|
+
colonna: la Panoramica non li richiede una seconda volta, altrimenti lo stesso
|
|
247
|
+
numero avrebbe due sorgenti e una delle due sarebbe sempre indietro. */
|
|
248
|
+
_jsx(Overview, { deliveries: deliveries.rows, expiring: deliveries.expiring, requests: requests.rows, pending: requests.pending, features: settings.data?.features ?? null, bookings: bookings.rows, bookingsPending: bookings.pending, bookingsError: bookings.error, go: go })), section === "bookings" && (bookings.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: bookings.error }), _jsx("button", { className: "ak-btn", onClick: () => void bookings.load(), children: "Riprova" })] })) : !bookings.rows || !bookings.settings ? _jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })
|
|
249
|
+
: bookingSettingsOpen ? _jsx(BookingSettingsHub, { store: bookings })
|
|
250
|
+
: openBooking ? _jsx(BookingDetail, { row: openBooking, store: bookings })
|
|
251
|
+
: _jsx(BookingAgenda, { store: bookings, onOpen: (id) => go(`/bookings/${id}`) })), section === "pages" && (pages.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: pages.error }), _jsx("button", { className: "ak-btn", onClick: () => void pages.load(), children: "Riprova" })] })) : editing ? (seoEditing ? (_jsx(PageSeoEditor, { page: editing, onChange: replace, onRefresh: pages.refresh, pristine: pages.isPristine(editing.slug), pickMediaFiles: pickMediaFiles, siteSettings: settings.data })) : (_jsx(PageEditor, { page: editing, onChange: replace, onRefresh: pages.refresh, pristine: pages.isPristine(editing.slug), pickMedia: pickMedia, pickMediaFiles: pickMediaFiles }))) : (_jsx("p", { className: "pane-empty ak-hint", children: list ? "Scegli una pagina dall'elenco, o creane una nuova." : "Carico…" }))), section === "home" && (
|
|
252
|
+
/* Con un errore di caricamento c'era solo la riga rossa: nessun modo di riprovare
|
|
253
|
+
se non ricaricando il pannello a mano. Le altre sezioni il bottone ce l'hanno. */
|
|
254
|
+
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" })] }))
|
|
255
|
+
: home.data ? _jsx(HomeEditor, { data: home.data, group: homeGroup, onChange: home.change, pickMedia: pickMedia })
|
|
256
|
+
: _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"
|
|
257
|
+
? _jsx(WebsiteSeoSettingsEditor, { value: settings.data, onChange: settings.change })
|
|
258
|
+
: _jsx(SiteSettingsEditor, { value: settings.data, onChange: settings.change, pickMediaFiles: pickMediaFiles, focus: route[1] })) : _jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })), view === "media" && _jsx(MediaGrid, { lib: media }), view === "trash" && _jsx(Trash, {}), section === "requests" && (requests.error ? _jsx("p", { className: "ak-error", children: requests.error })
|
|
259
|
+
: openReq ? (_jsx(RequestDetail, { row: openReq, onMark: (h) => void requests.mark([openReq.id], h), onRemove: () => { void requests.remove([openReq.id]); go("/requests"); } }))
|
|
260
|
+
: _jsx("p", { className: "pane-empty ak-hint", children: requests.rows?.length ? "Scegli una richiesta dall'elenco." : "" })), section === "deliveries" && (deliveries.error ? _jsx("p", { className: "ak-error", children: deliveries.error })
|
|
261
|
+
: deliverySettingsOpen ? (_jsx(DeliverySettings, { settings: deliveries.settings, onAction: deliveries.action, pickMedia: pickMedia })) : activeDeliveryId && !deliveries.detail ? (_jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })) : (
|
|
262
|
+
/* Un transfer nuovo e uno già spedito sono la stessa schermata: cambia solo se
|
|
263
|
+
arriva già con dei dati. La `key` la rimonta quando si passa da un transfer
|
|
264
|
+
all'altro, e quando «Nuovo transfer» chiede di ricominciare da capo. */
|
|
265
|
+
_jsx(DeliveryWorkspace, { delivery: deliveries.detail, busy: deliveries.busy, onAction: deliveries.action, onRefresh: deliveries.refresh, onReload: deliveries.load, onRead: deliveries.read, onDeleted: () => go("/deliveries"), onOpen: (id) => { setQuickSelectedId(undefined); go(`/deliveries/${id}`); }, onSelected: setQuickSelectedId }, deliveries.detail ? deliveries.detail.id : `nuovo-${quickKey}`))), section === "preview" && local && _jsx(Preview, {})] })] })] }), _jsx(UploadDock, {}), pickerElement] }) }));
|
|
266
|
+
}
|
|
267
|
+
/* Le sezioni che hanno una seconda colonna. Le altre lasciano la modifica a tutta larghezza.
|
|
268
|
+
*
|
|
269
|
+
* Per i media l'elenco sono le CARTELLE: il browser non se le tiene più per sé, così stanno
|
|
270
|
+
* dove stanno tutti gli altri elenchi del pannello — e diventano il bersaglio a cui trascinare
|
|
271
|
+
* le foto per spostarle. */
|
|
272
|
+
function hasList(section) {
|
|
273
|
+
return section === "pages" || section === "home" || section === "media" ||
|
|
274
|
+
section === "deliveries" || section === "bookings" || section === "requests" || section === "settings";
|
|
275
|
+
}
|
|
276
|
+
/* ---- Le funzioni aggiuntive, quando sono spente ---------------------------------------------
|
|
277
|
+
*
|
|
278
|
+
* Una voce spenta non sparisce dal rail: chi usa il pannello deve poter scoprire che quella
|
|
279
|
+
* funzione esiste. Cliccandola trova questa schermata invece di un elenco vuoto o di un errore
|
|
280
|
+
* — che è la differenza fra «non ce l'hai» e «non funziona».
|
|
281
|
+
*
|
|
282
|
+
* I testi stanno qui e non nella configurazione di un sito perché descrivono il MODULO, che è
|
|
283
|
+
* lo stesso ovunque; l'unica cosa per-sito è a chi scrivere, e quella arriva da `site().support`.
|
|
284
|
+
* -------------------------------------------------------------------------------------------- */
|
|
285
|
+
const OPTIONAL_SECTIONS = {
|
|
286
|
+
deliveries: {
|
|
287
|
+
module: "delivery",
|
|
288
|
+
title: "Consegne",
|
|
289
|
+
lead: "Manda le fotografie a un cliente con un link privato, senza allegati e senza servizi di terze parti.",
|
|
290
|
+
points: [
|
|
291
|
+
"Un indirizzo riservato per ogni consegna, che scade da solo",
|
|
292
|
+
"Il cliente scarica tutto in una volta, o solo le foto che gli servono",
|
|
293
|
+
"Vedi quante volte è stato scaricato, e da quando",
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
bookings: {
|
|
297
|
+
module: "booking",
|
|
298
|
+
title: "Prenotazioni",
|
|
299
|
+
lead: "Fai scegliere data e ora direttamente dal sito, senza scambi di messaggi per trovare un momento.",
|
|
300
|
+
points: [
|
|
301
|
+
"Gli orari liberi vengono dal tuo calendario, non da un elenco da aggiornare a mano",
|
|
302
|
+
"Conferme e promemoria partono da soli",
|
|
303
|
+
"Gli appuntamenti li vedi qui e nel calendario insieme",
|
|
304
|
+
],
|
|
305
|
+
},
|
|
306
|
+
requests: {
|
|
307
|
+
module: "requests",
|
|
308
|
+
title: "Richieste",
|
|
309
|
+
lead: "Le richieste che arrivano dal modulo di contatto del sito, raccolte qui invece che in una casella di posta.",
|
|
310
|
+
points: [
|
|
311
|
+
"Nessuna richiesta persa fra le altre email",
|
|
312
|
+
"Segni quali hai già gestito",
|
|
313
|
+
"Rispondi con un click, dall'indirizzo di sempre",
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
function FeatureOff({ info }) {
|
|
318
|
+
const { name, email } = site().support;
|
|
319
|
+
return (_jsxs("section", { className: "feature-off", children: [_jsx("span", { className: "feature-off-kicker", children: "Funzione aggiuntiva" }), _jsx("h2", { children: info.title }), _jsx("p", { className: "feature-off-lead", children: info.lead }), _jsx("ul", { children: info.points.map((point) => _jsx("li", { children: point }, point)) }), _jsxs("p", { className: "feature-off-cta", children: ["Su questo sito non \u00E8 attiva. Per accenderla scrivi a ", _jsx("strong", { children: name }), ":", " ", _jsx("a", { href: `mailto:${email}?subject=${encodeURIComponent(`Attivare ${info.title} su ${site().name}`)}`, children: email }), "."] })] }));
|
|
320
|
+
}
|