@xiwebdesign/xicms-core 6.4.7 → 7.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/SiteChrome.d.ts +12 -0
- package/dist/SiteChrome.js +24 -0
- package/dist/SiteChrome.js.map +1 -0
- package/dist/XiCMSProvider.d.ts +16 -8
- package/dist/XiCMSProvider.js +83 -58
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/AdminApp.d.ts +1 -1
- package/dist/admin/AdminApp.js +79 -35
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/AdminHelp.d.ts +8 -1
- package/dist/admin/AdminHelp.js +2 -2
- package/dist/admin/AdminHelp.js.map +1 -1
- package/dist/admin/NewsEditor.js +9 -9
- package/dist/admin/SuperadminPanel.js +5 -5
- package/dist/admin/blockEditors/GenericEditor.d.ts +2 -2
- package/dist/admin/blockEditors/GenericEditor.js +12 -11
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/blockEditors/StyleEditor.d.ts +1 -1
- package/dist/admin/blockEditors/StyleEditor.js +2 -2
- package/dist/admin/blockEditors/SymbolRefEditor.d.ts +1 -1
- package/dist/admin/blockEditors/SymbolRefEditor.js +5 -5
- package/dist/admin/blockEditors/index.d.ts +1 -1
- package/dist/admin/blockEditors/index.js +10 -10
- package/dist/admin/customBlocks/CustomBlocksPanel.js +7 -7
- package/dist/admin/editors/RichTextEditor.js +8 -7
- package/dist/admin/editors/RichTextEditor.js.map +1 -1
- package/dist/admin/editors/fontSizeExtension.js +1 -1
- package/dist/admin/editors/fontSizeExtension.js.map +1 -1
- package/dist/admin/forms/FormsPanel.js +9 -9
- package/dist/admin/index.d.ts +16 -16
- package/dist/admin/index.js +16 -16
- package/dist/admin/lib/mediaUsage.js +1 -1
- package/dist/admin/lib/pagesClient.d.ts +1 -1
- package/dist/admin/lib/pagesClient.js +11 -7
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/sanitizeDraft.d.ts +9 -0
- package/dist/admin/lib/sanitizeDraft.js +28 -0
- package/dist/admin/lib/sanitizeDraft.js.map +1 -0
- package/dist/admin/lib/symbolsClient.d.ts +1 -1
- package/dist/admin/lib/symbolsClient.js +3 -3
- package/dist/admin/lib/upload.js +3 -3
- package/dist/admin/lib/useNestedDialog.d.ts +15 -0
- package/dist/admin/lib/useNestedDialog.js +23 -0
- package/dist/admin/lib/useNestedDialog.js.map +1 -0
- package/dist/admin/lib/zodFields.d.ts +4 -18
- package/dist/admin/lib/zodFields.js +54 -35
- package/dist/admin/lib/zodFields.js.map +1 -1
- package/dist/admin/media/MediaManager.js +7 -7
- package/dist/admin/media/MediaPicker.js +7 -7
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +1 -1
- package/dist/admin/pages/PageEditor.js +13 -13
- package/dist/admin/pages/PagesPanel.js +30 -30
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/panelRegistry.js +7 -7
- package/dist/admin/patterns/PatternInserter.d.ts +1 -1
- package/dist/admin/patterns/PatternInserter.js +5 -5
- package/dist/admin/patterns/PatternInserter.js.map +1 -1
- package/dist/admin/settings/MailSettings.js +5 -5
- package/dist/admin/settings/MailTemplates.d.ts +1 -1
- package/dist/admin/settings/MailTemplates.js +5 -5
- package/dist/admin/settings/SettingsPanel.js +20 -20
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/settings/SnapshotsCard.js +5 -5
- package/dist/admin/settings/moduleSettingsForm.d.ts +1 -1
- package/dist/admin/settings/moduleSettingsForm.js +4 -4
- package/dist/admin/settings/moduleSettingsForm.js.map +1 -1
- package/dist/admin/shop/CategoriesTab.js +8 -8
- package/dist/admin/shop/ConnectionsTab.js +3 -3
- package/dist/admin/shop/DiscountsTab.js +8 -8
- package/dist/admin/shop/OrdersTab.js +10 -10
- package/dist/admin/shop/OrdersTab.js.map +1 -1
- package/dist/admin/shop/ProductsTab.js +10 -10
- package/dist/admin/shop/ShopPanel.js +9 -9
- package/dist/admin/shop/ShopSettingsTab.js +4 -4
- package/dist/admin/shop/index.d.ts +2 -2
- package/dist/admin/shop/index.js +2 -2
- package/dist/admin/shop/lib.js +2 -2
- package/dist/admin/stats/StatsPanel.js +2 -2
- package/dist/admin/ui/ArrayEditor.js +1 -1
- package/dist/admin/ui/PanelIcon.d.ts +3 -0
- package/dist/admin/ui/PanelIcon.js +34 -0
- package/dist/admin/ui/PanelIcon.js.map +1 -0
- package/dist/admin/ui/dialog.d.ts +21 -6
- package/dist/admin/ui/dialog.js +55 -34
- package/dist/admin/ui/dialog.js.map +1 -1
- package/dist/admin/ui/index.d.ts +7 -7
- package/dist/admin/ui/index.js +7 -7
- package/dist/admin/ui/primitives.js +1 -1
- package/dist/admin/ui/tabs.js +1 -1
- package/dist/admin/visual/TranslationPanel.d.ts +1 -1
- package/dist/admin/visual/TranslationPanel.js +3 -3
- package/dist/admin/visual/VisualEditor.js +47 -25
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +2 -1
- package/dist/agentFiles/catalog.d.ts +2 -2
- package/dist/agentFiles/catalog.js +6 -5
- package/dist/agentFiles/catalog.js.map +1 -1
- package/dist/agentFiles/files.d.ts +5 -5
- package/dist/agentFiles/files.js +17 -17
- package/dist/agentFiles/files.js.map +1 -1
- package/dist/all.d.ts +4 -4
- package/dist/all.js +6 -6
- package/dist/auth/AuthProvider.d.ts +2 -2
- package/dist/auth/AuthProvider.js +51 -28
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/auth/LoginForm.js +1 -1
- package/dist/auth/roles.js +4 -1
- package/dist/auth/roles.js.map +1 -1
- package/dist/blocks/BlockRenderer.d.ts +1 -1
- package/dist/blocks/BlockRenderer.js +3 -3
- package/dist/blocks/blockStyle.d.ts +1 -1
- package/dist/blocks/builtins/Accordion.d.ts +1 -1
- package/dist/blocks/builtins/Accordion.js +13 -6
- package/dist/blocks/builtins/Accordion.js.map +1 -1
- package/dist/blocks/builtins/Agenda.d.ts +1 -1
- package/dist/blocks/builtins/Agenda.js +5 -5
- package/dist/blocks/builtins/Banner.d.ts +1 -1
- package/dist/blocks/builtins/Banner.js +2 -2
- package/dist/blocks/builtins/BeforeAfter.d.ts +1 -1
- package/dist/blocks/builtins/BeforeAfter.js +4 -4
- package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
- package/dist/blocks/builtins/Booking.d.ts +1 -1
- package/dist/blocks/builtins/Booking.js +5 -5
- package/dist/blocks/builtins/Breadcrumbs.d.ts +1 -1
- package/dist/blocks/builtins/Breadcrumbs.js +3 -3
- package/dist/blocks/builtins/Cards.d.ts +1 -1
- package/dist/blocks/builtins/Cards.js +5 -5
- package/dist/blocks/builtins/Cards.js.map +1 -1
- package/dist/blocks/builtins/Columns.d.ts +1 -1
- package/dist/blocks/builtins/Columns.js +4 -4
- package/dist/blocks/builtins/Contact.d.ts +3 -3
- package/dist/blocks/builtins/Contact.js +4 -4
- package/dist/blocks/builtins/Countdown.d.ts +1 -1
- package/dist/blocks/builtins/Countdown.js +4 -4
- package/dist/blocks/builtins/Countdown.js.map +1 -1
- package/dist/blocks/builtins/Cta.d.ts +1 -1
- package/dist/blocks/builtins/Cta.js +3 -3
- package/dist/blocks/builtins/Downloads.d.ts +1 -1
- package/dist/blocks/builtins/Downloads.js +4 -4
- package/dist/blocks/builtins/Elements.d.ts +3 -3
- package/dist/blocks/builtins/Elements.js +3 -3
- package/dist/blocks/builtins/Embed.d.ts +1 -1
- package/dist/blocks/builtins/Embed.js +3 -3
- package/dist/blocks/builtins/Faq.d.ts +1 -1
- package/dist/blocks/builtins/Faq.js +14 -6
- package/dist/blocks/builtins/Faq.js.map +1 -1
- package/dist/blocks/builtins/FormBlock.d.ts +1 -1
- package/dist/blocks/builtins/FormBlock.js +28 -18
- package/dist/blocks/builtins/FormBlock.js.map +1 -1
- package/dist/blocks/builtins/Gallery.d.ts +2 -2
- package/dist/blocks/builtins/Gallery.js +5 -5
- package/dist/blocks/builtins/Hero.d.ts +1 -1
- package/dist/blocks/builtins/Hero.js +4 -4
- package/dist/blocks/builtins/ImageBlock.d.ts +1 -1
- package/dist/blocks/builtins/ImageBlock.js +3 -3
- package/dist/blocks/builtins/Jobs.d.ts +2 -2
- package/dist/blocks/builtins/Jobs.js +5 -5
- package/dist/blocks/builtins/Layout.d.ts +2 -2
- package/dist/blocks/builtins/Layout.js +4 -4
- package/dist/blocks/builtins/MapBlock.d.ts +1 -1
- package/dist/blocks/builtins/MapBlock.js +3 -3
- package/dist/blocks/builtins/NewsListBlock.d.ts +1 -1
- package/dist/blocks/builtins/NewsListBlock.js +4 -4
- package/dist/blocks/builtins/Newsletter.d.ts +1 -1
- package/dist/blocks/builtins/Newsletter.js +7 -7
- package/dist/blocks/builtins/Newsletter.js.map +1 -1
- package/dist/blocks/builtins/OpenNowBadge.d.ts +1 -1
- package/dist/blocks/builtins/OpenNowBadge.js +8 -3
- package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
- package/dist/blocks/builtins/OpeningHours.d.ts +1 -1
- package/dist/blocks/builtins/OpeningHours.js +6 -6
- package/dist/blocks/builtins/PdfBlock.d.ts +1 -1
- package/dist/blocks/builtins/PdfBlock.js +3 -3
- package/dist/blocks/builtins/Portfolio.d.ts +1 -1
- package/dist/blocks/builtins/Portfolio.js +5 -5
- package/dist/blocks/builtins/Pricing.d.ts +2 -2
- package/dist/blocks/builtins/Pricing.js +5 -5
- package/dist/blocks/builtins/Pricing.js.map +1 -1
- package/dist/blocks/builtins/RichTextBlock.d.ts +1 -1
- package/dist/blocks/builtins/RichTextBlock.js +2 -2
- package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
- package/dist/blocks/builtins/Search.d.ts +1 -1
- package/dist/blocks/builtins/Search.js +9 -7
- package/dist/blocks/builtins/Search.js.map +1 -1
- package/dist/blocks/builtins/Sections.d.ts +4 -4
- package/dist/blocks/builtins/Sections.js +4 -4
- package/dist/blocks/builtins/Slider.d.ts +1 -1
- package/dist/blocks/builtins/Slider.js +3 -3
- package/dist/blocks/builtins/Spacer.d.ts +1 -1
- package/dist/blocks/builtins/Spacer.js +2 -2
- package/dist/blocks/builtins/SymbolRef.d.ts +1 -1
- package/dist/blocks/builtins/SymbolRef.js +31 -17
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/builtins/Team.d.ts +1 -1
- package/dist/blocks/builtins/Team.js +4 -4
- package/dist/blocks/builtins/Testimonials.d.ts +2 -2
- package/dist/blocks/builtins/Testimonials.js +4 -4
- package/dist/blocks/builtins/Video.d.ts +2 -2
- package/dist/blocks/builtins/Video.js +4 -4
- package/dist/blocks/builtins/Video.js.map +1 -1
- package/dist/blocks/builtins/WhatsappChat.d.ts +1 -1
- package/dist/blocks/builtins/WhatsappChat.js +2 -2
- package/dist/blocks/builtins/Widgets.d.ts +12 -12
- package/dist/blocks/builtins/Widgets.js +19 -19
- package/dist/blocks/builtins/Widgets.js.map +1 -1
- package/dist/blocks/builtins/consentEmbed.js +2 -2
- package/dist/blocks/builtins/index.d.ts +40 -40
- package/dist/blocks/builtins/index.js +40 -40
- package/dist/blocks/builtins/lib/submitForm.js +5 -3
- package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
- package/dist/blocks/context.d.ts +1 -1
- package/dist/blocks/customTemplate/definition.d.ts +3 -3
- package/dist/blocks/customTemplate/definition.js +6 -6
- package/dist/blocks/customTemplate/definition.js.map +1 -1
- package/dist/blocks/customTemplate/interpret.js +1 -1
- package/dist/blocks/index.d.ts +5 -5
- package/dist/blocks/index.js +5 -5
- package/dist/blocks/registry.d.ts +17 -7
- package/dist/blocks/registry.js +2 -1
- package/dist/blocks/registry.js.map +1 -1
- package/dist/components/EditBar.js +3 -3
- package/dist/components/FeedbackWidget.js +4 -4
- package/dist/components/News.d.ts +1 -1
- package/dist/components/News.js +50 -37
- package/dist/components/News.js.map +1 -1
- package/dist/components/PageView.js +11 -8
- package/dist/components/PageView.js.map +1 -1
- package/dist/components/ResponsiveImage.d.ts +1 -1
- package/dist/components/site/SiteFooter.d.ts +1 -1
- package/dist/components/site/SiteFooter.js +2 -2
- package/dist/config.d.ts +1 -1
- package/dist/demo/DemoStudio.js +3 -3
- package/dist/demo/demoStore.d.ts +3 -3
- package/dist/demo/index.d.ts +2 -2
- package/dist/demo/index.js +2 -2
- package/dist/editor/EditorProvider.js +1 -1
- package/dist/firebase/admin.d.ts +0 -2
- package/dist/firebase/admin.js +0 -6
- package/dist/firebase/admin.js.map +1 -1
- package/dist/firebase/appcheck.d.ts +1 -1
- package/dist/firebase/appcheck.js +5 -3
- package/dist/firebase/appcheck.js.map +1 -1
- package/dist/firebase/client.d.ts +2 -1
- package/dist/firebase/client.js +2 -14
- package/dist/firebase/client.js.map +1 -1
- package/dist/firebase/config.d.ts +31 -0
- package/dist/firebase/config.js +39 -0
- package/dist/firebase/config.js.map +1 -0
- package/dist/firebase/feedback.js +5 -3
- package/dist/firebase/feedback.js.map +1 -1
- package/dist/i18n/overlay.d.ts +2 -2
- package/dist/i18n/overlay.js +6 -4
- package/dist/i18n/overlay.js.map +1 -1
- package/dist/index.d.ts +45 -44
- package/dist/index.js +35 -33
- package/dist/index.js.map +1 -1
- package/dist/lock/DemoLock.js +1 -1
- package/dist/modules/builtins.d.ts +1 -1
- package/dist/modules/builtins.js +53 -55
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/consent/ConsentBanner.js +1 -1
- package/dist/modules/consent/useConsent.js +1 -1
- package/dist/modules/context.d.ts +1 -1
- package/dist/modules/contract.d.ts +1 -1
- package/dist/modules/defineModule.d.ts +1 -1
- package/dist/modules/index.d.ts +9 -9
- package/dist/modules/index.js +8 -8
- package/dist/modules/overrides.d.ts +1 -1
- package/dist/modules/registry.d.ts +2 -7
- package/dist/modules/registry.js +2 -1
- package/dist/modules/registry.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.d.ts +1 -1
- package/dist/modules/shop/blocks/Cart.js +6 -6
- package/dist/modules/shop/blocks/Checkout.d.ts +1 -1
- package/dist/modules/shop/blocks/Checkout.js +6 -6
- package/dist/modules/shop/blocks/Donation.d.ts +1 -1
- package/dist/modules/shop/blocks/Donation.js +5 -5
- package/dist/modules/shop/blocks/OrderStatus.d.ts +1 -1
- package/dist/modules/shop/blocks/OrderStatus.js +5 -5
- package/dist/modules/shop/blocks/ProductDetail.d.ts +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +8 -8
- package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -3
- package/dist/modules/shop/blocks/ProductGrid.js +7 -7
- package/dist/modules/shop/blocks/ShopCategories.d.ts +1 -1
- package/dist/modules/shop/blocks/ShopCategories.js +6 -6
- package/dist/modules/shop/index.d.ts +5 -5
- package/dist/modules/shop/index.js +5 -5
- package/dist/modules/shop/lib.d.ts +2 -2
- package/dist/modules/shop/lib.js +18 -13
- package/dist/modules/shop/lib.js.map +1 -1
- package/dist/modules/shop/module.d.ts +1 -1
- package/dist/modules/shop/module.js +9 -9
- package/dist/modules/shop/settings.d.ts +18 -50
- package/dist/modules/shop/settings.js +16 -16
- package/dist/modules/shop/settings.js.map +1 -1
- package/dist/modules/shop/strings.js +1 -1
- package/dist/modules/shop/types.d.ts +1 -1
- package/dist/modules/types.d.ts +2 -2
- package/dist/patterns/builtins.d.ts +1 -1
- package/dist/patterns/builtins.js +1 -1
- package/dist/patterns/index.d.ts +2 -2
- package/dist/patterns/index.js +2 -2
- package/dist/render/PageRenderer.d.ts +2 -2
- package/dist/render/PageRenderer.js +1 -1
- package/dist/render/PublishedPage.d.ts +1 -1
- package/dist/render/PublishedPage.js +2 -2
- package/dist/schema/blocks.d.ts +6793 -15095
- package/dist/schema/blocks.js +335 -355
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/customBlocks.d.ts +10 -29
- package/dist/schema/customBlocks.js +14 -13
- package/dist/schema/customBlocks.js.map +1 -1
- package/dist/schema/fields.d.ts +10 -16
- package/dist/schema/fields.js +37 -37
- package/dist/schema/fields.js.map +1 -1
- package/dist/schema/forms.d.ts +62 -111
- package/dist/schema/forms.js +17 -17
- package/dist/schema/forms.js.map +1 -1
- package/dist/schema/index.d.ts +7 -7
- package/dist/schema/index.js +7 -7
- package/dist/schema/mail.d.ts +39 -87
- package/dist/schema/mail.js +4 -4
- package/dist/schema/mail.js.map +1 -1
- package/dist/schema/pages.d.ts +152 -674
- package/dist/schema/pages.js +26 -15
- package/dist/schema/pages.js.map +1 -1
- package/dist/schema/seo.d.ts +9 -27
- package/dist/schema/seo.js +7 -7
- package/dist/schema/seo.js.map +1 -1
- package/dist/schema/settings.d.ts +218 -1074
- package/dist/schema/settings.js +55 -55
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +470 -1214
- package/dist/schema/shop.js +124 -124
- package/dist/schema/shop.js.map +1 -1
- package/dist/schema/siteConfig.d.ts +73 -597
- package/dist/schema/siteConfig.js +17 -17
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/schema/symbols.d.ts +34 -191
- package/dist/schema/symbols.js +8 -8
- package/dist/schema/symbols.js.map +1 -1
- package/dist/server/auth.d.ts +17 -0
- package/dist/server/auth.js +11 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/bundle.d.ts +15 -6
- package/dist/server/bundle.js +5 -1
- package/dist/server/bundle.js.map +1 -1
- package/dist/server/draft.d.ts +16 -0
- package/dist/server/draft.js +12 -0
- package/dist/server/draft.js.map +1 -0
- package/dist/server/env.js +8 -8
- package/dist/server/env.js.map +1 -1
- package/dist/server/index.d.ts +10 -8
- package/dist/server/index.js +12 -9
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +14 -7
- package/dist/server/pages.js +183 -17
- package/dist/server/pages.js.map +1 -1
- package/dist/server/revalidate.d.ts +6 -0
- package/dist/server/revalidate.js +40 -0
- package/dist/server/revalidate.js.map +1 -0
- package/dist/server/seo.d.ts +3 -3
- package/dist/server/seo.js +1 -1
- package/dist/server/shop.d.ts +3 -3
- package/dist/server/shop.js +6 -3
- package/dist/server/shop.js.map +1 -1
- package/dist/settings/useSiteSettings.d.ts +2 -2
- package/dist/settings/useSiteSettings.js +48 -28
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/styles.css +4 -1
- package/dist/theme/presets.d.ts +1 -1
- package/package.json +35 -34
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/media/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { collection, limit, onSnapshot, orderBy, query } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { DEMO_EVENT, isDemoActive, listDemoMediaAll } from \"../../demo/demoStore\";\nimport type { MediaItem } from \"../../content/types\";\nimport { Button, Input } from \"../ui/primitives\";\nimport { Dialog, DialogContent } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { uploadImage } from \"../lib/upload\";\n\nexport interface PickedMedia {\n url: string;\n mediaId: string;\n alt?: string;\n}\n\n/** Paginagrootte: begrensd laden i.p.v. de hele bibliotheek bij elke kiezer-open. */\nconst PICKER_PAGE_SIZE = 48;\n\n/** Gecontroleerde mediabibliotheek-kiezer (open/onOpenChange extern) — zoek + klik om te kiezen, of\n * upload direct. Gebruikt door de knop hieronder én door de inline beeld-vervangen in de editor. */\nexport function MediaPickerDialog({\n siteId,\n open,\n onOpenChange,\n onSelect,\n}: {\n siteId: string;\n open: boolean;\n onOpenChange: (v: boolean) => void;\n onSelect: (m: PickedMedia) => void;\n}) {\n const { toast } = useToast();\n const [items, setItems] = useState<MediaItem[]>([]);\n const [q, setQ] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [pageCount, setPageCount] = useState(1);\n const [hasMore, setHasMore] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (!open) return;\n // Demo-modus: lokale bibliotheek (geseede foto's + eigen \"uploads\") i.p.v. Firestore.\n if (isDemoActive()) {\n const sync = () => setItems(listDemoMediaAll(siteId));\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) return;\n // Begrensd laden (+\"meer laden\"): geen volledige collectie-read bij elke kiezer-open.\n const max = pageCount * PICKER_PAGE_SIZE;\n const qy = query(collection(db, \"sites\", siteId, \"media\"), orderBy(\"createdAt\", \"desc\"), limit(max + 1));\n return onSnapshot(\n qy,\n (snap) => {\n setHasMore(snap.docs.length > max);\n setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...(d.data() as Omit<MediaItem, \"id\">) })));\n },\n // Zonder onError worden lees-/rules-/App-Check-fouten stil geslikt → de kiezer toont dan\n // onterecht \"Nog geen afbeeldingen\". Nu een duidelijke melding.\n (err) => toast({ title: \"Kon mediabibliotheek niet laden\", description: err.message, tone: \"error\" }),\n );\n }, [open, siteId, toast, pageCount]);\n\n const filtered = useMemo(() => {\n const term = q.trim().toLowerCase();\n if (!term) return items;\n return items.filter((m) => `${m.name ?? \"\"} ${m.alt ?? \"\"}`.toLowerCase().includes(term));\n }, [items, q]);\n\n function pick(m: MediaItem) {\n onSelect({ url: m.url, mediaId: m.id, alt: m.alt });\n onOpenChange(false);\n }\n\n async function onUpload(files: FileList | null) {\n const f = files?.[0];\n if (!f) return;\n setBusy(true);\n try {\n const { url, mediaId } = await uploadImage(siteId, f);\n onSelect({ url, mediaId, alt: \"\" });\n onOpenChange(false);\n } catch (err) {\n toast({ title: \"Upload mislukt\", description: (err as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent title=\"Afbeelding kiezen\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/media/MediaPicker.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { collection, limit, onSnapshot, orderBy, query } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { DEMO_EVENT, isDemoActive, listDemoMediaAll } from \"../../demo/demoStore\";\nimport type { MediaItem } from \"../../content/types\";\nimport { Button, Input } from \"../ui/primitives\";\nimport { Dialog, DialogContent } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { uploadImage } from \"../lib/upload\";\n\nexport interface PickedMedia {\n url: string;\n mediaId: string;\n alt?: string;\n}\n\n/** Paginagrootte: begrensd laden i.p.v. de hele bibliotheek bij elke kiezer-open. */\nconst PICKER_PAGE_SIZE = 48;\n\n/** Gecontroleerde mediabibliotheek-kiezer (open/onOpenChange extern) — zoek + klik om te kiezen, of\n * upload direct. Gebruikt door de knop hieronder én door de inline beeld-vervangen in de editor. */\nexport function MediaPickerDialog({\n siteId,\n open,\n onOpenChange,\n onSelect,\n}: {\n siteId: string;\n open: boolean;\n onOpenChange: (v: boolean) => void;\n onSelect: (m: PickedMedia) => void;\n}) {\n const { toast } = useToast();\n const [items, setItems] = useState<MediaItem[]>([]);\n const [q, setQ] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [pageCount, setPageCount] = useState(1);\n const [hasMore, setHasMore] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (!open) return;\n // Demo-modus: lokale bibliotheek (geseede foto's + eigen \"uploads\") i.p.v. Firestore.\n if (isDemoActive()) {\n const sync = () => setItems(listDemoMediaAll(siteId));\n sync();\n window.addEventListener(DEMO_EVENT, sync);\n return () => window.removeEventListener(DEMO_EVENT, sync);\n }\n const db = getDb();\n if (!db) return;\n // Begrensd laden (+\"meer laden\"): geen volledige collectie-read bij elke kiezer-open.\n const max = pageCount * PICKER_PAGE_SIZE;\n const qy = query(collection(db, \"sites\", siteId, \"media\"), orderBy(\"createdAt\", \"desc\"), limit(max + 1));\n return onSnapshot(\n qy,\n (snap) => {\n setHasMore(snap.docs.length > max);\n setItems(snap.docs.slice(0, max).map((d) => ({ id: d.id, ...(d.data() as Omit<MediaItem, \"id\">) })));\n },\n // Zonder onError worden lees-/rules-/App-Check-fouten stil geslikt → de kiezer toont dan\n // onterecht \"Nog geen afbeeldingen\". Nu een duidelijke melding.\n (err) => toast({ title: \"Kon mediabibliotheek niet laden\", description: err.message, tone: \"error\" }),\n );\n }, [open, siteId, toast, pageCount]);\n\n const filtered = useMemo(() => {\n const term = q.trim().toLowerCase();\n if (!term) return items;\n return items.filter((m) => `${m.name ?? \"\"} ${m.alt ?? \"\"}`.toLowerCase().includes(term));\n }, [items, q]);\n\n function pick(m: MediaItem) {\n onSelect({ url: m.url, mediaId: m.id, alt: m.alt });\n onOpenChange(false);\n }\n\n async function onUpload(files: FileList | null) {\n const f = files?.[0];\n if (!f) return;\n setBusy(true);\n try {\n const { url, mediaId } = await uploadImage(siteId, f);\n onSelect({ url, mediaId, alt: \"\" });\n onOpenChange(false);\n } catch (err) {\n toast({ title: \"Upload mislukt\", description: (err as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent title=\"Afbeelding kiezen\" width=\"min(92vw, 42rem)\">\n <div className=\"mb-3 flex items-center gap-2\">\n <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder=\"Zoek in bibliotheek…\" className=\"h-9\" />\n <Button type=\"button\" disabled={busy} onClick={() => inputRef.current?.click()}>\n {busy ? \"Uploaden…\" : \"Upload nieuwe\"}\n </Button>\n <input ref={inputRef} type=\"file\" accept=\"image/*\" hidden onChange={(e) => { void onUpload(e.target.files); e.target.value = \"\"; }} />\n </div>\n <div className=\"max-h-[55vh] overflow-y-auto\">\n {filtered.length === 0 ? (\n <p className=\"py-8 text-center text-sm text-slate-500\">\n {items.length === 0 ? \"Nog geen afbeeldingen — upload er een.\" : \"Geen resultaten.\"}\n </p>\n ) : (\n <div className=\"grid grid-cols-3 gap-2 sm:grid-cols-4\">\n {filtered.map((m) => (\n <button\n key={m.id}\n type=\"button\"\n onClick={() => pick(m)}\n title={m.alt || m.name}\n className=\"group relative overflow-hidden rounded-md border border-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500\"\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={m.url} alt={m.alt ?? m.name} className=\"aspect-square w-full object-cover transition group-hover:opacity-80\" />\n </button>\n ))}\n </div>\n )}\n {hasMore && !q.trim() && (\n <p className=\"mt-2 text-center\">\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setPageCount((c) => c + 1)}>\n Meer laden…\n </Button>\n </p>\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * Knop die de mediabibliotheek-kiezer opent: zoek + klik om een bestaande afbeelding te kiezen, of\n * upload er direct een nieuwe in. Vervangt het plakken van losse URL's.\n */\nexport function MediaPickerButton({\n siteId,\n onSelect,\n label = \"Kies / upload\",\n size = \"sm\",\n variant = \"secondary\",\n}: {\n siteId: string;\n onSelect: (m: PickedMedia) => void;\n label?: string;\n size?: \"sm\" | \"md\";\n variant?: \"primary\" | \"secondary\" | \"ghost\";\n}) {\n const [open, setOpen] = useState(false);\n return (\n <>\n <Button type=\"button\" variant={variant} size={size} onClick={() => setOpen(true)}>{label}</Button>\n <MediaPickerDialog siteId={siteId} open={open} onOpenChange={setOpen} onSelect={onSelect} />\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { useRegistry } from "../../blocks/context";
|
|
5
|
-
import { buildBlockAddOptions } from "../../blocks/registry";
|
|
6
|
-
import { useModules } from "../../modules/context";
|
|
7
|
-
import { moduleIdForBlockType } from "../../modules/registry";
|
|
8
|
-
import { Button, Card, Field, Input, Textarea, Badge } from "../ui/primitives";
|
|
9
|
-
import { Select } from "../ui/select";
|
|
10
|
-
import { useToast } from "../ui/toast";
|
|
11
|
-
import { useConfirm } from "../ui/ConfirmDialog";
|
|
12
|
-
import { builtinBlockEditors, GenericEditor } from "../blockEditors";
|
|
13
|
-
import { StyleEditor } from "../blockEditors/StyleEditor";
|
|
14
|
-
import { PatternInserter } from "../patterns/PatternInserter";
|
|
15
|
-
import { saveDraft, publishPage, subscribeVersions, restoreVersion } from "../lib/pagesClient";
|
|
16
|
-
import { slugify } from "../lib/slugify";
|
|
4
|
+
import { useRegistry } from "../../blocks/context.js";
|
|
5
|
+
import { buildBlockAddOptions } from "../../blocks/registry.js";
|
|
6
|
+
import { useModules } from "../../modules/context.js";
|
|
7
|
+
import { moduleIdForBlockType } from "../../modules/registry.js";
|
|
8
|
+
import { Button, Card, Field, Input, Textarea, Badge } from "../ui/primitives.js";
|
|
9
|
+
import { Select } from "../ui/select.js";
|
|
10
|
+
import { useToast } from "../ui/toast.js";
|
|
11
|
+
import { useConfirm } from "../ui/ConfirmDialog.js";
|
|
12
|
+
import { builtinBlockEditors, GenericEditor } from "../blockEditors/index.js";
|
|
13
|
+
import { StyleEditor } from "../blockEditors/StyleEditor.js";
|
|
14
|
+
import { PatternInserter } from "../patterns/PatternInserter.js";
|
|
15
|
+
import { saveDraft, publishPage, subscribeVersions, restoreVersion } from "../lib/pagesClient.js";
|
|
16
|
+
import { slugify } from "../lib/slugify.js";
|
|
17
17
|
function newKey() {
|
|
18
18
|
return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);
|
|
19
19
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import { Button, Card, Field, Input, Badge } from "../ui/primitives";
|
|
5
|
-
import { Dialog, DialogTrigger, DialogContent, DialogClose } from "../ui/dialog";
|
|
6
|
-
import { useToast } from "../ui/toast";
|
|
7
|
-
import { useConfirm } from "../ui/ConfirmDialog";
|
|
8
|
-
import { subscribePages, createPage, deletePage } from "../lib/pagesClient";
|
|
9
|
-
import { PageEditor } from "./PageEditor";
|
|
4
|
+
import { Button, Card, Field, Input, Badge } from "../ui/primitives.js";
|
|
5
|
+
import { Dialog, DialogTrigger, DialogContent, DialogClose } from "../ui/dialog.js";
|
|
6
|
+
import { useToast } from "../ui/toast.js";
|
|
7
|
+
import { useConfirm } from "../ui/ConfirmDialog.js";
|
|
8
|
+
import { subscribePages, createPage, deletePage } from "../lib/pagesClient.js";
|
|
9
|
+
import { PageEditor } from "./PageEditor.js";
|
|
10
10
|
function slugify(s) {
|
|
11
11
|
return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
12
12
|
}
|
|
@@ -37,8 +37,8 @@ function PagesPanel({ siteId }) {
|
|
|
37
37
|
}
|
|
38
38
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[260px_1fr]", children: [
|
|
39
39
|
/* @__PURE__ */ jsxs(Card, { className: "h-fit", children: [
|
|
40
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
|
|
41
|
-
/* @__PURE__ */ jsx("
|
|
40
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium tracking-wide text-slate-500 uppercase", children: "Overzicht" }),
|
|
42
42
|
/* @__PURE__ */ jsxs(Dialog, { children: [
|
|
43
43
|
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { size: "sm", children: "+ Nieuw" }) }),
|
|
44
44
|
/* @__PURE__ */ jsx(DialogContent, { title: "Nieuwe pagina", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
@@ -50,27 +50,25 @@ function PagesPanel({ siteId }) {
|
|
|
50
50
|
] }) })
|
|
51
51
|
] })
|
|
52
52
|
] }),
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
children:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
) }, p.id)),
|
|
65
|
-
pages.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-slate-500", children: "Nog geen pagina's." })
|
|
66
|
-
] })
|
|
53
|
+
/* @__PURE__ */ jsx("ul", { className: "grid gap-1", children: pages.map((p) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
54
|
+
"button",
|
|
55
|
+
{
|
|
56
|
+
onClick: () => setSelected(p.id),
|
|
57
|
+
className: `flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm ${p.id === selected ? "bg-brand-50 text-brand-700" : "hover:bg-slate-100"}`,
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: p.title }),
|
|
60
|
+
p.published ? /* @__PURE__ */ jsx(Badge, { tone: "green", children: "live" }) : /* @__PURE__ */ jsx(Badge, { tone: "amber", children: "concept" })
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
) }, p.id)) })
|
|
67
64
|
] }),
|
|
68
65
|
/* @__PURE__ */ jsx("div", { children: current ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
69
|
-
/* @__PURE__ */ jsx(
|
|
70
|
-
|
|
66
|
+
/* @__PURE__ */ jsx(PageEditor, { siteId, page: current, allPages: pages }),
|
|
67
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-end border-t border-slate-200 pt-4", children: /* @__PURE__ */ jsx(
|
|
68
|
+
"button",
|
|
71
69
|
{
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
type: "button",
|
|
71
|
+
className: "rounded px-2 py-1 text-sm text-red-600 hover:underline focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none",
|
|
74
72
|
onClick: async () => {
|
|
75
73
|
const ok = await confirm({
|
|
76
74
|
title: `Pagina "${current.title}" verwijderen?`,
|
|
@@ -83,11 +81,13 @@ function PagesPanel({ siteId }) {
|
|
|
83
81
|
setSelected(null);
|
|
84
82
|
toast({ title: "Pagina verwijderd" });
|
|
85
83
|
},
|
|
86
|
-
children: "
|
|
84
|
+
children: "Pagina verwijderen"
|
|
87
85
|
}
|
|
88
|
-
) })
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
) })
|
|
87
|
+
] }) : /* @__PURE__ */ jsx(Card, { className: "grid place-items-center py-16 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm", children: [
|
|
88
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-slate-700", children: pages.length === 0 ? "Nog geen pagina's" : "Geen pagina geselecteerd" }),
|
|
89
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-slate-500", children: pages.length === 0 ? "Maak je eerste pagina met + Nieuw. Wat je maakt blijft een concept tot je publiceert." : "Kies links een pagina om de tekst en secties aan te passen." })
|
|
90
|
+
] }) }) })
|
|
91
91
|
] });
|
|
92
92
|
}
|
|
93
93
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/pages/PagesPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Dialog, DialogTrigger, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { subscribePages, createPage, deletePage, type DraftPage } from \"../lib/pagesClient\";\nimport { PageEditor } from \"./PageEditor\";\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\nexport function PagesPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [newTitle, setNewTitle] = useState(\"\");\n\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n useEffect(() => {\n if (!selected && pages.length) setSelected(pages[0].id);\n }, [pages, selected]);\n\n const current = pages.find((p) => p.id === selected) ?? null;\n\n async function create() {\n const title = newTitle.trim();\n if (!title) return;\n const slug = slugify(title) || \"pagina\";\n const path = slug === \"home\" ? \"/\" : `/${slug}`;\n try {\n const id = await createPage(siteId, { title, slug, path });\n setNewTitle(\"\");\n setSelected(id);\n toast({ title: \"Pagina aangemaakt\", description: path });\n } catch (e) {\n // De dialoog sluit sowieso (DialogClose) — zonder deze toast dacht de klant dat de pagina\n // bestond terwijl de write (rules/offline) faalde.\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between\">\n <
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/pages/PagesPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Button, Card, Field, Input, Badge } from \"../ui/primitives\";\nimport { Dialog, DialogTrigger, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { subscribePages, createPage, deletePage, type DraftPage } from \"../lib/pagesClient\";\nimport { PageEditor } from \"./PageEditor\";\n\nfunction slugify(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/(^-|-$)/g, \"\");\n}\n\nexport function PagesPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [selected, setSelected] = useState<string | null>(null);\n const [newTitle, setNewTitle] = useState(\"\");\n\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n useEffect(() => {\n if (!selected && pages.length) setSelected(pages[0].id);\n }, [pages, selected]);\n\n const current = pages.find((p) => p.id === selected) ?? null;\n\n async function create() {\n const title = newTitle.trim();\n if (!title) return;\n const slug = slugify(title) || \"pagina\";\n const path = slug === \"home\" ? \"/\" : `/${slug}`;\n try {\n const id = await createPage(siteId, { title, slug, path });\n setNewTitle(\"\");\n setSelected(id);\n toast({ title: \"Pagina aangemaakt\", description: path });\n } catch (e) {\n // De dialoog sluit sowieso (DialogClose) — zonder deze toast dacht de klant dat de pagina\n // bestond terwijl de write (rules/offline) faalde.\n toast({ title: \"Pagina aanmaken mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n return (\n <div className=\"grid gap-4 md:grid-cols-[260px_1fr]\">\n <Card className=\"h-fit\">\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n <span className=\"text-xs font-medium tracking-wide text-slate-500 uppercase\">Overzicht</span>\n <Dialog>\n <DialogTrigger asChild>\n <Button size=\"sm\">+ Nieuw</Button>\n </DialogTrigger>\n <DialogContent title=\"Nieuwe pagina\">\n <div className=\"grid gap-3\">\n <Field label=\"Titel\">\n <Input value={newTitle} onChange={(e) => setNewTitle(e.target.value)} placeholder=\"bv. Over ons\" />\n </Field>\n <div className=\"flex justify-end gap-2\">\n <DialogClose asChild>\n <Button variant=\"secondary\">Annuleren</Button>\n </DialogClose>\n <DialogClose asChild>\n <Button onClick={create}>Aanmaken</Button>\n </DialogClose>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </div>\n <ul className=\"grid gap-1\">\n {pages.map((p) => (\n <li key={p.id}>\n <button\n onClick={() => setSelected(p.id)}\n className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-left text-sm ${\n p.id === selected ? \"bg-brand-50 text-brand-700\" : \"hover:bg-slate-100\"\n }`}\n >\n <span className=\"truncate\">{p.title}</span>\n {p.published ? <Badge tone=\"green\">live</Badge> : <Badge tone=\"amber\">concept</Badge>}\n </button>\n </li>\n ))}\n {/* Geen lege-lijst-tekst hier: de lege staat rechts zegt hetzelfde én noemt de volgende\n stap. Twee keer \"Nog geen pagina's\" naast elkaar leest als een fout. */}\n </ul>\n </Card>\n\n <div>\n {current ? (\n <>\n <PageEditor siteId={siteId} page={current} allPages={pages} />\n {/* Verwijderen staat ONDER de editor en draagt geen rode vlakknop meer. Het was het\n opvallendste element op het scherm — helderrood, bovenaan, bóven \"Publiceren\" —\n waarmee de onomkeerbare actie meer aandacht kreeg dan de actie waarvoor je komt.\n Een tekstknop in de foutkleur is duidelijk genoeg; de bevestiging vangt de rest. */}\n <div className=\"mt-4 flex justify-end border-t border-slate-200 pt-4\">\n <button\n type=\"button\"\n className=\"rounded px-2 py-1 text-sm text-red-600 hover:underline focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n onClick={async () => {\n const ok = await confirm({\n title: `Pagina \"${current.title}\" verwijderen?`,\n description:\n \"De pagina en de bijbehorende werkversie worden verwijderd. Als de pagina gepubliceerd is, verdwijnt hij ook van de live site. Dit kan niet ongedaan worden gemaakt.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n await deletePage(siteId, current.id);\n setSelected(null);\n toast({ title: \"Pagina verwijderd\" });\n }}\n >\n Pagina verwijderen\n </button>\n </div>\n </>\n ) : (\n <Card className=\"grid place-items-center py-16 text-center\">\n <div className=\"max-w-sm\">\n {/* Een lege lijst is een uitnodiging, geen mededeling: zeg wat de volgende stap is. */}\n <p className=\"text-sm font-medium text-slate-700\">\n {pages.length === 0 ? \"Nog geen pagina's\" : \"Geen pagina geselecteerd\"}\n </p>\n <p className=\"mt-1 text-sm text-slate-500\">\n {pages.length === 0\n ? \"Maak je eerste pagina met + Nieuw. Wat je maakt blijft een concept tot je publiceert.\"\n : \"Kies links een pagina om de tekst en secties aan te passen.\"}\n </p>\n </div>\n </Card>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { PagesPanel } from "./pages/PagesPanel";
|
|
4
|
-
import { MediaManager } from "./media/MediaManager";
|
|
5
|
-
import { FormsPanel } from "./forms/FormsPanel";
|
|
6
|
-
import { StatsPanel } from "./stats/StatsPanel";
|
|
7
|
-
import { SettingsPanel } from "./settings/SettingsPanel";
|
|
8
|
-
import { NewsEditor } from "./NewsEditor";
|
|
9
|
-
import { CustomBlocksPanel } from "./customBlocks/CustomBlocksPanel";
|
|
3
|
+
import { PagesPanel } from "./pages/PagesPanel.js";
|
|
4
|
+
import { MediaManager } from "./media/MediaManager.js";
|
|
5
|
+
import { FormsPanel } from "./forms/FormsPanel.js";
|
|
6
|
+
import { StatsPanel } from "./stats/StatsPanel.js";
|
|
7
|
+
import { SettingsPanel } from "./settings/SettingsPanel.js";
|
|
8
|
+
import { NewsEditor } from "./NewsEditor.js";
|
|
9
|
+
import { CustomBlocksPanel } from "./customBlocks/CustomBlocksPanel.js";
|
|
10
10
|
function NewsPanel(_) {
|
|
11
11
|
return /* @__PURE__ */ jsx(NewsEditor, {});
|
|
12
12
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Pattern } from "../../patterns";
|
|
1
|
+
import { type Pattern } from "../../patterns/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* "Sectie toevoegen" — kiest een kant-en-klaar patroon en voegt de blocks (met verse keys) in.
|
|
4
4
|
* Hergebruikt in de visuele editor én het /beheer-paginapaneel.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
|
-
import { Dialog, DialogContent, DialogClose } from "../ui/dialog";
|
|
5
|
-
import { Button } from "../ui/primitives";
|
|
6
|
-
import { useRegistry } from "../../blocks/context";
|
|
7
|
-
import { builtinPatterns, instantiatePattern, collectPatternTypes } from "../../patterns";
|
|
4
|
+
import { Dialog, DialogContent, DialogClose } from "../ui/dialog.js";
|
|
5
|
+
import { Button } from "../ui/primitives.js";
|
|
6
|
+
import { useRegistry } from "../../blocks/context.js";
|
|
7
|
+
import { builtinPatterns, instantiatePattern, collectPatternTypes } from "../../patterns/index.js";
|
|
8
8
|
function PatternInserter({
|
|
9
9
|
patterns = builtinPatterns,
|
|
10
10
|
onInsert,
|
|
@@ -24,7 +24,7 @@ function PatternInserter({
|
|
|
24
24
|
}, [patterns, registry]);
|
|
25
25
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
26
26
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setOpen(true), children: label }),
|
|
27
|
-
/* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(DialogContent, { title: "Sectie toevoegen",
|
|
27
|
+
/* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(DialogContent, { title: "Sectie toevoegen", width: "min(92vw, 36rem)", children: [
|
|
28
28
|
/* @__PURE__ */ jsx("p", { className: "mb-3 text-sm text-slate-500", children: "Kies een kant-en-klare sectie; je past de inhoud daarna aan." }),
|
|
29
29
|
/* @__PURE__ */ jsx("div", { className: "grid max-h-[60vh] gap-4 overflow-auto", children: byCategory.map(([cat, items]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
30
30
|
/* @__PURE__ */ jsx("h4", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-slate-400", children: cat }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/patterns/PatternInserter.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport { Dialog, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { Button } from \"../ui/primitives\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { builtinPatterns, instantiatePattern, collectPatternTypes, type Pattern } from \"../../patterns\";\n\n/**\n * \"Sectie toevoegen\" — kiest een kant-en-klaar patroon en voegt de blocks (met verse keys) in.\n * Hergebruikt in de visuele editor én het /beheer-paginapaneel.\n */\nexport function PatternInserter({\n patterns = builtinPatterns,\n onInsert,\n label = \"+ Sectie toevoegen\",\n}: {\n patterns?: Pattern[];\n onInsert: (blocks: Array<Record<string, unknown>>) => void;\n label?: string;\n}) {\n const [open, setOpen] = useState(false);\n const registry = useRegistry();\n const byCategory = useMemo(() => {\n // Verberg patronen waarvan een benodigd blok niet in de registry zit (module uitgeschakeld):\n // invoegen zou anders \"Onbekend blok\"-gaten geven.\n const available = patterns.filter((p) => collectPatternTypes(p).every((t) => registry.get(t)));\n const map = new Map<string, Pattern[]>();\n for (const p of available) {\n const arr = map.get(p.category) ?? [];\n arr.push(p);\n map.set(p.category, arr);\n }\n return [...map.entries()];\n }, [patterns, registry]);\n\n return (\n <>\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={() => setOpen(true)}>\n {label}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent title=\"Sectie toevoegen\"
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/patterns/PatternInserter.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport { Dialog, DialogContent, DialogClose } from \"../ui/dialog\";\nimport { Button } from \"../ui/primitives\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { builtinPatterns, instantiatePattern, collectPatternTypes, type Pattern } from \"../../patterns\";\n\n/**\n * \"Sectie toevoegen\" — kiest een kant-en-klaar patroon en voegt de blocks (met verse keys) in.\n * Hergebruikt in de visuele editor én het /beheer-paginapaneel.\n */\nexport function PatternInserter({\n patterns = builtinPatterns,\n onInsert,\n label = \"+ Sectie toevoegen\",\n}: {\n patterns?: Pattern[];\n onInsert: (blocks: Array<Record<string, unknown>>) => void;\n label?: string;\n}) {\n const [open, setOpen] = useState(false);\n const registry = useRegistry();\n const byCategory = useMemo(() => {\n // Verberg patronen waarvan een benodigd blok niet in de registry zit (module uitgeschakeld):\n // invoegen zou anders \"Onbekend blok\"-gaten geven.\n const available = patterns.filter((p) => collectPatternTypes(p).every((t) => registry.get(t)));\n const map = new Map<string, Pattern[]>();\n for (const p of available) {\n const arr = map.get(p.category) ?? [];\n arr.push(p);\n map.set(p.category, arr);\n }\n return [...map.entries()];\n }, [patterns, registry]);\n\n return (\n <>\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" onClick={() => setOpen(true)}>\n {label}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent title=\"Sectie toevoegen\" width=\"min(92vw, 36rem)\">\n <p className=\"mb-3 text-sm text-slate-500\">Kies een kant-en-klare sectie; je past de inhoud daarna aan.</p>\n <div className=\"grid max-h-[60vh] gap-4 overflow-auto\">\n {byCategory.map(([cat, items]) => (\n <div key={cat}>\n <h4 className=\"mb-1 text-xs font-semibold uppercase tracking-wide text-slate-400\">{cat}</h4>\n <div className=\"grid gap-2\">\n {items.map((p) => (\n <button\n key={p.id}\n type=\"button\"\n className=\"rounded-lg border border-slate-200 p-3 text-left text-sm hover:border-brand-500 hover:bg-slate-50\"\n onClick={() => {\n onInsert(instantiatePattern(p));\n setOpen(false);\n }}\n >\n <span className=\"flex items-center justify-between gap-2\">\n <span className=\"font-medium\">{p.label}</span>\n {/* Mini-preview: de iconen van de blokken die dit patroon invoegt. */}\n <span className=\"shrink-0 text-base opacity-70\" aria-hidden>\n {p.blocks.slice(0, 6).map((b, idx) => (\n <span key={idx}>{registry.get(b._type)?.icon ?? \"🧩\"}</span>\n ))}\n </span>\n </span>\n {p.description && <span className=\"block text-xs text-slate-500\">{p.description}</span>}\n </button>\n ))}\n </div>\n </div>\n ))}\n </div>\n <div className=\"mt-4 flex justify-end\">\n <DialogClose asChild>\n <Button variant=\"secondary\" size=\"sm\">Sluiten</Button>\n </DialogClose>\n </div>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n"],"mappings":";AAqCI,mBACE,KAqBgB,YAtBlB;AAnCJ,SAAS,SAAS,gBAAgB;AAClC,SAAS,QAAQ,eAAe,mBAAmB;AACnD,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB,oBAAoB,2BAAyC;AAMhF,SAAS,gBAAgB;AAAA,EAC9B,WAAW;AAAA,EACX;AAAA,EACA,QAAQ;AACV,GAIG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAa,QAAQ,MAAM;AAG/B,UAAM,YAAY,SAAS,OAAO,CAAC,MAAM,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC;AAC7F,UAAM,MAAM,oBAAI,IAAuB;AACvC,eAAW,KAAK,WAAW;AACzB,YAAM,MAAM,IAAI,IAAI,EAAE,QAAQ,KAAK,CAAC;AACpC,UAAI,KAAK,CAAC;AACV,UAAI,IAAI,EAAE,UAAU,GAAG;AAAA,IACzB;AACA,WAAO,CAAC,GAAG,IAAI,QAAQ,CAAC;AAAA,EAC1B,GAAG,CAAC,UAAU,QAAQ,CAAC;AAEvB,SACE,iCACE;AAAA,wBAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,MAAK,MAAK,SAAS,MAAM,QAAQ,IAAI,GAC5E,iBACH;AAAA,IACA,oBAAC,UAAO,MAAY,cAAc,SAChC,+BAAC,iBAAc,OAAM,oBAAmB,OAAM,oBAC5C;AAAA,0BAAC,OAAE,WAAU,+BAA8B,0EAA4D;AAAA,MACvG,oBAAC,SAAI,WAAU,yCACZ,qBAAW,IAAI,CAAC,CAAC,KAAK,KAAK,MAC1B,qBAAC,SACC;AAAA,4BAAC,QAAG,WAAU,qEAAqE,eAAI;AAAA,QACvF,oBAAC,SAAI,WAAU,cACZ,gBAAM,IAAI,CAAC,MACV;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,SAAS,MAAM;AACb,uBAAS,mBAAmB,CAAC,CAAC;AAC9B,sBAAQ,KAAK;AAAA,YACf;AAAA,YAEA;AAAA,mCAAC,UAAK,WAAU,2CACd;AAAA,oCAAC,UAAK,WAAU,eAAe,YAAE,OAAM;AAAA,gBAEvC,oBAAC,UAAK,WAAU,iCAAgC,eAAW,MACxD,YAAE,OAAO,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,QAC5B,oBAAC,UAAgB,mBAAS,IAAI,EAAE,KAAK,GAAG,QAAQ,eAArC,GAA0C,CACtD,GACH;AAAA,iBACF;AAAA,cACC,EAAE,eAAe,oBAAC,UAAK,WAAU,gCAAgC,YAAE,aAAY;AAAA;AAAA;AAAA,UAjB3E,EAAE;AAAA,QAkBT,CACD,GACH;AAAA,WAzBQ,GA0BV,CACD,GACH;AAAA,MACA,oBAAC,SAAI,WAAU,yBACb,8BAAC,eAAY,SAAO,MAClB,8BAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,qBAAO,GAC/C,GACF;AAAA,OACF,GACF;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useState } from "react";
|
|
4
4
|
import { getFunctions, httpsCallable } from "firebase/functions";
|
|
5
|
-
import { getFirebaseApp } from "../../firebase/client";
|
|
6
|
-
import { functionsRegion } from "../../firebase/region";
|
|
7
|
-
import { Badge, Button, Card, Field, Input } from "../ui/primitives";
|
|
8
|
-
import { Switch } from "../ui/switch";
|
|
9
|
-
import { useToast } from "../ui/toast";
|
|
5
|
+
import { getFirebaseApp } from "../../firebase/client.js";
|
|
6
|
+
import { functionsRegion } from "../../firebase/region.js";
|
|
7
|
+
import { Badge, Button, Card, Field, Input } from "../ui/primitives.js";
|
|
8
|
+
import { Switch } from "../ui/switch.js";
|
|
9
|
+
import { useToast } from "../ui/toast.js";
|
|
10
10
|
function callFunction(name) {
|
|
11
11
|
return async (data) => {
|
|
12
12
|
const app = getFirebaseApp();
|
|
@@ -3,11 +3,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
4
|
import { doc, getDoc, setDoc } from "firebase/firestore";
|
|
5
5
|
import { getFunctions, httpsCallable } from "firebase/functions";
|
|
6
|
-
import { getDb, getFirebaseApp } from "../../firebase/client";
|
|
7
|
-
import { functionsRegion } from "../../firebase/region";
|
|
8
|
-
import { MAIL_TEMPLATE_DEFS } from "../../schema/mail";
|
|
9
|
-
import { Button, Card, Field, Input, Textarea } from "../ui/primitives";
|
|
10
|
-
import { useToast } from "../ui/toast";
|
|
6
|
+
import { getDb, getFirebaseApp } from "../../firebase/client.js";
|
|
7
|
+
import { functionsRegion } from "../../firebase/region.js";
|
|
8
|
+
import { MAIL_TEMPLATE_DEFS } from "../../schema/mail.js";
|
|
9
|
+
import { Button, Card, Field, Input, Textarea } from "../ui/primitives.js";
|
|
10
|
+
import { useToast } from "../ui/toast.js";
|
|
11
11
|
function callFunction(name) {
|
|
12
12
|
return async (data) => {
|
|
13
13
|
const app = getFirebaseApp();
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { doc, onSnapshot
|
|
4
|
+
import { doc, onSnapshot } from "firebase/firestore";
|
|
5
5
|
import { getFunctions, httpsCallable } from "firebase/functions";
|
|
6
|
-
import { getDb, getFirebaseApp } from "../../firebase/client";
|
|
7
|
-
import { functionsRegion } from "../../firebase/region";
|
|
8
|
-
import { SiteSettingsSchema
|
|
9
|
-
import { useModules } from "../../modules/context";
|
|
10
|
-
import { builtinThemePresets } from "../../theme/presets";
|
|
11
|
-
import { Button, Card, Field, Input } from "../ui/primitives";
|
|
12
|
-
import { Select } from "../ui/select";
|
|
13
|
-
import { Switch } from "../ui/switch";
|
|
14
|
-
import { ArrayEditor } from "../ui/ArrayEditor";
|
|
15
|
-
import { useToast } from "../ui/toast";
|
|
16
|
-
import { useConfirm } from "../ui/ConfirmDialog";
|
|
17
|
-
import { ModuleSettingsForm } from "./moduleSettingsForm";
|
|
18
|
-
import { SnapshotsCard } from "./SnapshotsCard";
|
|
19
|
-
import { MailSettings } from "./MailSettings";
|
|
6
|
+
import { getDb, getFirebaseApp } from "../../firebase/client.js";
|
|
7
|
+
import { functionsRegion } from "../../firebase/region.js";
|
|
8
|
+
import { SiteSettingsSchema } from "../../schema/settings.js";
|
|
9
|
+
import { useModules } from "../../modules/context.js";
|
|
10
|
+
import { builtinThemePresets } from "../../theme/presets.js";
|
|
11
|
+
import { Button, Card, Field, Input } from "../ui/primitives.js";
|
|
12
|
+
import { Select } from "../ui/select.js";
|
|
13
|
+
import { Switch } from "../ui/switch.js";
|
|
14
|
+
import { ArrayEditor } from "../ui/ArrayEditor.js";
|
|
15
|
+
import { useToast } from "../ui/toast.js";
|
|
16
|
+
import { useConfirm } from "../ui/ConfirmDialog.js";
|
|
17
|
+
import { ModuleSettingsForm } from "./moduleSettingsForm.js";
|
|
18
|
+
import { SnapshotsCard } from "./SnapshotsCard.js";
|
|
19
|
+
import { MailSettings } from "./MailSettings.js";
|
|
20
20
|
function SettingsPanel({ siteId }) {
|
|
21
21
|
const { toast } = useToast();
|
|
22
22
|
const confirm = useConfirm();
|
|
@@ -117,11 +117,11 @@ function SettingsPanel({ siteId }) {
|
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
try {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
await
|
|
124
|
-
toast({ title: "Opgeslagen", description: "Wijzigingen zijn
|
|
120
|
+
const app = getFirebaseApp();
|
|
121
|
+
if (!app) throw new Error("Firebase is niet geconfigureerd.");
|
|
122
|
+
const fn = httpsCallable(getFunctions(app, functionsRegion()), "saveSiteSettings");
|
|
123
|
+
const res = await fn({ siteId, settings: parsed.data });
|
|
124
|
+
toast({ title: "Opgeslagen", description: res.data?.message ?? "Wijzigingen zijn live.", tone: "success" });
|
|
125
125
|
} catch (e) {
|
|
126
126
|
toast({ title: "Opslaan mislukt", description: e.message, tone: "error" });
|
|
127
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot, writeBatch } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { SiteSettingsSchema, toPublicSettings, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = httpsCallable<{ siteId: string }, Record<string, unknown>>(\n getFunctions(app, functionsRegion()),\n \"exportSite\",\n );\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n if (snap.exists()) setS(snap.data() as SiteSettings);\n setLoaded(true);\n });\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Volledige (rol-only) settings + de publiek-veilige subset die de live site leest —\n // atomair in één batch, zodat `site` en `public` nooit uit sync kunnen raken.\n try {\n const batch = writeBatch(db);\n batch.set(doc(db, \"sites\", siteId, \"settings\", \"site\"), parsed.data, { merge: true });\n batch.set(doc(db, \"sites\", siteId, \"settings\", \"public\"), toPublicSettings(parsed.data), { merge: true });\n await batch.commit();\n toast({ title: \"Opgeslagen\", description: \"Wijzigingen zijn direct live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n <Field label=\"Logo-URL (e-mail & factuur)\" hint=\"Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n\n {/* Site-brede mailprovider + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up & herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n\n <div className=\"flex justify-end\">\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/settings/SettingsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { doc, onSnapshot } from \"firebase/firestore\";\nimport { getFunctions, httpsCallable } from \"firebase/functions\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { functionsRegion } from \"../../firebase/region\";\nimport { SiteSettingsSchema, type SiteSettings } from \"../../schema/settings\";\nimport { useModules } from \"../../modules/context\";\nimport type { ModulesSiteConfig } from \"../../modules/types\";\nimport { builtinThemePresets } from \"../../theme/presets\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { ModuleSettingsForm } from \"./moduleSettingsForm\";\nimport { SnapshotsCard } from \"./SnapshotsCard\";\nimport { MailSettings } from \"./MailSettings\";\n\nexport function SettingsPanel({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const { defs } = useModules();\n const [s, setS] = useState<SiteSettings>({});\n const [loaded, setLoaded] = useState(false);\n const [exporting, setExporting] = useState(false);\n const [restoring, setRestoring] = useState(false);\n const restoreInput = useRef<HTMLInputElement>(null);\n\n async function downloadBackup(silent = false): Promise<boolean> {\n const app = getFirebaseApp();\n if (!app) return false;\n setExporting(true);\n try {\n const fn = httpsCallable<{ siteId: string }, Record<string, unknown>>(\n getFunctions(app, functionsRegion()),\n \"exportSite\",\n );\n const res = await fn({ siteId });\n const blob = new Blob([JSON.stringify(res.data, null, 2)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `xicms-backup-${siteId}-${new Date().toISOString().slice(0, 10)}.json`;\n a.click();\n URL.revokeObjectURL(url);\n if (!silent) toast({ title: \"Back-up gedownload\", tone: \"success\" });\n return true;\n } catch (e) {\n toast({ title: \"Back-up mislukt\", description: (e as Error).message, tone: \"error\" });\n return false;\n } finally {\n setExporting(false);\n }\n }\n\n async function restoreFromFile(file: File) {\n let backup: Record<string, unknown>;\n try {\n backup = JSON.parse(await file.text()) as Record<string, unknown>;\n } catch {\n toast({ title: \"Ongeldig bestand\", description: \"Dit is geen geldig back-upbestand.\", tone: \"error\" });\n return;\n }\n if (backup._format !== \"xicms-export\") {\n toast({ title: \"Ongeldig bestand\", description: \"Dit lijkt geen XiCMS-back-up.\", tone: \"error\" });\n return;\n }\n if (backup.siteId && backup.siteId !== siteId) {\n const ok = await confirm({\n title: \"Back-up van een andere site\",\n description: `Deze back-up is van “${String(backup.siteId)}”, niet van deze site (${siteId}). Toch terugzetten?`,\n confirmLabel: \"Toch terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n }\n const ok = await confirm({\n title: \"Back-up terugzetten?\",\n description: `Dit VERVANGT alle huidige pagina's, nieuws, media en instellingen door de inhoud van de back-up${backup.exportedAt ? ` van ${new Date(String(backup.exportedAt)).toLocaleString(\"nl-NL\")}` : \"\"}. We maken eerst automatisch een back-up van de huidige stand.`,\n confirmLabel: \"Terugzetten\",\n tone: \"danger\",\n });\n if (!ok) return;\n setRestoring(true);\n try {\n // Veiligheid: eerst de huidige stand wegschrijven (als download) vóór we overschrijven.\n // Lukt die back-up niet, dan NIET doorgaan met de destructieve restore (anders ben je de\n // huidige stand kwijt zonder vangnet). De gebruiker kan het daarna opnieuw proberen.\n if (!(await downloadBackup(true))) {\n toast({ title: \"Terugzetten afgebroken\", description: \"De veiligheidsback-up van de huidige stand mislukte; er is niets overschreven.\", tone: \"error\" });\n return;\n }\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"restoreSite\");\n await fn({ siteId, backup });\n toast({ title: \"Back-up teruggezet\", description: \"Herlaad de pagina om alles bij te werken.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Terugzetten mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setRestoring(false);\n }\n }\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoaded(true);\n return;\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"settings\", \"site\"), (snap) => {\n if (snap.exists()) setS(snap.data() as SiteSettings);\n setLoaded(true);\n });\n }, [siteId]);\n\n async function save() {\n const db = getDb();\n if (!db) return;\n const parsed = SiteSettingsSchema.safeParse(s);\n if (!parsed.success) {\n toast({ title: \"Ongeldige instellingen\", description: parsed.error.issues[0]?.message, tone: \"error\" });\n return;\n }\n // Via de callable i.p.v. rechtstreeks: die valideert server-side, leidt de publieke subset\n // zelf af (zodat `site` en `public` niet uit elkaar kunnen lopen) én duwt de cache-tag door,\n // waardoor de wijziging binnen seconden live staat i.p.v. na het ISR-vangnet.\n try {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase is niet geconfigureerd.\");\n const fn = httpsCallable(getFunctions(app, functionsRegion()), \"saveSiteSettings\");\n const res = await fn({ siteId, settings: parsed.data });\n toast({ title: \"Opgeslagen\", description: (res.data as { message?: string })?.message ?? \"Wijzigingen zijn live.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n if (!loaded) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n const forms = s.forms ?? { mode: \"xicms\" as const };\n const theme = s.theme ?? { primary: \"#4f46e5\" };\n const footer = s.footer ?? {};\n\n // Hulpfuncties voor de module-instellingen.\n const moduleState = (id: string, defaultEnabled: boolean) => {\n const raw = (s.modules as ModulesSiteConfig | undefined)?.[id];\n if (typeof raw === \"boolean\") return { enabled: raw, config: {} as Record<string, unknown> };\n return { enabled: raw?.enabled ?? defaultEnabled, config: raw?.config ?? {} };\n };\n const setModule = (id: string, next: { enabled: boolean; config: Record<string, unknown> }) =>\n setS({ ...s, modules: { ...(s.modules as ModulesSiteConfig | undefined), [id]: next } });\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Algemeen</h3>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Sitenaam\"><Input value={s.name ?? \"\"} onChange={(e) => setS({ ...s, name: e.target.value })} /></Field>\n <Field label=\"Domein\"><Input value={s.domain ?? \"\"} onChange={(e) => setS({ ...s, domain: e.target.value })} /></Field>\n <Field label=\"Contact-e-mail\"><Input value={s.contactEmail ?? \"\"} onChange={(e) => setS({ ...s, contactEmail: e.target.value })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Huisstijl</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Thema-preset\" hint=\"Vult kleuren, lettertype en afronding in één keer in.\">\n <Select\n value=\"\"\n placeholder=\"Kies een preset…\"\n onValueChange={(id) => {\n const preset = builtinThemePresets.find((p) => p.id === id);\n if (preset) setS({ ...s, theme: { ...theme, ...preset.theme } });\n }}\n options={builtinThemePresets.map((p) => ({ value: p.id, label: p.label }))}\n />\n </Field>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Primaire kleur\"><Input value={theme.primary} onChange={(e) => setS({ ...s, theme: { ...theme, primary: e.target.value } })} /></Field>\n <Field label=\"Accentkleur\"><Input value={theme.accent ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, accent: e.target.value } })} /></Field>\n <Field label=\"Achtergrondkleur\"><Input value={theme.background ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, background: e.target.value } })} placeholder=\"#ffffff\" /></Field>\n <Field label=\"Tekstkleur\"><Input value={theme.foreground ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, foreground: e.target.value } })} placeholder=\"#0f172a\" /></Field>\n </div>\n <Field label=\"Logo-URL (e-mail & factuur)\" hint=\"Absolute https-URL naar een PNG/JPG. Verschijnt in de mail-header en op de factuur-PDF.\">\n <Input value={theme.logo ?? \"\"} onChange={(e) => setS({ ...s, theme: { ...theme, logo: e.target.value } })} placeholder=\"https://…/logo.png\" />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">SEO (standaard)</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Standaard-omschrijving\">\n <Input value={s.seoDefaults?.description ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, description: e.target.value } })} />\n </Field>\n <Field label=\"OG-afbeelding (URL)\">\n <Input value={s.seoDefaults?.ogImage ?? \"\"} onChange={(e) => setS({ ...s, seoDefaults: { ...s.seoDefaults, ogImage: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Footer</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Kolommen met links, social-links en copyright. Wijzigingen zijn direct live.</p>\n <div className=\"grid gap-4\">\n <div>\n <span className=\"text-sm font-medium\">Kolommen</span>\n <ArrayEditor\n items={footer.columns ?? []}\n onChange={(columns) => setS({ ...s, footer: { ...footer, columns } })}\n newItem={() => ({ heading: \"\", links: [] })}\n addLabel=\"+ Kolom\"\n emptyLabel=\"Nog geen kolommen.\"\n renderRow={(col, update) => (\n <div className=\"grid gap-2\">\n <Field label=\"Kop\"><Input value={col.heading ?? \"\"} onChange={(e) => update({ heading: e.target.value })} /></Field>\n <ArrayEditor\n items={col.links ?? []}\n onChange={(links) => update({ links })}\n newItem={() => ({ label: \"\", href: \"\" })}\n addLabel=\"+ Link\"\n renderRow={(link, updL) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label} placeholder=\"Label\" onChange={(e) => updL({ label: e.target.value })} />\n <Input value={link.href} placeholder=\"/pad of https://…\" onChange={(e) => updL({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n )}\n />\n </div>\n <div>\n <span className=\"text-sm font-medium\">Social-links</span>\n <ArrayEditor\n items={footer.social ?? []}\n onChange={(social) => setS({ ...s, footer: { ...footer, social } })}\n newItem={() => ({ platform: \"\", href: \"\" })}\n addLabel=\"+ Social-link\"\n renderRow={(link, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.platform} placeholder=\"bv. Instagram\" onChange={(e) => update({ platform: e.target.value })} />\n <Input value={link.href} placeholder=\"https://…\" onChange={(e) => update({ href: e.target.value })} />\n </div>\n )}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Copyright (leeg = automatisch)\">\n <Input value={footer.copyright ?? \"\"} onChange={(e) => setS({ ...s, footer: { ...footer, copyright: e.target.value } })} />\n </Field>\n <label className=\"flex items-center gap-2 self-end text-sm\">\n <Switch checked={footer.showAdminLink !== false} onCheckedChange={(v) => setS({ ...s, footer: { ...footer, showAdminLink: v } })} />\n “Beheer”-link tonen\n </label>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Modules</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zet functies aan/uit en stel ze in. Uitgezette modules verbergen hun blokken en beheer-paneel.</p>\n <div className=\"grid gap-3\">\n {defs\n .filter((d) => !d.core)\n .map((d) => {\n const st = moduleState(d.id, d.defaultEnabled ?? true);\n return (\n <div key={d.id} className=\"rounded-lg border border-slate-200 p-3\">\n <label className=\"flex items-center justify-between gap-2\">\n <span>\n <strong className=\"text-sm\">{d.label}</strong>\n {d.description && <span className=\"block text-xs text-slate-500\">{d.description}</span>}\n </span>\n <Switch checked={st.enabled} onCheckedChange={(v) => setModule(d.id, { enabled: v, config: st.config })} />\n </label>\n {st.enabled && d.settingsSchema && (\n <div className=\"mt-3\">\n <ModuleSettingsForm\n schema={d.settingsSchema}\n value={st.config}\n onChange={(config) => setModule(d.id, { enabled: st.enabled, config })}\n />\n </div>\n )}\n </div>\n );\n })}\n </div>\n </Card>\n\n {/* Site-brede mailprovider + opmaak (formulieren én shop). Zie docs/SHARED-SETTINGS.md. */}\n <MailSettings siteId={siteId} />\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">E-mailteksten</h3>\n <p className=\"text-sm text-slate-600\">\n De teksten van automatische e-mails bewerk je bij het onderdeel zelf: formulier-mails onder{\" \"}\n <strong>Formulieren</strong>, webwinkel-mails onder <strong>Webwinkel → E-mails</strong>. Provider en\n opmaak (logo, huisstijlkleur) gelden site-breed en stel je hier in.\n </p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Formulier-afzender</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n De afzender-identiteit die formulier-notificaties gebruiken. Kies de XiCMS-afzender\n (werkt direct) of het eigen domein van de klant (vereist domeinverificatie). De provider\n waarmee daadwerkelijk verstuurd wordt, stel je hierboven bij <strong>E-mail</strong> in.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzender-modus\">\n <Select\n value={forms.mode}\n onValueChange={(v) => setS({ ...s, forms: { ...forms, mode: v as \"xicms\" | \"client\" } })}\n options={[\n { value: \"xicms\", label: \"XiCMS-domein (aanbevolen)\" },\n { value: \"client\", label: \"Eigen domein van de klant\" },\n ]}\n />\n </Field>\n <Field label=\"Van (from) e-mail\" hint=\"Moet een geverifieerd domein zijn.\">\n <Input value={forms.fromEmail ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromEmail: e.target.value } })} />\n </Field>\n <Field label=\"Van (naam)\"><Input value={forms.fromName ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, fromName: e.target.value } })} /></Field>\n <Field label=\"Notificaties naar\" hint=\"Meestal het mailadres van de klant.\">\n <Input value={forms.notifyTo ?? \"\"} onChange={(e) => setS({ ...s, forms: { ...forms, notifyTo: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Back-up & herstel</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Sla alle content van deze site (pagina's, nieuws, formulieren, instellingen en je foto-galerij)\n op in één bestand. Bewaar dat veilig; je kunt het later weer terugzetten. De foto-bestanden\n zelf blijven in de cloud staan — de back-up bevat de teksten, instellingen en verwijzingen.\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"secondary\" onClick={() => void downloadBackup()} disabled={exporting || restoring}>\n {exporting ? \"Bezig…\" : \"Back-up downloaden\"}\n </Button>\n <Button variant=\"secondary\" onClick={() => restoreInput.current?.click()} disabled={restoring || exporting}>\n {restoring ? \"Terugzetten…\" : \"Back-up terugzetten…\"}\n </Button>\n <input\n ref={restoreInput}\n type=\"file\"\n accept=\"application/json,.json\"\n hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void restoreFromFile(f);\n e.target.value = \"\";\n }}\n />\n </div>\n <p className=\"mt-2 text-xs text-slate-400\">\n Terugzetten vervangt de huidige content door de back-up. We maken automatisch eerst een\n back-up van de huidige stand, zodat je nooit iets kwijtraakt.\n </p>\n </Card>\n\n <SnapshotsCard siteId={siteId} />\n\n <div className=\"flex justify-end\">\n <Button onClick={save}>Instellingen opslaan</Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["ok"]}
|