@xiwebdesign/xicms-core 8.15.0 → 8.16.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/XiCMSProvider.js +1 -0
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/lib/veldTaal.js +1 -0
- package/dist/admin/lib/veldTaal.js.map +1 -1
- package/dist/admin/settings/BedrijfInstellingen.js +17 -2
- package/dist/admin/settings/BedrijfInstellingen.js.map +1 -1
- package/dist/admin/shop/ShopSettingsTab.js +29 -16
- package/dist/admin/shop/ShopSettingsTab.js.map +1 -1
- package/dist/auth/AuthProvider.js +1 -0
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/bedrijf/bedrijf.d.ts +8 -0
- package/dist/bedrijf/bedrijf.js +16 -1
- package/dist/bedrijf/bedrijf.js.map +1 -1
- package/dist/blocks/builtins/ContactRender.js +8 -0
- package/dist/blocks/builtins/ContactRender.js.map +1 -1
- package/dist/blocks/builtins/WhatsappChatRender.js +2 -0
- package/dist/blocks/builtins/WhatsappChatRender.js.map +1 -1
- package/dist/blocks/builtins/consentEmbed.js +26 -9
- package/dist/blocks/builtins/consentEmbed.js.map +1 -1
- package/dist/components/onderOffset.d.ts +13 -0
- package/dist/components/onderOffset.js +74 -0
- package/dist/components/onderOffset.js.map +1 -0
- package/dist/components/site/Bedrijfsnummers.d.ts +9 -0
- package/dist/components/site/Bedrijfsnummers.js +12 -0
- package/dist/components/site/Bedrijfsnummers.js.map +1 -0
- package/dist/components/site/SiteFooter.js +2 -1
- package/dist/components/site/SiteFooter.js.map +1 -1
- package/dist/firebase/functionsClient.js +5 -1
- package/dist/firebase/functionsClient.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/builtins.js +6 -6
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/consent/ConsentBanner.d.ts +5 -0
- package/dist/modules/consent/ConsentBanner.js +8 -7
- package/dist/modules/consent/ConsentBanner.js.map +1 -1
- package/dist/modules/consent/standaard.d.ts +12 -0
- package/dist/modules/consent/standaard.js +10 -0
- package/dist/modules/consent/standaard.js.map +1 -0
- package/dist/modules/consent/useConsent.d.ts +4 -0
- package/dist/modules/consent/useConsent.js +30 -1
- package/dist/modules/consent/useConsent.js.map +1 -1
- package/dist/releaseNotes.js +8 -0
- package/dist/releaseNotes.js.map +1 -1
- package/dist/schema/blocks.d.ts +3 -0
- package/dist/schema/blocks.js +3 -1
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/business.d.ts +1 -0
- package/dist/schema/business.js +2 -0
- package/dist/schema/business.js.map +1 -1
- package/dist/schema/settings.d.ts +2 -0
- package/dist/schema/siteConfig.d.ts +1 -0
- package/dist/styles.css +32 -4
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/XiCMSProvider.js
CHANGED
|
@@ -40,6 +40,7 @@ function warnOnce(message) {
|
|
|
40
40
|
function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }) {
|
|
41
41
|
useEffect(() => {
|
|
42
42
|
if (!process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY) return;
|
|
43
|
+
if (!/^\/(beheer|login)(\/|$)/.test(window.location.pathname)) return;
|
|
43
44
|
void initAppCheck();
|
|
44
45
|
}, []);
|
|
45
46
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nimport { canEdit } from \"./auth/roles\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\nimport { CustomBlockSchema, type CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nimport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nimport { resolveModules } from \"./modules/registry\";\nimport { ModulesProvider } from \"./modules/context\";\nimport { applyBlockOverrides, type BlockOverrides } from \"./modules/overrides\";\nimport { builtinModules } from \"./modules/builtins\";\nimport type { ModuleDefinition, ModulesSiteConfig } from \"./modules/types\";\nimport { EditorProvider } from \"./editor/EditorProvider\";\n// Puur en dependency-vrij; hier alleen nog geherexporteerd zodat de publieke API gelijk blijft.\nimport { isDarkColor, tekstOpKleur } from \"./theme/color\";\nexport { isDarkColor };\nimport { SiteSettingsProvider, useSiteSettings, useSiteSettingsLiveSync } from \"./settings/useSiteSettings\";\nimport { mergeThemeTokens, type ThemeTokens, type PublicSiteSettings } from \"./schema/settings\";\nimport { initAppCheck } from \"./firebase/appcheck\";\nimport { setDemoActive } from \"./demo/demoStore\";\nimport { fontStylesheet, isGoogleFontUrl } from \"./theme/fonts\";\nimport { fallbackFontFaces, metFallback } from \"./theme/fontFallback\";\nimport { preconnect } from \"react-dom\";\nimport { Theme, type DefinedTheme } from \"@astryxdesign/core/theme\";\nimport { neutralTheme } from \"@astryxdesign/theme-neutral/built\";\nimport { LinkProvider, type LinkComponentType } from \"@astryxdesign/core/Link\";\nimport { InternationalizationProvider } from \"@astryxdesign/core/i18n\";\nimport nlNL from \"@astryxdesign/core/locales/nl-NL.generated.js\";\nimport { ImageProvider, type ImageComponentType } from \"./media/ImageContext\";\nimport { validateSiteConfig, type SiteConfig } from \"./schema/siteConfig\";\nimport { validateModuleContract } from \"./modules/contract\";\n\ninterface ProviderProps {\n config: SiteConfig;\n /** Losse klant-blokken (via `defineBlock`) buiten een module om. Altijd actief. */\n blocks?: BlockDefinition[];\n /**\n * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)\n * en respecteren `config.modules` voor activatie.\n */\n modules?: ModuleDefinition[];\n /**\n * Per-site presentatie-overrides per block-type (`{ gallery: { Render: MijnGalerij } }`). Vervangt\n * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.\n */\n overrides?: BlockOverrides;\n /**\n * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`\n * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze\n * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).\n */\n customBlocksData?: CustomBlockData[];\n /**\n * SSR-startset van de publieke site-instellingen: geef het resultaat van\n * `getPublicSiteSettings(siteId)` (server) door zodat klant-thema/nav/footer al in de\n * server-HTML zitten (geen theme-flits bij eerste paint) én publiek geen client-read meer\n * doet — de ISR-gecachte layout ververst de settings per revalidate. Zonder deze prop laadt\n * de provider ze alsnog client-side (werkend, maar met mogelijke flits + 1 read per bezoeker).\n */\n settingsData?: PublicSiteSettings;\n /**\n * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.\n * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen\n * `defineTheme(...)` doorgeven voor component-overrides — zónder core te forken.\n */\n astryxTheme?: DefinedTheme;\n /**\n * Framework-link-component (bv. Next `Link`) voor soepele client-navigatie + prefetch. Astryx-\n * componenten met een `href` (knoppen/links/tabs) gebruiken 'm dan i.p.v. een rauwe `<a>` (= volledige\n * page-reload). Core importeert `next/link` niet zelf (optionele peer) — geef 'm hier door vanuit de site.\n */\n linkComponent?: LinkComponentType;\n /**\n * Framework-beeldcomponent (bv. Next `Image`) voor automatische beeldoptimalisatie (responsive\n * srcset, AVIF/WebP, geen layout-shift). Core importeert `next/image` niet zelf — geef 'm hier door.\n */\n imageComponent?: ImageComponentType;\n children: ReactNode;\n}\n\n// Stabiele lege defaults: een `= []`-defaultparameter maakt bij ELKE render een nieuwe\n// array-referentie en laat memo's/effects met `modules`/`blocks` in de deps onnodig hervuren.\nconst NO_BLOCKS: BlockDefinition[] = [];\nconst NO_MODULES: ModuleDefinition[] = [];\n\n// Dev-waarschuwingen één keer per boodschap (React strict-mode dubbelt effects; zonder dedupe\n// verschijnt dezelfde config-waarschuwing telkens opnieuw in de console).\nconst warned = new Set<string>();\nfunction warnOnce(message: string) {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(message);\n}\n\n/**\n * Wikkel de hele site in deze provider (in `app/layout.tsx`). Hij zet:\n * - de SiteConfig-context (siteId, theme, mode) + live publieke settings (footer/nav/modules);\n * - auth + content (Firebase, env-gedreven; werkt ook zonder Firebase);\n * - de block-registry (ingebouwde + klant-blokken) voor de page-renderer;\n * - de themavariabelen als CSS custom properties;\n * - App Check (alleen als geconfigureerd).\n *\n * De ZICHTBARE site-chrome (bewerkbalk, cookiebanner, demo) staat in `<SiteChrome />` en hoort in\n * de layout van je publieke routes — niet hier, anders verschijnt hij ook over /beheer.\n */\nexport function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }: ProviderProps) {\n useEffect(() => {\n // App Check hoort bij het praten mét Firebase; een bezoeker doet dat niet. Alleen aanroepen\n // als er een sleutel is, en de module zelf laadt zijn SDK dynamisch.\n if (!process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY) return;\n void initAppCheck();\n }, []);\n\n // Dev-vangnet: ontbrekende SSR-seeds. Zonder `settingsData` haalt élke bezoeker de publieke\n // settings zelf uit Firestore — één read per bezoeker, én het downloaden van de Firebase-SDK\n // (~680 KB) die anders nooit nodig is. Dat faalt volkomen stil: de site ziet er goed uit.\n // De referentiesite groenhof liep hier maandenlang op, dus: melden zodra het misgaat.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (!process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID) return; // geen Firebase → geen client-read\n if (settingsData === undefined) {\n warnOnce(\n \"[xicms] `settingsData` ontbreekt op XiCMSProvider. Elke bezoeker doet daardoor zélf een \" +\n \"Firestore-read voor de site-instellingen en downloadt de Firebase-SDK. Lees ze server-side \" +\n \"in je layout met `getPublicSiteSettings(siteId)` en geef ze door — zie template/app/layout.tsx.\",\n );\n }\n if (customBlocksData === undefined) {\n warnOnce(\n \"[xicms] `customBlocksData` ontbreekt op XiCMSProvider. Custom blocks worden dan pas na \" +\n \"hydratie zichtbaar (SEO-gat + flits). Lees ze server-side met `getSiteCustomBlocks(siteId)`.\",\n );\n }\n }, [settingsData, customBlocksData]);\n\n // Dev-vangnet voor (agent-)geschreven configs: meld config- en module-contractfouten meteen in\n // de console i.p.v. een subtiel kapotte site. In productie geen werk — en er wordt nooit gegooid.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const configIssues = validateSiteConfig(config);\n if (configIssues.length > 0) {\n warnOnce(`[xicms] site.config.ts bevat problemen:\\n- ${configIssues.join(\"\\n- \")}`);\n }\n if (modules.length > 0) {\n const contractIssues = validateModuleContract(modules);\n if (contractIssues.length > 0) {\n warnOnce(`[xicms] klant-modules voldoen niet aan het module-contract:\\n- ${contractIssues.join(\"\\n- \")}`);\n }\n }\n }, [config, modules]);\n\n return (\n <ConfigProvider config={config}>\n <SiteSettingsProvider siteId={config.siteId} initial={settingsData}>\n <ModuleLayer config={config} blocks={blocks} modules={modules} overrides={overrides} astryxTheme={astryxTheme} linkComponent={linkComponent} imageComponent={imageComponent} customBlocksData={customBlocksData}>\n {children}\n </ModuleLayer>\n </SiteSettingsProvider>\n </ConfigProvider>\n );\n}\n\n/**\n * De berekende theme-tokens, beschikbaar BUITEN `.xicms-root`.\n *\n * De visuele editor rendert als overlay naast die div (EditorProvider staat erboven in de boom),\n * dus zijn canvas erfde de merk-tokens niet: een groene primaire kleur werd daar de globale\n * indigo-fallback, en knoptekst kon zelfs samenvallen met de knopachtergrond. Door dezelfde\n * `cssVars` via context te delen toont het canvas exact wat de bezoeker straks ziet — zonder de\n * mapping te dupliceren of de overlay-structuur te verbouwen.\n */\nconst ThemeVarsCtx = createContext<{ vars: Record<string, string>; darkAuto: boolean }>({ vars: {}, darkAuto: false });\n\n/** Theme-tokens van de live site — voor UI die buiten `.xicms-root` rendert (de editor). */\nexport function useThemeVars() {\n return useContext(ThemeVarsCtx);\n}\n\n/**\n * Theme-tokens → CSS custom properties. Puur; wordt in `ModuleLayer` op het LIVE-gemergde thema\n * toegepast (SiteConfig + `settings/public.theme`), zodat huisstijl-wijzigingen uit /beheer direct\n * op de site verschijnen — net als module-instellingen.\n */\n/**\n * De donkere modus in één plek bepaald, zodat de tokenwaarden en de `data-dark`/`color-scheme` op\n * `.xicms-root` nooit uit elkaar kunnen lopen. `mode` wint; zonder `mode` valt hij terug op de\n * oude boolean, zodat bestaande site-configs (auto: true/false) ongewijzigd blijven werken.\n */\nfunction themeDarkMode(theme: ThemeTokens): \"light\" | \"dark\" | \"auto\" {\n return theme.dark?.mode ?? (theme.dark?.auto ? \"auto\" : \"light\");\n}\n\nfunction themeCssVars(theme: ThemeTokens): Record<string, string> {\n // Dark-auto via light-dark(): deze variabelen staan INLINE op `.xicms-root`, en inline wint\n // altijd van het `@media (prefers-color-scheme: dark)`-blok in styles.css. Alleen dat blok kan\n // een inline lichte waarde dus nooit overrulen — de tokenwaarde zelf moet meeschakelen.\n // (styles.css zet `color-scheme: light dark` op `[data-dark=\"auto\"]`, vereist voor light-dark().)\n // Driestand. `mode` wint; zonder `mode` valt hij terug op de oude boolean, zodat bestaande\n // site-configs (auto: true/false) ongewijzigd blijven werken.\n const donkerModus = themeDarkMode(theme);\n const auto = donkerModus === \"auto\";\n // Bij \"auto\" schakelt de tokenwaarde zélf mee via light-dark(); bij een vaste stand kiezen we hier\n // al. Dat moet op tokenniveau en niet via een @media-blok, want deze variabelen staan INLINE op\n // `.xicms-root` en inline wint altijd van een media-query in styles.css.\n const ld = (light: string, dark: string) =>\n donkerModus === \"auto\" ? `light-dark(${light}, ${dark})` : donkerModus === \"dark\" ? dark : light;\n const cssVars: Record<string, string> = {\n \"--xicms-primary\": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,\n \"--xicms-accent\": theme.accent ?? theme.primary,\n \"--xicms-bg\": ld(theme.background ?? \"#ffffff\", theme.dark?.background ?? \"#0f172a\"),\n \"--xicms-fg\": ld(theme.foreground ?? \"#0f172a\", theme.dark?.foreground ?? \"#e2e8f0\"),\n };\n // 8.8: met een reserve-lettertype op maat erachter (geen verspringende tekst bij het laden).\n if (theme.fontFamily) cssVars[\"--xicms-font\"] = metFallback(theme.fontFamily)!;\n // Optionele schalen → CSS-variabelen (alleen gezet als opgegeven; styles.css heeft defaults).\n for (const [k, v] of Object.entries(theme.spacing ?? {})) if (v) cssVars[`--xicms-spacing-${k}`] = v;\n if (theme.radius?.sm) cssVars[\"--xicms-radius-sm\"] = theme.radius.sm;\n if (theme.radius?.md) cssVars[\"--xicms-radius\"] = theme.radius.md;\n if (theme.radius?.lg) cssVars[\"--xicms-radius-lg\"] = theme.radius.lg;\n if (theme.container?.maxWidth) cssVars[\"--xicms-container-max\"] = theme.container.maxWidth;\n if (theme.container?.padding) cssVars[\"--xicms-container-pad\"] = theme.container.padding;\n if (theme.dark?.background) cssVars[\"--xicms-bg-dark\"] = theme.dark.background;\n if (theme.dark?.foreground) cssVars[\"--xicms-fg-dark\"] = theme.dark.foreground;\n if (theme.dark?.primary) cssVars[\"--xicms-primary-dark\"] = theme.dark.primary;\n\n // Astryx-tokens op XiCMS-waarden mappen, zodat de Astryx-componenten elke site z'n eigen merk\n // (kleur/typografie/radius) volgen. Inline op `.xicms-root` → wint over de theme.css-defaults.\n cssVars[\"--color-accent\"] = \"var(--xicms-primary)\";\n // Tekst óp de accentkleur: wit op een donkere merkkleur, donker op een lichte (geel/pastel) —\n // hardcoded wit maakte knop- en bannertekst onleesbaar op lichte primaire kleuren.\n const onAccent = tekstOpKleur(theme.primary) ?? \"#0f172a\";\n cssVars[\"--color-on-accent\"] = onAccent;\n cssVars[\"--xicms-on-primary\"] = onAccent;\n cssVars[\"--color-text-accent\"] = \"var(--xicms-primary)\";\n cssVars[\"--radius-element\"] = \"var(--xicms-radius-sm, 8px)\";\n cssVars[\"--radius-container\"] = \"var(--xicms-radius, 14px)\";\n cssVars[\"--radius-page\"] = \"var(--xicms-radius-lg, 24px)\";\n // Volledige paletmapping — ALLEEN als de site een eigen achtergrond/tekst kiest. Zo verschilt niet\n // enkel het accent maar de hele sfeer (kaarten, borders, secundaire tekst) per site; een minimale\n // config blijft op de gepolijste neutrale basis (incl. automatische dark-mode via light-dark()).\n if (theme.background) {\n cssVars[\"--color-background-body\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 4%, var(--xicms-bg))\";\n cssVars[\"--color-background-card\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--color-background-popover\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 6%, var(--xicms-bg))\";\n // Ook de XiCMS-oppervlaktetokens meeschakelen: hun defaults in styles.css zijn licht (#fff/zwart-\n // rgba) en het dark-blok vuurt alleen bij `data-dark=\"auto\"`. Zonder deze mapping tonen presets\n // met een vaste donkere achtergrond (donker-modern, luxe-donker) witte kaarten en zwarte borders.\n cssVars[\"--xicms-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--xicms-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 5%, var(--xicms-bg))\";\n }\n if (theme.foreground) {\n cssVars[\"--color-text-primary\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-text-secondary\"] = \"color-mix(in srgb, var(--xicms-fg) 65%, transparent)\";\n cssVars[\"--color-neutral\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-border\"] = \"color-mix(in srgb, var(--xicms-fg) 14%, transparent)\";\n cssVars[\"--xicms-border\"] = \"color-mix(in srgb, var(--xicms-fg) 12%, transparent)\";\n cssVars[\"--xicms-border-strong\"] = \"color-mix(in srgb, var(--xicms-fg) 22%, transparent)\";\n }\n if (theme.headingFont) cssVars[\"--xicms-font-heading\"] = metFallback(theme.headingFont)!;\n if (theme.fontFamily) {\n cssVars[\"--font-family-body\"] = \"var(--xicms-font)\";\n // Koppen volgen een apart display/serif-font als dat is opgegeven, anders het body-font.\n cssVars[\"--font-family-heading\"] = theme.headingFont ? \"var(--xicms-font-heading)\" : \"var(--xicms-font)\";\n } else if (theme.headingFont) {\n cssVars[\"--font-family-heading\"] = \"var(--xicms-font-heading)\";\n }\n\n return cssVars;\n}\n\n/**\n * Binnenlaag: leest de live publieke settings en resolvet daarmee de modules (settings/public.modules\n * legt zich over SiteConfig.modules) én het thema (settings/public.theme over SiteConfig.theme).\n * Zo werken module-aan/uit, -instellingen én huisstijl die de klant in /beheer zet direct door.\n */\nfunction ModuleLayer({\n config,\n blocks = NO_BLOCKS,\n modules = NO_MODULES,\n overrides,\n customBlocksData,\n astryxTheme,\n linkComponent,\n imageComponent,\n children,\n}: ProviderProps) {\n const { settings } = useSiteSettings();\n\n // LIVE thema: wijzigingen uit /beheer (settings/public.theme) winnen per token van SiteConfig.\n const theme = useMemo(() => mergeThemeTokens(config.theme, settings.theme), [config.theme, settings.theme]);\n const cssVars = useMemo(() => themeCssVars(theme), [theme]);\n const fontFallbackCss = useMemo(() => fallbackFontFaces([theme.fontFamily, theme.headingFont]), [theme.fontFamily, theme.headingFont]);\n // 8.8: laadt de site Google Fonts rechtstreeks, open dan alvast de verbinding naar de bestanden.\n if (theme.fontUrl && !config.selfHostFonts && isGoogleFontUrl(theme.fontUrl)) {\n preconnect(\"https://fonts.gstatic.com\", { crossOrigin: \"anonymous\" });\n }\n\n // Demo-bewerkmodus globaal markeren zodat de (Firebase-)dataclients lokaal-only werken.\n // In een effect (niet tijdens render): render-pure + veilig onder concurrent rendering. De\n // demoStore-default is `false`, dus vóór het effect gedraagt alles zich als niet-demo.\n useEffect(() => {\n setDemoActive(Boolean(config.demoEditing));\n }, [config.demoEditing]);\n\n const resolved = useMemo(\n () =>\n resolveModules([...builtinModules, ...modules], {\n modules: config.modules,\n firestoreModules: settings.modules as ModulesSiteConfig | undefined,\n }),\n [config.modules, modules, settings.modules],\n );\n\n // Custom blocks (5.0): runtime-gedefinieerde bloktypes uit sites/{siteId}/customBlocks.\n // Publieke bezoekers krijgen UITSLUITEND de SSR-startset (customBlocksData, server-side gelezen) —\n // géén Firestore-listener per bezoeker (kosten/verbinding). Alleen ingelogde editors krijgen een\n // live sync (CustomBlocksSync, binnen AuthProvider) zodat een nieuw blok uit /beheer of een\n // agent-push zonder redeploy in het toevoeg-menu en op de pagina verschijnt.\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);\n\n const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),\n [resolved.blocks, customDefs, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <PreviewDraftSync />\n <CustomBlocksSync siteId={config.siteId} onData={setCustomBlocks} />\n <SiteSettingsSync siteId={config.siteId} />\n <ThemeVarsCtx.Provider value={{ vars: cssVars, darkAuto: themeDarkMode(theme) === \"auto\" }}>\n <EditorProvider>\n {/* React 19 hijst <link rel=\"stylesheet\"> automatisch naar <head> → lettertype laadt. */}\n {theme.fontUrl && <link rel=\"stylesheet\" href={fontStylesheet(theme.fontUrl, config.selfHostFonts)} />}\n {/* 8.8: reserve-lettertypen op maat (zie theme/fontFallback.ts); React hijst ze naar <head>. */}\n {fontFallbackCss && (\n <style href=\"xicms-font-fallback\" precedence=\"xicms\">\n {fontFallbackCss}\n </style>\n )}\n {/* 8.8: ook bij een volledige paginaload (gewone <a>, andere tab) een rustige overgang. */}\n {config.paginaOvergang && (\n <style href=\"xicms-paginaovergang\" precedence=\"xicms\">\n {\"@view-transition{navigation:auto}\"}\n </style>\n )}\n {/* Astryx-thema (Neutral basis) — de XiCMS-token-mapping op `.xicms-root` legt elke\n site z'n eigen merk eroverheen. */}\n <Theme theme={astryxTheme ?? neutralTheme} mode={themeDarkMode(theme) === \"auto\" ? \"system\" : themeDarkMode(theme) === \"dark\" || isDarkColor(theme.background) ? \"dark\" : \"light\"}>\n {/* Framework-router voor Astryx-links/knoppen (soepele client-nav + prefetch);\n zonder `linkComponent` valt het terug op een gewone `<a>` (huidig gedrag). */}\n {/* 8.0: Astryx-teksten (o.a. \"Verplicht\"/\"Optioneel\" bij velden, schermlezer-meldingen)\n in de taal van de site. Zonder provider sprak Astryx overal Engels. */}\n <InternationalizationProvider {...astryxLocale(config.lang)}>\n <LinkProvider component={linkComponent ?? \"a\"}>\n <ImageProvider component={imageComponent}>\n <div\n className=\"xicms-root\"\n // Stuurt `color-scheme` (styles.css): \"auto\" = light dark, \"dark\" = dark.\n data-dark={themeDarkMode(theme) === \"light\" ? undefined : themeDarkMode(theme)}\n style={cssVars as React.CSSProperties}\n >\n {/* De zichtbare site-chrome (demo-balk, bewerkbalk, feedback, cookiebanner) is\n BEWUST geen onderdeel meer van de provider: die hoort bij het site-oppervlak\n en werd zo ook over /beheer heen gerenderd — een cookiebanner en een\n \"Zet live\"-balk bovenop het CMS. Sites renderen nu `<SiteChrome />` in hun\n eigen (site)-layout; zie ./SiteChrome.tsx. */}\n {children}\n </div>\n </ImageProvider>\n </LinkProvider>\n </InternationalizationProvider>\n </Theme>\n </EditorProvider>\n </ThemeVarsCtx.Provider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n\n/**\n * Live sync van custom blocks — ALLEEN voor ingelogde editors. Publieke bezoekers krijgen de\n * SSR-startset (customBlocksData) en géén Firestore-listener: dat scheelt een collection-read +\n * open verbinding per bezoeker, conform het getDoc-voor-publiek-patroon van useSiteSettings/News.\n */\n/**\n * Live sync van de publieke site-instellingen — ALLEEN voor ingelogde editors (zelfde patroon als\n * CustomBlocksSync). Zo ziet een editor een /beheer-wijziging (footer/nav/thema) meteen op de site,\n * terwijl publiek op de SSR-seed (settingsData) of de eenmalige getDoc blijft draaien.\n */\n/**\n * Zet Next's Draft Mode aan zodra een editor is ingelogd, zodat de pagina-route de gecommitte\n * content-bundel + de ISR-cache overslaat (server/pages.ts, `opts.skipBundle`) en deze browser\n * altijd de nieuwste GEPUBLICEERDE stand ziet — een publicatie in /beheer komt anders pas via het\n * ISR-vangnet mee.\n *\n * WAAROM EEN FETCH EN GEEN `document.cookie` ZOALS HIERVOOR: de vorige opzet zette zelf een\n * `xicms-preview`-cookie, die een proxy vertaalde naar `?preview=1`. Dat werkte niet: een route\n * die `searchParams` in zijn signatuur heeft merkt Next áltijd als dynamisch — ongeacht of de\n * proxy ooit herschrijft. Gemeten op groenhof: `ƒ (Dynamic)` met\n * `Cache-Control: private, no-cache, no-store` voor élke bezoeker. De cache was dus voor iedereen\n * stuk om een knop te bedienen die alleen de beheerder gebruikt.\n *\n * Draft Mode lost precies dát op: het zet de ondertekende `__prerender_bypass`-cookie die Next\n * VÓÓR de cache controleert, buiten de route om. Gemeten met dezelfde opzet: publiek houdt\n * `● (SSG)` met `s-maxage=300`, terwijl het request mét cookie `private, no-cache, no-store`\n * krijgt en vers rendert.\n *\n * Draagt geen rechten — net als de oude cookie. Er wordt nooit meer getoond dan de al-publieke,\n * gepubliceerde content, dus zelf `/api/draft` aanroepen levert hoogstens een verse blik op iets\n * dat toch al openbaar is. Wat het wél kost is een ongecachte render per request; wil je dat\n * dichttimmeren, laat de route dan een Firebase-ID-token verifiëren voor hij `enable()` doet.\n *\n * Faalt stil en onschadelijk als de site `/api/draft` niet heeft (zie template/): dan blijft de\n * beheerder gewoon op de gecachte stand, precies zoals een site zonder preview.\n */\nfunction PreviewDraftSync() {\n const { role } = useAuth();\n useEffect(() => {\n const gewenst = canEdit(role);\n // Alleen aanroepen als de stand écht moet veranderen. Zonder deze vergelijking zou élke\n // publieke bezoeker bij élke paginaweergave een POST doen om preview \"uit\" te zetten die al\n // uit stond — een serverless-aanroep per bezoeker, precies de kosten die deze hele omzetting\n // moet vermijden.\n //\n // De marker is een SESSIE-cookie, net als Next's `__prerender_bypass`: die laatste is httpOnly\n // en dus onleesbaar voor deze code, dus we houden er zelf een spiegel van bij. Omdat beide\n // met het browservenster verdwijnen lopen ze niet uit de pas — een marker die de cookie\n // overleeft zou preview stilletjes uitgeschakeld laten.\n const gemarkeerd = document.cookie.includes(\"xicms-draft=1\");\n if (gewenst === gemarkeerd) return;\n void fetch(`/api/draft?aan=${gewenst ? \"1\" : \"0\"}`, { method: \"POST\" })\n .then((r) => {\n if (!r.ok) return; // site zonder /api/draft: laat de marker met rust, dan blijft dit stil.\n document.cookie = gewenst ? \"xicms-draft=1; path=/; samesite=lax\" : \"xicms-draft=; path=/; max-age=0\";\n })\n .catch(() => {});\n }, [role]);\n return null;\n}\n\nfunction SiteSettingsSync({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n useSiteSettingsLiveSync(siteId, canEdit(role));\n return null;\n}\n\nfunction CustomBlocksSync({ siteId, onData }: { siteId: string; onData: (defs: CustomBlockData[]) => void }) {\n const { role } = useAuth();\n const editing = canEdit(role);\n useEffect(() => {\n // Alleen editors luisteren mee (kosten + verbinding), en de SDK laadt pas dán.\n if (!editing) return;\n let unsub: (() => void) | undefined;\n let afgebroken = false;\n void (async () => {\n const [{ getDb }, { collection, onSnapshot }] = await Promise.all([\n import(\"./firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) return;\n unsub = onSnapshot(\n collection(db, \"sites\", siteId, \"customBlocks\"),\n (snap) => {\n const defs: CustomBlockData[] = [];\n snap.forEach((d) => {\n const res = CustomBlockSchema.safeParse({ ...(d.data() as Record<string, unknown>), type: (d.data() as { type?: string }).type ?? d.id });\n if (res.success) defs.push(res.data);\n });\n onData(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [editing, siteId, onData]);\n return null;\n}\n\n/** Astryx-locale bij `SiteConfig.lang`. Alleen Nederlands wordt meegebundeld (~5 KB gzip); andere talen vallen terug op het ingebouwde Engels. */\nfunction astryxLocale(lang: string | undefined): { locale: string; messages?: { \"nl-NL\": typeof nlNL } } {\n const l = (lang ?? \"nl\").toLowerCase();\n return l === \"nl\" || l.startsWith(\"nl-\") ? { locale: \"nl-NL\", messages: { \"nl-NL\": nlNL } } : { locale: \"en\" };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nimport { canEdit } from \"./auth/roles\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\nimport { CustomBlockSchema, type CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nimport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nimport { resolveModules } from \"./modules/registry\";\nimport { ModulesProvider } from \"./modules/context\";\nimport { applyBlockOverrides, type BlockOverrides } from \"./modules/overrides\";\nimport { builtinModules } from \"./modules/builtins\";\nimport type { ModuleDefinition, ModulesSiteConfig } from \"./modules/types\";\nimport { EditorProvider } from \"./editor/EditorProvider\";\n// Puur en dependency-vrij; hier alleen nog geherexporteerd zodat de publieke API gelijk blijft.\nimport { isDarkColor, tekstOpKleur } from \"./theme/color\";\nexport { isDarkColor };\nimport { SiteSettingsProvider, useSiteSettings, useSiteSettingsLiveSync } from \"./settings/useSiteSettings\";\nimport { mergeThemeTokens, type ThemeTokens, type PublicSiteSettings } from \"./schema/settings\";\nimport { initAppCheck } from \"./firebase/appcheck\";\nimport { setDemoActive } from \"./demo/demoStore\";\nimport { fontStylesheet, isGoogleFontUrl } from \"./theme/fonts\";\nimport { fallbackFontFaces, metFallback } from \"./theme/fontFallback\";\nimport { preconnect } from \"react-dom\";\nimport { Theme, type DefinedTheme } from \"@astryxdesign/core/theme\";\nimport { neutralTheme } from \"@astryxdesign/theme-neutral/built\";\nimport { LinkProvider, type LinkComponentType } from \"@astryxdesign/core/Link\";\nimport { InternationalizationProvider } from \"@astryxdesign/core/i18n\";\nimport nlNL from \"@astryxdesign/core/locales/nl-NL.generated.js\";\nimport { ImageProvider, type ImageComponentType } from \"./media/ImageContext\";\nimport { validateSiteConfig, type SiteConfig } from \"./schema/siteConfig\";\nimport { validateModuleContract } from \"./modules/contract\";\n\ninterface ProviderProps {\n config: SiteConfig;\n /** Losse klant-blokken (via `defineBlock`) buiten een module om. Altijd actief. */\n blocks?: BlockDefinition[];\n /**\n * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)\n * en respecteren `config.modules` voor activatie.\n */\n modules?: ModuleDefinition[];\n /**\n * Per-site presentatie-overrides per block-type (`{ gallery: { Render: MijnGalerij } }`). Vervangt\n * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.\n */\n overrides?: BlockOverrides;\n /**\n * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`\n * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze\n * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).\n */\n customBlocksData?: CustomBlockData[];\n /**\n * SSR-startset van de publieke site-instellingen: geef het resultaat van\n * `getPublicSiteSettings(siteId)` (server) door zodat klant-thema/nav/footer al in de\n * server-HTML zitten (geen theme-flits bij eerste paint) én publiek geen client-read meer\n * doet — de ISR-gecachte layout ververst de settings per revalidate. Zonder deze prop laadt\n * de provider ze alsnog client-side (werkend, maar met mogelijke flits + 1 read per bezoeker).\n */\n settingsData?: PublicSiteSettings;\n /**\n * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.\n * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen\n * `defineTheme(...)` doorgeven voor component-overrides — zónder core te forken.\n */\n astryxTheme?: DefinedTheme;\n /**\n * Framework-link-component (bv. Next `Link`) voor soepele client-navigatie + prefetch. Astryx-\n * componenten met een `href` (knoppen/links/tabs) gebruiken 'm dan i.p.v. een rauwe `<a>` (= volledige\n * page-reload). Core importeert `next/link` niet zelf (optionele peer) — geef 'm hier door vanuit de site.\n */\n linkComponent?: LinkComponentType;\n /**\n * Framework-beeldcomponent (bv. Next `Image`) voor automatische beeldoptimalisatie (responsive\n * srcset, AVIF/WebP, geen layout-shift). Core importeert `next/image` niet zelf — geef 'm hier door.\n */\n imageComponent?: ImageComponentType;\n children: ReactNode;\n}\n\n// Stabiele lege defaults: een `= []`-defaultparameter maakt bij ELKE render een nieuwe\n// array-referentie en laat memo's/effects met `modules`/`blocks` in de deps onnodig hervuren.\nconst NO_BLOCKS: BlockDefinition[] = [];\nconst NO_MODULES: ModuleDefinition[] = [];\n\n// Dev-waarschuwingen één keer per boodschap (React strict-mode dubbelt effects; zonder dedupe\n// verschijnt dezelfde config-waarschuwing telkens opnieuw in de console).\nconst warned = new Set<string>();\nfunction warnOnce(message: string) {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(message);\n}\n\n/**\n * Wikkel de hele site in deze provider (in `app/layout.tsx`). Hij zet:\n * - de SiteConfig-context (siteId, theme, mode) + live publieke settings (footer/nav/modules);\n * - auth + content (Firebase, env-gedreven; werkt ook zonder Firebase);\n * - de block-registry (ingebouwde + klant-blokken) voor de page-renderer;\n * - de themavariabelen als CSS custom properties;\n * - App Check (alleen als geconfigureerd).\n *\n * De ZICHTBARE site-chrome (bewerkbalk, cookiebanner, demo) staat in `<SiteChrome />` en hoort in\n * de layout van je publieke routes — niet hier, anders verschijnt hij ook over /beheer.\n */\nexport function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }: ProviderProps) {\n useEffect(() => {\n // App Check hoort bij het praten mét Firebase; een bezoeker doet dat niet. 8.16: op de\n // publieke pagina's nu helemaal niet meer vooraf — reCAPTCHA laadde scripts van Google bij\n // ELKE bezoeker, ook zonder formulier of bestelling. `callable()` start het nu pas vlak vóór\n // een formulier/bestelling, en de AuthProvider zodra iemand inlogt. Alleen het beheer\n // (inlogscherm) start het meteen.\n if (!process.env.NEXT_PUBLIC_FIREBASE_APPCHECK_KEY) return;\n if (!/^\\/(beheer|login)(\\/|$)/.test(window.location.pathname)) return;\n void initAppCheck();\n }, []);\n\n // Dev-vangnet: ontbrekende SSR-seeds. Zonder `settingsData` haalt élke bezoeker de publieke\n // settings zelf uit Firestore — één read per bezoeker, én het downloaden van de Firebase-SDK\n // (~680 KB) die anders nooit nodig is. Dat faalt volkomen stil: de site ziet er goed uit.\n // De referentiesite groenhof liep hier maandenlang op, dus: melden zodra het misgaat.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n if (!process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID) return; // geen Firebase → geen client-read\n if (settingsData === undefined) {\n warnOnce(\n \"[xicms] `settingsData` ontbreekt op XiCMSProvider. Elke bezoeker doet daardoor zélf een \" +\n \"Firestore-read voor de site-instellingen en downloadt de Firebase-SDK. Lees ze server-side \" +\n \"in je layout met `getPublicSiteSettings(siteId)` en geef ze door — zie template/app/layout.tsx.\",\n );\n }\n if (customBlocksData === undefined) {\n warnOnce(\n \"[xicms] `customBlocksData` ontbreekt op XiCMSProvider. Custom blocks worden dan pas na \" +\n \"hydratie zichtbaar (SEO-gat + flits). Lees ze server-side met `getSiteCustomBlocks(siteId)`.\",\n );\n }\n }, [settingsData, customBlocksData]);\n\n // Dev-vangnet voor (agent-)geschreven configs: meld config- en module-contractfouten meteen in\n // de console i.p.v. een subtiel kapotte site. In productie geen werk — en er wordt nooit gegooid.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const configIssues = validateSiteConfig(config);\n if (configIssues.length > 0) {\n warnOnce(`[xicms] site.config.ts bevat problemen:\\n- ${configIssues.join(\"\\n- \")}`);\n }\n if (modules.length > 0) {\n const contractIssues = validateModuleContract(modules);\n if (contractIssues.length > 0) {\n warnOnce(`[xicms] klant-modules voldoen niet aan het module-contract:\\n- ${contractIssues.join(\"\\n- \")}`);\n }\n }\n }, [config, modules]);\n\n return (\n <ConfigProvider config={config}>\n <SiteSettingsProvider siteId={config.siteId} initial={settingsData}>\n <ModuleLayer config={config} blocks={blocks} modules={modules} overrides={overrides} astryxTheme={astryxTheme} linkComponent={linkComponent} imageComponent={imageComponent} customBlocksData={customBlocksData}>\n {children}\n </ModuleLayer>\n </SiteSettingsProvider>\n </ConfigProvider>\n );\n}\n\n/**\n * De berekende theme-tokens, beschikbaar BUITEN `.xicms-root`.\n *\n * De visuele editor rendert als overlay naast die div (EditorProvider staat erboven in de boom),\n * dus zijn canvas erfde de merk-tokens niet: een groene primaire kleur werd daar de globale\n * indigo-fallback, en knoptekst kon zelfs samenvallen met de knopachtergrond. Door dezelfde\n * `cssVars` via context te delen toont het canvas exact wat de bezoeker straks ziet — zonder de\n * mapping te dupliceren of de overlay-structuur te verbouwen.\n */\nconst ThemeVarsCtx = createContext<{ vars: Record<string, string>; darkAuto: boolean }>({ vars: {}, darkAuto: false });\n\n/** Theme-tokens van de live site — voor UI die buiten `.xicms-root` rendert (de editor). */\nexport function useThemeVars() {\n return useContext(ThemeVarsCtx);\n}\n\n/**\n * Theme-tokens → CSS custom properties. Puur; wordt in `ModuleLayer` op het LIVE-gemergde thema\n * toegepast (SiteConfig + `settings/public.theme`), zodat huisstijl-wijzigingen uit /beheer direct\n * op de site verschijnen — net als module-instellingen.\n */\n/**\n * De donkere modus in één plek bepaald, zodat de tokenwaarden en de `data-dark`/`color-scheme` op\n * `.xicms-root` nooit uit elkaar kunnen lopen. `mode` wint; zonder `mode` valt hij terug op de\n * oude boolean, zodat bestaande site-configs (auto: true/false) ongewijzigd blijven werken.\n */\nfunction themeDarkMode(theme: ThemeTokens): \"light\" | \"dark\" | \"auto\" {\n return theme.dark?.mode ?? (theme.dark?.auto ? \"auto\" : \"light\");\n}\n\nfunction themeCssVars(theme: ThemeTokens): Record<string, string> {\n // Dark-auto via light-dark(): deze variabelen staan INLINE op `.xicms-root`, en inline wint\n // altijd van het `@media (prefers-color-scheme: dark)`-blok in styles.css. Alleen dat blok kan\n // een inline lichte waarde dus nooit overrulen — de tokenwaarde zelf moet meeschakelen.\n // (styles.css zet `color-scheme: light dark` op `[data-dark=\"auto\"]`, vereist voor light-dark().)\n // Driestand. `mode` wint; zonder `mode` valt hij terug op de oude boolean, zodat bestaande\n // site-configs (auto: true/false) ongewijzigd blijven werken.\n const donkerModus = themeDarkMode(theme);\n const auto = donkerModus === \"auto\";\n // Bij \"auto\" schakelt de tokenwaarde zélf mee via light-dark(); bij een vaste stand kiezen we hier\n // al. Dat moet op tokenniveau en niet via een @media-blok, want deze variabelen staan INLINE op\n // `.xicms-root` en inline wint altijd van een media-query in styles.css.\n const ld = (light: string, dark: string) =>\n donkerModus === \"auto\" ? `light-dark(${light}, ${dark})` : donkerModus === \"dark\" ? dark : light;\n const cssVars: Record<string, string> = {\n \"--xicms-primary\": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,\n \"--xicms-accent\": theme.accent ?? theme.primary,\n \"--xicms-bg\": ld(theme.background ?? \"#ffffff\", theme.dark?.background ?? \"#0f172a\"),\n \"--xicms-fg\": ld(theme.foreground ?? \"#0f172a\", theme.dark?.foreground ?? \"#e2e8f0\"),\n };\n // 8.8: met een reserve-lettertype op maat erachter (geen verspringende tekst bij het laden).\n if (theme.fontFamily) cssVars[\"--xicms-font\"] = metFallback(theme.fontFamily)!;\n // Optionele schalen → CSS-variabelen (alleen gezet als opgegeven; styles.css heeft defaults).\n for (const [k, v] of Object.entries(theme.spacing ?? {})) if (v) cssVars[`--xicms-spacing-${k}`] = v;\n if (theme.radius?.sm) cssVars[\"--xicms-radius-sm\"] = theme.radius.sm;\n if (theme.radius?.md) cssVars[\"--xicms-radius\"] = theme.radius.md;\n if (theme.radius?.lg) cssVars[\"--xicms-radius-lg\"] = theme.radius.lg;\n if (theme.container?.maxWidth) cssVars[\"--xicms-container-max\"] = theme.container.maxWidth;\n if (theme.container?.padding) cssVars[\"--xicms-container-pad\"] = theme.container.padding;\n if (theme.dark?.background) cssVars[\"--xicms-bg-dark\"] = theme.dark.background;\n if (theme.dark?.foreground) cssVars[\"--xicms-fg-dark\"] = theme.dark.foreground;\n if (theme.dark?.primary) cssVars[\"--xicms-primary-dark\"] = theme.dark.primary;\n\n // Astryx-tokens op XiCMS-waarden mappen, zodat de Astryx-componenten elke site z'n eigen merk\n // (kleur/typografie/radius) volgen. Inline op `.xicms-root` → wint over de theme.css-defaults.\n cssVars[\"--color-accent\"] = \"var(--xicms-primary)\";\n // Tekst óp de accentkleur: wit op een donkere merkkleur, donker op een lichte (geel/pastel) —\n // hardcoded wit maakte knop- en bannertekst onleesbaar op lichte primaire kleuren.\n const onAccent = tekstOpKleur(theme.primary) ?? \"#0f172a\";\n cssVars[\"--color-on-accent\"] = onAccent;\n cssVars[\"--xicms-on-primary\"] = onAccent;\n cssVars[\"--color-text-accent\"] = \"var(--xicms-primary)\";\n cssVars[\"--radius-element\"] = \"var(--xicms-radius-sm, 8px)\";\n cssVars[\"--radius-container\"] = \"var(--xicms-radius, 14px)\";\n cssVars[\"--radius-page\"] = \"var(--xicms-radius-lg, 24px)\";\n // Volledige paletmapping — ALLEEN als de site een eigen achtergrond/tekst kiest. Zo verschilt niet\n // enkel het accent maar de hele sfeer (kaarten, borders, secundaire tekst) per site; een minimale\n // config blijft op de gepolijste neutrale basis (incl. automatische dark-mode via light-dark()).\n if (theme.background) {\n cssVars[\"--color-background-body\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 4%, var(--xicms-bg))\";\n cssVars[\"--color-background-card\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--color-background-popover\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 6%, var(--xicms-bg))\";\n // Ook de XiCMS-oppervlaktetokens meeschakelen: hun defaults in styles.css zijn licht (#fff/zwart-\n // rgba) en het dark-blok vuurt alleen bij `data-dark=\"auto\"`. Zonder deze mapping tonen presets\n // met een vaste donkere achtergrond (donker-modern, luxe-donker) witte kaarten en zwarte borders.\n cssVars[\"--xicms-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--xicms-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 5%, var(--xicms-bg))\";\n }\n if (theme.foreground) {\n cssVars[\"--color-text-primary\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-text-secondary\"] = \"color-mix(in srgb, var(--xicms-fg) 65%, transparent)\";\n cssVars[\"--color-neutral\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-border\"] = \"color-mix(in srgb, var(--xicms-fg) 14%, transparent)\";\n cssVars[\"--xicms-border\"] = \"color-mix(in srgb, var(--xicms-fg) 12%, transparent)\";\n cssVars[\"--xicms-border-strong\"] = \"color-mix(in srgb, var(--xicms-fg) 22%, transparent)\";\n }\n if (theme.headingFont) cssVars[\"--xicms-font-heading\"] = metFallback(theme.headingFont)!;\n if (theme.fontFamily) {\n cssVars[\"--font-family-body\"] = \"var(--xicms-font)\";\n // Koppen volgen een apart display/serif-font als dat is opgegeven, anders het body-font.\n cssVars[\"--font-family-heading\"] = theme.headingFont ? \"var(--xicms-font-heading)\" : \"var(--xicms-font)\";\n } else if (theme.headingFont) {\n cssVars[\"--font-family-heading\"] = \"var(--xicms-font-heading)\";\n }\n\n return cssVars;\n}\n\n/**\n * Binnenlaag: leest de live publieke settings en resolvet daarmee de modules (settings/public.modules\n * legt zich over SiteConfig.modules) én het thema (settings/public.theme over SiteConfig.theme).\n * Zo werken module-aan/uit, -instellingen én huisstijl die de klant in /beheer zet direct door.\n */\nfunction ModuleLayer({\n config,\n blocks = NO_BLOCKS,\n modules = NO_MODULES,\n overrides,\n customBlocksData,\n astryxTheme,\n linkComponent,\n imageComponent,\n children,\n}: ProviderProps) {\n const { settings } = useSiteSettings();\n\n // LIVE thema: wijzigingen uit /beheer (settings/public.theme) winnen per token van SiteConfig.\n const theme = useMemo(() => mergeThemeTokens(config.theme, settings.theme), [config.theme, settings.theme]);\n const cssVars = useMemo(() => themeCssVars(theme), [theme]);\n const fontFallbackCss = useMemo(() => fallbackFontFaces([theme.fontFamily, theme.headingFont]), [theme.fontFamily, theme.headingFont]);\n // 8.8: laadt de site Google Fonts rechtstreeks, open dan alvast de verbinding naar de bestanden.\n if (theme.fontUrl && !config.selfHostFonts && isGoogleFontUrl(theme.fontUrl)) {\n preconnect(\"https://fonts.gstatic.com\", { crossOrigin: \"anonymous\" });\n }\n\n // Demo-bewerkmodus globaal markeren zodat de (Firebase-)dataclients lokaal-only werken.\n // In een effect (niet tijdens render): render-pure + veilig onder concurrent rendering. De\n // demoStore-default is `false`, dus vóór het effect gedraagt alles zich als niet-demo.\n useEffect(() => {\n setDemoActive(Boolean(config.demoEditing));\n }, [config.demoEditing]);\n\n const resolved = useMemo(\n () =>\n resolveModules([...builtinModules, ...modules], {\n modules: config.modules,\n firestoreModules: settings.modules as ModulesSiteConfig | undefined,\n }),\n [config.modules, modules, settings.modules],\n );\n\n // Custom blocks (5.0): runtime-gedefinieerde bloktypes uit sites/{siteId}/customBlocks.\n // Publieke bezoekers krijgen UITSLUITEND de SSR-startset (customBlocksData, server-side gelezen) —\n // géén Firestore-listener per bezoeker (kosten/verbinding). Alleen ingelogde editors krijgen een\n // live sync (CustomBlocksSync, binnen AuthProvider) zodat een nieuw blok uit /beheer of een\n // agent-push zonder redeploy in het toevoeg-menu en op de pagina verschijnt.\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);\n\n const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),\n [resolved.blocks, customDefs, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <PreviewDraftSync />\n <CustomBlocksSync siteId={config.siteId} onData={setCustomBlocks} />\n <SiteSettingsSync siteId={config.siteId} />\n <ThemeVarsCtx.Provider value={{ vars: cssVars, darkAuto: themeDarkMode(theme) === \"auto\" }}>\n <EditorProvider>\n {/* React 19 hijst <link rel=\"stylesheet\"> automatisch naar <head> → lettertype laadt. */}\n {theme.fontUrl && <link rel=\"stylesheet\" href={fontStylesheet(theme.fontUrl, config.selfHostFonts)} />}\n {/* 8.8: reserve-lettertypen op maat (zie theme/fontFallback.ts); React hijst ze naar <head>. */}\n {fontFallbackCss && (\n <style href=\"xicms-font-fallback\" precedence=\"xicms\">\n {fontFallbackCss}\n </style>\n )}\n {/* 8.8: ook bij een volledige paginaload (gewone <a>, andere tab) een rustige overgang. */}\n {config.paginaOvergang && (\n <style href=\"xicms-paginaovergang\" precedence=\"xicms\">\n {\"@view-transition{navigation:auto}\"}\n </style>\n )}\n {/* Astryx-thema (Neutral basis) — de XiCMS-token-mapping op `.xicms-root` legt elke\n site z'n eigen merk eroverheen. */}\n <Theme theme={astryxTheme ?? neutralTheme} mode={themeDarkMode(theme) === \"auto\" ? \"system\" : themeDarkMode(theme) === \"dark\" || isDarkColor(theme.background) ? \"dark\" : \"light\"}>\n {/* Framework-router voor Astryx-links/knoppen (soepele client-nav + prefetch);\n zonder `linkComponent` valt het terug op een gewone `<a>` (huidig gedrag). */}\n {/* 8.0: Astryx-teksten (o.a. \"Verplicht\"/\"Optioneel\" bij velden, schermlezer-meldingen)\n in de taal van de site. Zonder provider sprak Astryx overal Engels. */}\n <InternationalizationProvider {...astryxLocale(config.lang)}>\n <LinkProvider component={linkComponent ?? \"a\"}>\n <ImageProvider component={imageComponent}>\n <div\n className=\"xicms-root\"\n // Stuurt `color-scheme` (styles.css): \"auto\" = light dark, \"dark\" = dark.\n data-dark={themeDarkMode(theme) === \"light\" ? undefined : themeDarkMode(theme)}\n style={cssVars as React.CSSProperties}\n >\n {/* De zichtbare site-chrome (demo-balk, bewerkbalk, feedback, cookiebanner) is\n BEWUST geen onderdeel meer van de provider: die hoort bij het site-oppervlak\n en werd zo ook over /beheer heen gerenderd — een cookiebanner en een\n \"Zet live\"-balk bovenop het CMS. Sites renderen nu `<SiteChrome />` in hun\n eigen (site)-layout; zie ./SiteChrome.tsx. */}\n {children}\n </div>\n </ImageProvider>\n </LinkProvider>\n </InternationalizationProvider>\n </Theme>\n </EditorProvider>\n </ThemeVarsCtx.Provider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n\n/**\n * Live sync van custom blocks — ALLEEN voor ingelogde editors. Publieke bezoekers krijgen de\n * SSR-startset (customBlocksData) en géén Firestore-listener: dat scheelt een collection-read +\n * open verbinding per bezoeker, conform het getDoc-voor-publiek-patroon van useSiteSettings/News.\n */\n/**\n * Live sync van de publieke site-instellingen — ALLEEN voor ingelogde editors (zelfde patroon als\n * CustomBlocksSync). Zo ziet een editor een /beheer-wijziging (footer/nav/thema) meteen op de site,\n * terwijl publiek op de SSR-seed (settingsData) of de eenmalige getDoc blijft draaien.\n */\n/**\n * Zet Next's Draft Mode aan zodra een editor is ingelogd, zodat de pagina-route de gecommitte\n * content-bundel + de ISR-cache overslaat (server/pages.ts, `opts.skipBundle`) en deze browser\n * altijd de nieuwste GEPUBLICEERDE stand ziet — een publicatie in /beheer komt anders pas via het\n * ISR-vangnet mee.\n *\n * WAAROM EEN FETCH EN GEEN `document.cookie` ZOALS HIERVOOR: de vorige opzet zette zelf een\n * `xicms-preview`-cookie, die een proxy vertaalde naar `?preview=1`. Dat werkte niet: een route\n * die `searchParams` in zijn signatuur heeft merkt Next áltijd als dynamisch — ongeacht of de\n * proxy ooit herschrijft. Gemeten op groenhof: `ƒ (Dynamic)` met\n * `Cache-Control: private, no-cache, no-store` voor élke bezoeker. De cache was dus voor iedereen\n * stuk om een knop te bedienen die alleen de beheerder gebruikt.\n *\n * Draft Mode lost precies dát op: het zet de ondertekende `__prerender_bypass`-cookie die Next\n * VÓÓR de cache controleert, buiten de route om. Gemeten met dezelfde opzet: publiek houdt\n * `● (SSG)` met `s-maxage=300`, terwijl het request mét cookie `private, no-cache, no-store`\n * krijgt en vers rendert.\n *\n * Draagt geen rechten — net als de oude cookie. Er wordt nooit meer getoond dan de al-publieke,\n * gepubliceerde content, dus zelf `/api/draft` aanroepen levert hoogstens een verse blik op iets\n * dat toch al openbaar is. Wat het wél kost is een ongecachte render per request; wil je dat\n * dichttimmeren, laat de route dan een Firebase-ID-token verifiëren voor hij `enable()` doet.\n *\n * Faalt stil en onschadelijk als de site `/api/draft` niet heeft (zie template/): dan blijft de\n * beheerder gewoon op de gecachte stand, precies zoals een site zonder preview.\n */\nfunction PreviewDraftSync() {\n const { role } = useAuth();\n useEffect(() => {\n const gewenst = canEdit(role);\n // Alleen aanroepen als de stand écht moet veranderen. Zonder deze vergelijking zou élke\n // publieke bezoeker bij élke paginaweergave een POST doen om preview \"uit\" te zetten die al\n // uit stond — een serverless-aanroep per bezoeker, precies de kosten die deze hele omzetting\n // moet vermijden.\n //\n // De marker is een SESSIE-cookie, net als Next's `__prerender_bypass`: die laatste is httpOnly\n // en dus onleesbaar voor deze code, dus we houden er zelf een spiegel van bij. Omdat beide\n // met het browservenster verdwijnen lopen ze niet uit de pas — een marker die de cookie\n // overleeft zou preview stilletjes uitgeschakeld laten.\n const gemarkeerd = document.cookie.includes(\"xicms-draft=1\");\n if (gewenst === gemarkeerd) return;\n void fetch(`/api/draft?aan=${gewenst ? \"1\" : \"0\"}`, { method: \"POST\" })\n .then((r) => {\n if (!r.ok) return; // site zonder /api/draft: laat de marker met rust, dan blijft dit stil.\n document.cookie = gewenst ? \"xicms-draft=1; path=/; samesite=lax\" : \"xicms-draft=; path=/; max-age=0\";\n })\n .catch(() => {});\n }, [role]);\n return null;\n}\n\nfunction SiteSettingsSync({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n useSiteSettingsLiveSync(siteId, canEdit(role));\n return null;\n}\n\nfunction CustomBlocksSync({ siteId, onData }: { siteId: string; onData: (defs: CustomBlockData[]) => void }) {\n const { role } = useAuth();\n const editing = canEdit(role);\n useEffect(() => {\n // Alleen editors luisteren mee (kosten + verbinding), en de SDK laadt pas dán.\n if (!editing) return;\n let unsub: (() => void) | undefined;\n let afgebroken = false;\n void (async () => {\n const [{ getDb }, { collection, onSnapshot }] = await Promise.all([\n import(\"./firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db || afgebroken) return;\n unsub = onSnapshot(\n collection(db, \"sites\", siteId, \"customBlocks\"),\n (snap) => {\n const defs: CustomBlockData[] = [];\n snap.forEach((d) => {\n const res = CustomBlockSchema.safeParse({ ...(d.data() as Record<string, unknown>), type: (d.data() as { type?: string }).type ?? d.id });\n if (res.success) defs.push(res.data);\n });\n onData(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n })();\n return () => {\n afgebroken = true;\n unsub?.();\n };\n }, [editing, siteId, onData]);\n return null;\n}\n\n/** Astryx-locale bij `SiteConfig.lang`. Alleen Nederlands wordt meegebundeld (~5 KB gzip); andere talen vallen terug op het ingebouwde Engels. */\nfunction astryxLocale(lang: string | undefined): { locale: string; messages?: { \"nl-NL\": typeof nlNL } } {\n const l = (lang ?? \"nl\").toLowerCase();\n return l === \"nl\" || l.startsWith(\"nl-\") ? { locale: \"nl-NL\", messages: { \"nl-NL\": nlNL } } : { locale: \"en\" };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -105,6 +105,7 @@ const LABELS = {
|
|
|
105
105
|
requestLabel: "Tekst op de knop",
|
|
106
106
|
periodField: "Formulierveld voor de periode (technische naam)",
|
|
107
107
|
unitField: "Formulierveld voor de accommodatie (technische naam)",
|
|
108
|
+
bedrijfsnummers: "KvK- en btw-nummer tonen (uit Instellingen \u2192 Bedrijf)",
|
|
108
109
|
reviewKnop: "Knop \u2018Laat een review achter\u2019 (link uit Instellingen \u2192 Bedrijf)",
|
|
109
110
|
minHeight: "Hoogte",
|
|
110
111
|
headingLevel: "Kopniveau",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/lib/veldTaal.ts"],"sourcesContent":["import type { Field } from \"../../schema/fields\";\n\n/**\n * De taallaag van de blokinstellingen (8.11).\n *\n * Het automatische formulier (GenericEditor) leidt zijn velden af uit het Zod-schema, en daar staan\n * sleutels: \"heading\", \"imageStyle\", \"items\". 22 van de 57 ingebouwde blokken hebben geen eigen\n * editor, dus een klant zag bij Diensten, Team, Reviews of Openingstijden letterlijk die Engelse\n * sleutels — en keuzes als \"inset\" en \"cover\". Dit bestand vertaalt ze, op één plek, voor alle\n * blokken tegelijk (ook maatwerkblokken die dezelfde sleutels gebruiken). Een `.describe()` op het\n * schema of een `fields`-hint van het blok wint altijd: dit is de terugval, niet de baas.\n *\n * Ook het TYPE wordt hier rechtgezet waar de afleiding raadt: elke korte tekst in de schema's mag\n * 2000 tekens, en \"langer dan 500 = tekstvak\" maakte van elke kop een groot invoervak. Alleen velden\n * die echt lopende tekst zijn, worden een tekstvak.\n */\n\nconst LABELS: Record<string, string> = {\n heading: \"Kop\",\n intro: \"Inleiding\",\n title: \"Titel\",\n subtitle: \"Ondertitel\",\n eyebrow: \"Regeltje boven de titel\",\n text: \"Tekst\",\n description: \"Omschrijving\",\n body: \"Tekst\",\n bio: \"Over deze persoon\",\n quote: \"Citaat\",\n author: \"Naam\",\n role: \"Functie of plaats\",\n name: \"Naam\",\n label: \"Tekst\",\n linkLabel: \"Tekst van de link\",\n buttonLabel: \"Knoptekst\",\n buttonHref: \"Knoplink\",\n buttonVariant: \"Knopstijl\",\n button2Label: \"Tweede knop: tekst\",\n button2Href: \"Tweede knop: link\",\n ctaLabel: \"Knoptekst\",\n ctaHref: \"Knoplink\",\n href: \"Link\",\n url: \"Adres\",\n applyUrl: \"Link om te solliciteren\",\n icon: \"Icoon\",\n imageUrl: \"Foto\",\n photoUrl: \"Foto\",\n avatarUrl: \"Foto\",\n beforeUrl: \"Foto vóór\",\n afterUrl: \"Foto na\",\n beforeLabel: \"Label vóór\",\n afterLabel: \"Label na\",\n src: \"Logo\",\n alt: \"Beschrijving van de foto\",\n columns: \"Kolommen\",\n imageStyle: \"Fotostijl\",\n layout: \"Weergave\",\n align: \"Uitlijning\",\n variant: \"Stijl\",\n tone: \"Kleur\",\n size: \"Grootte\",\n spacing: \"Ruimte\",\n height: \"Hoogte\",\n aspectRatio: \"Verhouding\",\n position: \"Plaats op het scherm\",\n items: \"Onderdelen\",\n members: \"Teamleden\",\n rows: \"Regels\",\n documents: \"Documenten\",\n social: \"Social media\",\n platform: \"Platform\",\n featured: \"Uitgelicht\",\n rating: \"Sterren\",\n value: \"Getal\",\n prefix: \"Vóór het getal\",\n suffix: \"Na het getal\",\n animate: \"Laten optellen bij in beeld komen\",\n day: \"Dag\",\n hours: \"Tijden\",\n date: \"Datum\",\n datePosted: \"Geplaatst op\",\n category: \"Categorie\",\n location: \"Plaats\",\n employmentType: \"Soort dienstverband\",\n salary: \"Salaris\",\n department: \"Afdeling\",\n email: \"E-mailadres\",\n phone: \"Telefoonnummer\",\n meta: \"Extra info (bv. 'PDF · 2 MB')\",\n prefilledText: \"Begintekst van het bericht\",\n placeholder: \"Tekst in het zoekveld\",\n homeLabel: \"Naam van de homepage\",\n dismissible: \"Bezoeker kan de balk wegklikken\",\n dismissVersion: \"Versie (voor opnieuw tonen)\",\n showStatus: \"Toon 'nu open / gesloten'\",\n showFilter: \"Filterknoppen per categorie\",\n lightbox: \"Foto groot openen bij klikken\",\n showDownload: \"Downloadknop tonen\",\n includeNews: \"Ook nieuwsberichten doorzoeken\",\n grayscale: \"Logo's in grijstinten\",\n marquee: \"Laten doorschuiven\",\n alternate: \"Afwisselend links en rechts\",\n groupByDepartment: \"Groeperen per afdeling\",\n sticky: \"Vast onderaan het scherm\",\n jsonLd: \"Laat Google dit herkennen\",\n sections: \"Onderdelen\",\n note: \"Toelichting\",\n footnote: \"Kleine lettertjes onderaan\",\n price: \"Prijs\",\n duration: \"Duur\",\n tags: \"Labels (met komma's)\",\n vAlign: \"Tekst verticaal\",\n trust: \"Vertrouwenspunten (✓)\",\n status: \"Status\",\n start: \"Begint op\",\n end: \"Eindigt op\",\n address: \"Adres (als het niet bij jou is)\",\n events: \"Activiteiten\",\n icalUrl: \"iCal-link (agenda-export)\",\n months: \"Aantal maanden\",\n minNights: \"Minimaal aantal nachten\",\n requestPath: \"Pagina met het aanvraagformulier\",\n requestLabel: \"Tekst op de knop\",\n periodField: \"Formulierveld voor de periode (technische naam)\",\n unitField: \"Formulierveld voor de accommodatie (technische naam)\",\n reviewKnop: \"Knop ‘Laat een review achter’ (link uit Instellingen → Bedrijf)\",\n minHeight: \"Hoogte\",\n headingLevel: \"Kopniveau\",\n overlayOpacity: \"Donker maken (%)\",\n overlayGradient: \"Verloop voor leesbare tekst\",\n overlayColor: \"Kleur over de foto\",\n videoUrl: \"Video (mp4)\",\n};\n\n/** Uitleg onder een veld, waar het label alleen niet genoeg zegt. */\nconst HINTS: Record<string, string> = {\n jsonLd: \"Gestructureerde gegevens voor Google (bijvoorbeeld openingstijden of vacatures in de zoekresultaten).\",\n dismissVersion: \"Verhoog dit getal om de balk opnieuw te tonen aan bezoekers die hem al wegklikten.\",\n alt: \"Voor slechtzienden en Google: wat staat er op de foto?\",\n icon: \"Een emoji, bijvoorbeeld 🌱 of ✂️\",\n prefilledText: \"Staat alvast in het WhatsApp-bericht van de bezoeker.\",\n};\n\n/** Velden die alleen Xi Webdesign hoeft te zien (technisch, of met gevolgen buiten de pagina). */\nexport const TECHNISCHE_VELDEN = new Set([\"dismissVersion\"]);\n\n/** Velden met lopende tekst → tekstvak; al het andere tekstachtige wordt een gewoon invoerveld. */\nconst LANGE_TEKST = new Set([\"text\", \"description\", \"bio\", \"quote\", \"intro\", \"body\", \"prefilledText\", \"detail\", \"note\"]);\n\n/** Velden die een foto zijn, ook als de sleutel dat niet verraadt. */\nconst FOTO = new Set([\"beforeUrl\", \"afterUrl\", \"src\"]);\nconst ICOON = new Set([\"icon\"]);\nconst DATUM = new Set([\"datePosted\", \"date\"]);\n\nconst OPTIES: Record<string, Record<string, string>> = {\n tone: { info: \"Informatie (blauw)\", success: \"Positief (groen)\", warning: \"Let op (oranje)\" },\n imageStyle: { inset: \"Ingekaderd\", cover: \"Over de hele kaart\" },\n variant: { line: \"Lijn\", dots: \"Puntjes\", space: \"Alleen ruimte\", primary: \"Primair\", secondary: \"Secundair\", outline: \"Omlijnd\", lijst: \"Lijst\", menu: \"Menukaart\", kolommen: \"Kolommen\" },\n buttonVariant: { primary: \"Primair\", secondary: \"Secundair\", outline: \"Omlijnd\" },\n position: { br: \"Rechtsonder\", bl: \"Linksonder\" },\n align: { left: \"Links\", center: \"Midden\", right: \"Rechts\" },\n headingLevel: { \"1\": \"Hoofdkop (H1)\", \"2\": \"Tussenkop (H2)\" },\n status: { afgelast: \"Afgelast\", verplaatst: \"Verplaatst\", online: \"Online\" },\n layout: { list: \"Lijst\", grid: \"Raster\", overlay: \"Tekst op de foto\", naast: \"Tekst naast de foto\" },\n};\n/** Waarden die overal hetzelfde betekenen. */\nconst ALGEMEEN: Record<string, string> = {\n sm: \"Klein\",\n md: \"Normaal\",\n lg: \"Groot\",\n xl: \"Extra groot\",\n full: \"Volledig scherm\",\n auto: \"Automatisch\",\n \"16/9\": \"Breed (16:9)\",\n \"4/3\": \"Standaard (4:3)\",\n \"3/2\": \"Foto (3:2)\",\n \"1/1\": \"Vierkant\",\n \"21/9\": \"Panorama (21:9)\",\n fixed: \"Vaste hoogte\",\n top: \"Boven\",\n center: \"Midden\",\n bottom: \"Onder\",\n left: \"Links\",\n right: \"Rechts\",\n youtube: \"YouTube\",\n vimeo: \"Vimeo\",\n file: \"Eigen bestand\",\n osm: \"OpenStreetMap\",\n google: \"Google Maps\",\n inherit: \"Zoals de rest van de site\",\n on: \"Aan\",\n off: \"Uit\",\n message: \"Melding tonen\",\n hide: \"Verbergen\",\n};\n\n/** Hoe heet één onderdeel van een lijst, per blok (\"+ Kaart\", \"+ Teamlid\"). */\nconst ITEMNAAM: Record<string, Record<string, string>> = {\n hero: { buttons: \"Knop\" },\n availability: { units: \"Accommodatie\" },\n priceList: { sections: \"Onderdeel\", \"sections.items\": \"Regel\" },\n faq: { items: \"Vraag\" },\n accordion: { items: \"Onderdeel\" },\n pricing: { items: \"Pakket\" },\n gallery: { images: \"Foto\" },\n slider: { slides: \"Slide\" },\n cards: { items: \"Kaart\" },\n counters: { items: \"Getal\" },\n testimonials: { items: \"Review\" },\n team: { members: \"Teamlid\", \"members.social\": \"Social-link\" },\n timeline: { items: \"Stap\" },\n jobs: { items: \"Vacature\" },\n logos: { items: \"Logo\" },\n downloads: { items: \"Document\" },\n pdf: { documents: \"Document\" },\n portfolio: { items: \"Project\" },\n buttons: { items: \"Knop\" },\n social: { items: \"Social-link\" },\n openingHours: { rows: \"Regel\" },\n};\n/** En de lijst zelf, per blok. */\nconst LIJSTNAAM: Record<string, Record<string, string>> = {\n cards: { items: \"Kaarten\" },\n availability: { units: \"Accommodaties\" },\n priceList: { sections: \"Onderdelen van de lijst\", \"sections.items\": \"Regels\" },\n counters: { items: \"Getallen\" },\n testimonials: { items: \"Reviews\" },\n timeline: { items: \"Stappen\" },\n jobs: { items: \"Vacatures\" },\n logos: { items: \"Logo's\" },\n downloads: { items: \"Documenten\" },\n portfolio: { items: \"Projecten\" },\n buttons: { items: \"Knoppen\" },\n social: { items: \"Links\" },\n openingHours: { rows: \"Openingstijden\" },\n};\n\n/** \"imageStyle\" → \"Image style\" als het echt nergens staat — beter dan een camelCase-sleutel. */\nexport function mensVanSleutel(sleutel: string): string {\n const woorden = sleutel.replace(/([a-z])([A-Z])/g, \"$1 $2\").replace(/[_-]+/g, \" \").toLowerCase();\n return woorden.charAt(0).toUpperCase() + woorden.slice(1);\n}\n\nexport function optieLabel(veld: string, waarde: string): string {\n return OPTIES[veld]?.[waarde] ?? ALGEMEEN[waarde] ?? waarde;\n}\n\n/** Het Nederlandse label voor een veldsleutel (de laatste schakel van een pad). */\nexport function veldLabel(sleutel: string): string {\n return LABELS[sleutel] ?? mensVanSleutel(sleutel);\n}\n\nexport function itemNaam(blokType: string, pad: string): string | undefined {\n return ITEMNAAM[blokType]?.[pad];\n}\n\n/**\n * Vertaalt een afgeleide Field-IR. Alleen waar het schema/de hint niets zei: een label dat gelijk\n * is aan de sleutel gold als \"niet gezet\". Recursief door groepen en lijsten.\n */\nexport function vertaalVelden(velden: Field[], blokType: string, pad = \"\"): Field[] {\n return velden.map((f) => {\n const vol = pad ? `${pad}.${f.key}` : f.key;\n const labelGezet = f.label && f.label !== f.key;\n const label = labelGezet ? f.label : (LIJSTNAAM[blokType]?.[vol] ?? veldLabel(f.key));\n let uit = { ...f, label, hint: f.hint ?? HINTS[f.key] } as Field;\n\n if (uit.type === \"textarea\" && !LANGE_TEKST.has(f.key)) uit = { ...uit, type: \"text\" } as Field;\n if ((uit.type === \"text\" || uit.type === \"textarea\" || uit.type === \"url\") && FOTO.has(f.key)) uit = { ...uit, type: \"image\" } as Field;\n if ((uit.type === \"text\" || uit.type === \"textarea\") && ICOON.has(f.key)) uit = { ...uit, type: \"icon\" } as Field;\n if ((uit.type === \"text\" || uit.type === \"textarea\") && DATUM.has(f.key)) uit = { ...uit, type: \"date\" } as Field;\n\n if (uit.type === \"select\") {\n uit = { ...uit, options: uit.options.map((o) => ({ value: o.value, label: o.label && o.label !== o.value ? o.label : optieLabel(f.key, o.value) })) };\n }\n if ((uit.type === \"repeater\" || uit.type === \"group\") && \"fields\" in uit) {\n const sub = vertaalVelden(uit.fields as Field[], blokType, vol);\n uit = { ...uit, fields: sub, ...(uit.type === \"repeater\" && !uit.itemLabel ? { itemLabel: itemNaam(blokType, vol) ?? \"Onderdeel\" } : {}) } as Field;\n }\n return uit;\n });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/lib/veldTaal.ts"],"sourcesContent":["import type { Field } from \"../../schema/fields\";\n\n/**\n * De taallaag van de blokinstellingen (8.11).\n *\n * Het automatische formulier (GenericEditor) leidt zijn velden af uit het Zod-schema, en daar staan\n * sleutels: \"heading\", \"imageStyle\", \"items\". 22 van de 57 ingebouwde blokken hebben geen eigen\n * editor, dus een klant zag bij Diensten, Team, Reviews of Openingstijden letterlijk die Engelse\n * sleutels — en keuzes als \"inset\" en \"cover\". Dit bestand vertaalt ze, op één plek, voor alle\n * blokken tegelijk (ook maatwerkblokken die dezelfde sleutels gebruiken). Een `.describe()` op het\n * schema of een `fields`-hint van het blok wint altijd: dit is de terugval, niet de baas.\n *\n * Ook het TYPE wordt hier rechtgezet waar de afleiding raadt: elke korte tekst in de schema's mag\n * 2000 tekens, en \"langer dan 500 = tekstvak\" maakte van elke kop een groot invoervak. Alleen velden\n * die echt lopende tekst zijn, worden een tekstvak.\n */\n\nconst LABELS: Record<string, string> = {\n heading: \"Kop\",\n intro: \"Inleiding\",\n title: \"Titel\",\n subtitle: \"Ondertitel\",\n eyebrow: \"Regeltje boven de titel\",\n text: \"Tekst\",\n description: \"Omschrijving\",\n body: \"Tekst\",\n bio: \"Over deze persoon\",\n quote: \"Citaat\",\n author: \"Naam\",\n role: \"Functie of plaats\",\n name: \"Naam\",\n label: \"Tekst\",\n linkLabel: \"Tekst van de link\",\n buttonLabel: \"Knoptekst\",\n buttonHref: \"Knoplink\",\n buttonVariant: \"Knopstijl\",\n button2Label: \"Tweede knop: tekst\",\n button2Href: \"Tweede knop: link\",\n ctaLabel: \"Knoptekst\",\n ctaHref: \"Knoplink\",\n href: \"Link\",\n url: \"Adres\",\n applyUrl: \"Link om te solliciteren\",\n icon: \"Icoon\",\n imageUrl: \"Foto\",\n photoUrl: \"Foto\",\n avatarUrl: \"Foto\",\n beforeUrl: \"Foto vóór\",\n afterUrl: \"Foto na\",\n beforeLabel: \"Label vóór\",\n afterLabel: \"Label na\",\n src: \"Logo\",\n alt: \"Beschrijving van de foto\",\n columns: \"Kolommen\",\n imageStyle: \"Fotostijl\",\n layout: \"Weergave\",\n align: \"Uitlijning\",\n variant: \"Stijl\",\n tone: \"Kleur\",\n size: \"Grootte\",\n spacing: \"Ruimte\",\n height: \"Hoogte\",\n aspectRatio: \"Verhouding\",\n position: \"Plaats op het scherm\",\n items: \"Onderdelen\",\n members: \"Teamleden\",\n rows: \"Regels\",\n documents: \"Documenten\",\n social: \"Social media\",\n platform: \"Platform\",\n featured: \"Uitgelicht\",\n rating: \"Sterren\",\n value: \"Getal\",\n prefix: \"Vóór het getal\",\n suffix: \"Na het getal\",\n animate: \"Laten optellen bij in beeld komen\",\n day: \"Dag\",\n hours: \"Tijden\",\n date: \"Datum\",\n datePosted: \"Geplaatst op\",\n category: \"Categorie\",\n location: \"Plaats\",\n employmentType: \"Soort dienstverband\",\n salary: \"Salaris\",\n department: \"Afdeling\",\n email: \"E-mailadres\",\n phone: \"Telefoonnummer\",\n meta: \"Extra info (bv. 'PDF · 2 MB')\",\n prefilledText: \"Begintekst van het bericht\",\n placeholder: \"Tekst in het zoekveld\",\n homeLabel: \"Naam van de homepage\",\n dismissible: \"Bezoeker kan de balk wegklikken\",\n dismissVersion: \"Versie (voor opnieuw tonen)\",\n showStatus: \"Toon 'nu open / gesloten'\",\n showFilter: \"Filterknoppen per categorie\",\n lightbox: \"Foto groot openen bij klikken\",\n showDownload: \"Downloadknop tonen\",\n includeNews: \"Ook nieuwsberichten doorzoeken\",\n grayscale: \"Logo's in grijstinten\",\n marquee: \"Laten doorschuiven\",\n alternate: \"Afwisselend links en rechts\",\n groupByDepartment: \"Groeperen per afdeling\",\n sticky: \"Vast onderaan het scherm\",\n jsonLd: \"Laat Google dit herkennen\",\n sections: \"Onderdelen\",\n note: \"Toelichting\",\n footnote: \"Kleine lettertjes onderaan\",\n price: \"Prijs\",\n duration: \"Duur\",\n tags: \"Labels (met komma's)\",\n vAlign: \"Tekst verticaal\",\n trust: \"Vertrouwenspunten (✓)\",\n status: \"Status\",\n start: \"Begint op\",\n end: \"Eindigt op\",\n address: \"Adres (als het niet bij jou is)\",\n events: \"Activiteiten\",\n icalUrl: \"iCal-link (agenda-export)\",\n months: \"Aantal maanden\",\n minNights: \"Minimaal aantal nachten\",\n requestPath: \"Pagina met het aanvraagformulier\",\n requestLabel: \"Tekst op de knop\",\n periodField: \"Formulierveld voor de periode (technische naam)\",\n unitField: \"Formulierveld voor de accommodatie (technische naam)\",\n bedrijfsnummers: \"KvK- en btw-nummer tonen (uit Instellingen → Bedrijf)\",\n reviewKnop: \"Knop ‘Laat een review achter’ (link uit Instellingen → Bedrijf)\",\n minHeight: \"Hoogte\",\n headingLevel: \"Kopniveau\",\n overlayOpacity: \"Donker maken (%)\",\n overlayGradient: \"Verloop voor leesbare tekst\",\n overlayColor: \"Kleur over de foto\",\n videoUrl: \"Video (mp4)\",\n};\n\n/** Uitleg onder een veld, waar het label alleen niet genoeg zegt. */\nconst HINTS: Record<string, string> = {\n jsonLd: \"Gestructureerde gegevens voor Google (bijvoorbeeld openingstijden of vacatures in de zoekresultaten).\",\n dismissVersion: \"Verhoog dit getal om de balk opnieuw te tonen aan bezoekers die hem al wegklikten.\",\n alt: \"Voor slechtzienden en Google: wat staat er op de foto?\",\n icon: \"Een emoji, bijvoorbeeld 🌱 of ✂️\",\n prefilledText: \"Staat alvast in het WhatsApp-bericht van de bezoeker.\",\n};\n\n/** Velden die alleen Xi Webdesign hoeft te zien (technisch, of met gevolgen buiten de pagina). */\nexport const TECHNISCHE_VELDEN = new Set([\"dismissVersion\"]);\n\n/** Velden met lopende tekst → tekstvak; al het andere tekstachtige wordt een gewoon invoerveld. */\nconst LANGE_TEKST = new Set([\"text\", \"description\", \"bio\", \"quote\", \"intro\", \"body\", \"prefilledText\", \"detail\", \"note\"]);\n\n/** Velden die een foto zijn, ook als de sleutel dat niet verraadt. */\nconst FOTO = new Set([\"beforeUrl\", \"afterUrl\", \"src\"]);\nconst ICOON = new Set([\"icon\"]);\nconst DATUM = new Set([\"datePosted\", \"date\"]);\n\nconst OPTIES: Record<string, Record<string, string>> = {\n tone: { info: \"Informatie (blauw)\", success: \"Positief (groen)\", warning: \"Let op (oranje)\" },\n imageStyle: { inset: \"Ingekaderd\", cover: \"Over de hele kaart\" },\n variant: { line: \"Lijn\", dots: \"Puntjes\", space: \"Alleen ruimte\", primary: \"Primair\", secondary: \"Secundair\", outline: \"Omlijnd\", lijst: \"Lijst\", menu: \"Menukaart\", kolommen: \"Kolommen\" },\n buttonVariant: { primary: \"Primair\", secondary: \"Secundair\", outline: \"Omlijnd\" },\n position: { br: \"Rechtsonder\", bl: \"Linksonder\" },\n align: { left: \"Links\", center: \"Midden\", right: \"Rechts\" },\n headingLevel: { \"1\": \"Hoofdkop (H1)\", \"2\": \"Tussenkop (H2)\" },\n status: { afgelast: \"Afgelast\", verplaatst: \"Verplaatst\", online: \"Online\" },\n layout: { list: \"Lijst\", grid: \"Raster\", overlay: \"Tekst op de foto\", naast: \"Tekst naast de foto\" },\n};\n/** Waarden die overal hetzelfde betekenen. */\nconst ALGEMEEN: Record<string, string> = {\n sm: \"Klein\",\n md: \"Normaal\",\n lg: \"Groot\",\n xl: \"Extra groot\",\n full: \"Volledig scherm\",\n auto: \"Automatisch\",\n \"16/9\": \"Breed (16:9)\",\n \"4/3\": \"Standaard (4:3)\",\n \"3/2\": \"Foto (3:2)\",\n \"1/1\": \"Vierkant\",\n \"21/9\": \"Panorama (21:9)\",\n fixed: \"Vaste hoogte\",\n top: \"Boven\",\n center: \"Midden\",\n bottom: \"Onder\",\n left: \"Links\",\n right: \"Rechts\",\n youtube: \"YouTube\",\n vimeo: \"Vimeo\",\n file: \"Eigen bestand\",\n osm: \"OpenStreetMap\",\n google: \"Google Maps\",\n inherit: \"Zoals de rest van de site\",\n on: \"Aan\",\n off: \"Uit\",\n message: \"Melding tonen\",\n hide: \"Verbergen\",\n};\n\n/** Hoe heet één onderdeel van een lijst, per blok (\"+ Kaart\", \"+ Teamlid\"). */\nconst ITEMNAAM: Record<string, Record<string, string>> = {\n hero: { buttons: \"Knop\" },\n availability: { units: \"Accommodatie\" },\n priceList: { sections: \"Onderdeel\", \"sections.items\": \"Regel\" },\n faq: { items: \"Vraag\" },\n accordion: { items: \"Onderdeel\" },\n pricing: { items: \"Pakket\" },\n gallery: { images: \"Foto\" },\n slider: { slides: \"Slide\" },\n cards: { items: \"Kaart\" },\n counters: { items: \"Getal\" },\n testimonials: { items: \"Review\" },\n team: { members: \"Teamlid\", \"members.social\": \"Social-link\" },\n timeline: { items: \"Stap\" },\n jobs: { items: \"Vacature\" },\n logos: { items: \"Logo\" },\n downloads: { items: \"Document\" },\n pdf: { documents: \"Document\" },\n portfolio: { items: \"Project\" },\n buttons: { items: \"Knop\" },\n social: { items: \"Social-link\" },\n openingHours: { rows: \"Regel\" },\n};\n/** En de lijst zelf, per blok. */\nconst LIJSTNAAM: Record<string, Record<string, string>> = {\n cards: { items: \"Kaarten\" },\n availability: { units: \"Accommodaties\" },\n priceList: { sections: \"Onderdelen van de lijst\", \"sections.items\": \"Regels\" },\n counters: { items: \"Getallen\" },\n testimonials: { items: \"Reviews\" },\n timeline: { items: \"Stappen\" },\n jobs: { items: \"Vacatures\" },\n logos: { items: \"Logo's\" },\n downloads: { items: \"Documenten\" },\n portfolio: { items: \"Projecten\" },\n buttons: { items: \"Knoppen\" },\n social: { items: \"Links\" },\n openingHours: { rows: \"Openingstijden\" },\n};\n\n/** \"imageStyle\" → \"Image style\" als het echt nergens staat — beter dan een camelCase-sleutel. */\nexport function mensVanSleutel(sleutel: string): string {\n const woorden = sleutel.replace(/([a-z])([A-Z])/g, \"$1 $2\").replace(/[_-]+/g, \" \").toLowerCase();\n return woorden.charAt(0).toUpperCase() + woorden.slice(1);\n}\n\nexport function optieLabel(veld: string, waarde: string): string {\n return OPTIES[veld]?.[waarde] ?? ALGEMEEN[waarde] ?? waarde;\n}\n\n/** Het Nederlandse label voor een veldsleutel (de laatste schakel van een pad). */\nexport function veldLabel(sleutel: string): string {\n return LABELS[sleutel] ?? mensVanSleutel(sleutel);\n}\n\nexport function itemNaam(blokType: string, pad: string): string | undefined {\n return ITEMNAAM[blokType]?.[pad];\n}\n\n/**\n * Vertaalt een afgeleide Field-IR. Alleen waar het schema/de hint niets zei: een label dat gelijk\n * is aan de sleutel gold als \"niet gezet\". Recursief door groepen en lijsten.\n */\nexport function vertaalVelden(velden: Field[], blokType: string, pad = \"\"): Field[] {\n return velden.map((f) => {\n const vol = pad ? `${pad}.${f.key}` : f.key;\n const labelGezet = f.label && f.label !== f.key;\n const label = labelGezet ? f.label : (LIJSTNAAM[blokType]?.[vol] ?? veldLabel(f.key));\n let uit = { ...f, label, hint: f.hint ?? HINTS[f.key] } as Field;\n\n if (uit.type === \"textarea\" && !LANGE_TEKST.has(f.key)) uit = { ...uit, type: \"text\" } as Field;\n if ((uit.type === \"text\" || uit.type === \"textarea\" || uit.type === \"url\") && FOTO.has(f.key)) uit = { ...uit, type: \"image\" } as Field;\n if ((uit.type === \"text\" || uit.type === \"textarea\") && ICOON.has(f.key)) uit = { ...uit, type: \"icon\" } as Field;\n if ((uit.type === \"text\" || uit.type === \"textarea\") && DATUM.has(f.key)) uit = { ...uit, type: \"date\" } as Field;\n\n if (uit.type === \"select\") {\n uit = { ...uit, options: uit.options.map((o) => ({ value: o.value, label: o.label && o.label !== o.value ? o.label : optieLabel(f.key, o.value) })) };\n }\n if ((uit.type === \"repeater\" || uit.type === \"group\") && \"fields\" in uit) {\n const sub = vertaalVelden(uit.fields as Field[], blokType, vol);\n uit = { ...uit, fields: sub, ...(uit.type === \"repeater\" && !uit.itemLabel ? { itemLabel: itemNaam(blokType, vol) ?? \"Onderdeel\" } : {}) } as Field;\n }\n return uit;\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -8,7 +8,7 @@ import { ArrayEditor } from "../ui/ArrayEditor.js";
|
|
|
8
8
|
import { LinkVeld } from "../ui/LinkVeld.js";
|
|
9
9
|
import { MediaPickerButton } from "../media/MediaPicker.js";
|
|
10
10
|
import { VorigeTerugzetten } from "./VorigeTerugzetten.js";
|
|
11
|
-
import { afwijkingDatumTekst } from "../../bedrijf/bedrijf.js";
|
|
11
|
+
import { afwijkingDatumTekst, bedrijfsnummerAdvies } from "../../bedrijf/bedrijf.js";
|
|
12
12
|
function BedrijfInstellingen({
|
|
13
13
|
siteId,
|
|
14
14
|
waarde,
|
|
@@ -36,7 +36,22 @@ function BedrijfInstellingen({
|
|
|
36
36
|
/* @__PURE__ */ jsx(Field, { label: "Telefoonnummer", children: /* @__PURE__ */ jsx(Input, { value: b.phone ?? "", autoComplete: "tel", onChange: (e) => zet({ phone: e.target.value || void 0 }), placeholder: "030 \u2013 123 45 67" }) }),
|
|
37
37
|
/* @__PURE__ */ jsx(Field, { label: "E-mailadres", children: /* @__PURE__ */ jsx(Input, { value: b.email ?? "", autoComplete: "email", onChange: (e) => zet({ email: e.target.value || void 0 }), placeholder: "info@\u2026" }) }),
|
|
38
38
|
/* @__PURE__ */ jsx(Field, { label: "WhatsApp (optioneel)", hint: "Internationaal, bv. +31 6 12345678.", children: /* @__PURE__ */ jsx(Input, { value: b.whatsapp ?? "", onChange: (e) => zet({ whatsapp: e.target.value || void 0 }) }) }),
|
|
39
|
-
/* @__PURE__ */ jsx(
|
|
39
|
+
/* @__PURE__ */ jsx(
|
|
40
|
+
Field,
|
|
41
|
+
{
|
|
42
|
+
label: "KvK-nummer",
|
|
43
|
+
hint: bedrijfsnummerAdvies("kvk", b.kvk) ?? "Verplicht om op je site te vermelden; het verschijnt onderaan je site en bij contact.",
|
|
44
|
+
children: /* @__PURE__ */ jsx(Input, { value: b.kvk ?? "", onChange: (e) => zet({ kvk: e.target.value || void 0 }), placeholder: "12345678" })
|
|
45
|
+
}
|
|
46
|
+
),
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
Field,
|
|
49
|
+
{
|
|
50
|
+
label: "Btw-nummer",
|
|
51
|
+
hint: bedrijfsnummerAdvies("vat", b.vat) ?? "Verplicht als je aan consumenten verkoopt. Komt ook op je facturen.",
|
|
52
|
+
children: /* @__PURE__ */ jsx(Input, { value: b.vat ?? "", onChange: (e) => zet({ vat: e.target.value.toUpperCase() || void 0 }), placeholder: "NL123456789B01" })
|
|
53
|
+
}
|
|
54
|
+
)
|
|
40
55
|
] }),
|
|
41
56
|
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(Field, { label: "Korte omschrijving", hint: "E\xE9n of twee zinnen over wat je doet \u2014 voor Google.", children: /* @__PURE__ */ jsx(Textarea, { rows: 2, value: b.description ?? "", onChange: (e) => zet({ description: e.target.value || void 0 }) }) }) })
|
|
42
57
|
] }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/settings/BedrijfInstellingen.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Business, BusinessException } from \"../../schema/business\";\nimport { BEDRIJFSTYPES } from \"../../schema/business\";\nimport type { HeaderSettings } from \"../../schema/settings\";\nimport { Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { LinkVeld } from \"../ui/LinkVeld\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { VorigeTerugzetten } from \"./VorigeTerugzetten\";\nimport { afwijkingDatumTekst } from \"../../bedrijf/bedrijf\";\n\n/**\n * Instellingen → Bedrijf (8.13): één plek voor adres, telefoon en openingstijden. De site gebruikt\n * dit voor Google (JSON-LD), het contact- en openingstijdenblok en de footer. Wie hier zijn\n * telefoonnummer wijzigt, wijzigt het overal.\n */\nexport function BedrijfInstellingen({\n siteId,\n waarde,\n onChange,\n dirty,\n}: {\n siteId: string;\n waarde: Business | undefined;\n onChange: (b: Business) => void;\n dirty: boolean;\n}) {\n const b = waarde ?? {};\n const zet = (patch: Partial<Business>) => onChange({ ...b, ...patch });\n const adres = b.address ?? {};\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Bedrijf</h3>\n <p className=\"mb-1 text-xs text-slate-500\">\n Eén keer invullen, overal gebruikt: in Google, in het contact- en openingstijdenblok en onderaan je site.\n </p>\n <div className=\"mb-3\"><VorigeTerugzetten siteId={siteId} sleutel=\"business\" dirty={dirty} /></div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Soort bedrijf\" hint=\"Helpt Google te begrijpen wat je doet.\">\n <Select\n value={b.type ?? \"LocalBusiness\"}\n onValueChange={(v) => zet({ type: v })}\n options={BEDRIJFSTYPES.map((t) => ({ value: t.value, label: t.label }))}\n />\n </Field>\n <Field label=\"Officiële naam (optioneel)\" hint=\"Alleen als die afwijkt van de sitenaam, bv. met B.V.\">\n <Input value={b.name ?? \"\"} onChange={(e) => zet({ name: e.target.value || undefined })} />\n </Field>\n <Field label=\"Telefoonnummer\"><Input value={b.phone ?? \"\"} autoComplete=\"tel\" onChange={(e) => zet({ phone: e.target.value || undefined })} placeholder=\"030 – 123 45 67\" /></Field>\n <Field label=\"E-mailadres\"><Input value={b.email ?? \"\"} autoComplete=\"email\" onChange={(e) => zet({ email: e.target.value || undefined })} placeholder=\"info@…\" /></Field>\n <Field label=\"WhatsApp (optioneel)\" hint=\"Internationaal, bv. +31 6 12345678.\">\n <Input value={b.whatsapp ?? \"\"} onChange={(e) => zet({ whatsapp: e.target.value || undefined })} />\n </Field>\n <Field label=\"KvK-nummer (optioneel)\"><Input value={b.kvk ?? \"\"} onChange={(e) => zet({ kvk: e.target.value || undefined })} /></Field>\n </div>\n <div className=\"mt-3\">\n <Field label=\"Korte omschrijving\" hint=\"Eén of twee zinnen over wat je doet — voor Google.\">\n <Textarea rows={2} value={b.description ?? \"\"} onChange={(e) => zet({ description: e.target.value || undefined })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Adres</h3>\n <div className=\"grid gap-3 md:grid-cols-[2fr_1fr_1.5fr]\">\n <Field label=\"Straat en huisnummer\"><Input value={adres.street ?? \"\"} autoComplete=\"street-address\" onChange={(e) => zet({ address: { ...adres, street: e.target.value || undefined } })} /></Field>\n <Field label=\"Postcode\"><Input value={adres.postalCode ?? \"\"} autoComplete=\"postal-code\" onChange={(e) => zet({ address: { ...adres, postalCode: e.target.value || undefined } })} /></Field>\n <Field label=\"Plaats\"><Input value={adres.city ?? \"\"} autoComplete=\"address-level2\" onChange={(e) => zet({ address: { ...adres, city: e.target.value || undefined } })} /></Field>\n </div>\n <p className=\"mt-2 text-xs text-slate-500\">Bezoekers krijgen een knop “Route plannen” naar dit adres.</p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Openingstijden</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Gewone taal mag: “Maandag t/m vrijdag” · “09:00 – 17:00”, of “Zondag” · “Gesloten”. Dan klopt ook “Nu geopend” op je site.\n </p>\n <ArrayEditor\n items={b.hours ?? []}\n onChange={(hours) => zet({ hours })}\n newItem={() => ({ day: \"\", hours: \"\" })}\n addLabel=\"+ Regel\"\n emptyLabel=\"Nog geen openingstijden.\"\n renderRow={(r, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={r.day} placeholder=\"Maandag t/m vrijdag\" onChange={(e) => update({ day: e.target.value })} aria-label=\"Dag(en)\" />\n <Input value={r.hours} placeholder=\"09:00 – 17:00\" onChange={(e) => update({ hours: e.target.value })} aria-label=\"Tijden\" />\n </div>\n )}\n />\n <div className=\"mt-3\">\n <Field label=\"Toelichting (optioneel)\"><Input value={b.hoursNote ?? \"\"} placeholder=\"Op afspraak ook 's avonds\" onChange={(e) => zet({ hoursNote: e.target.value || undefined })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Afwijkende dagen</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Feestdagen, vakantie, een koopavond. Ze staan op je site zodra ze binnen drie weken vallen, en Google toont ze als aangepaste tijden.\n </p>\n <ArrayEditor<BusinessException>\n items={b.exceptions ?? []}\n onChange={(exceptions) => zet({ exceptions })}\n newItem={() => ({ date: new Date().toISOString().slice(0, 10), hours: \"Gesloten\" })}\n addLabel=\"+ Afwijkende dag\"\n emptyLabel=\"Geen afwijkende dagen.\"\n renderRow={(a, update) => (\n <div className=\"grid gap-2 md:grid-cols-[auto_auto_1fr_1fr]\">\n <Field label=\"Van\"><Input type=\"date\" value={a.date} onChange={(e) => update({ date: e.target.value })} /></Field>\n <Field label=\"Tot en met (optioneel)\"><Input type=\"date\" value={a.until ?? \"\"} onChange={(e) => update({ until: e.target.value || undefined })} /></Field>\n <Field label=\"Tijden\"><Input value={a.hours} placeholder=\"Gesloten of 10:00 – 14:00\" onChange={(e) => update({ hours: e.target.value })} /></Field>\n <Field label=\"Omschrijving\" hint={/^\\d{4}-\\d{2}-\\d{2}$/.test(a.date) ? afwijkingDatumTekst(a) : undefined}>\n <Input value={a.label ?? \"\"} placeholder=\"Eerste Kerstdag\" onChange={(e) => update({ label: e.target.value || undefined })} />\n </Field>\n </div>\n )}\n />\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Reviews en profielen</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Link om een review achter te laten\" hint=\"Je Google-bedrijfsprofiel → ‘Vraag om reviews’. Het reviewblok kan er een knop ‘Beoordeel ons’ van maken.\">\n <Input value={b.reviewUrl ?? \"\"} placeholder=\"https://g.page/r/…\" onChange={(e) => zet({ reviewUrl: e.target.value || undefined })} />\n </Field>\n <Field label=\"Prijsklasse (optioneel)\" hint=\"Bv. €€ of € 25 – € 45.\">\n <Input value={b.priceRange ?? \"\"} onChange={(e) => zet({ priceRange: e.target.value || undefined })} />\n </Field>\n </div>\n </Card>\n </div>\n );\n}\n\n/** Instellingen → Kop (8.13): logo en actieknop van `SiteHeader`. */\nexport function KopInstellingen({\n siteId,\n waarde,\n onChange,\n dirty,\n}: {\n siteId: string;\n waarde: HeaderSettings | undefined;\n onChange: (h: HeaderSettings) => void;\n dirty: boolean;\n}) {\n const h = waarde ?? {};\n const zet = (patch: Partial<HeaderSettings>) => onChange({ ...h, ...patch });\n return (\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Kop van de site</h3>\n <p className=\"mb-1 text-xs text-slate-500\">Logo en de knop rechtsboven. Het menu zelf beheer je onder Menu.</p>\n <div className=\"mb-3\"><VorigeTerugzetten siteId={siteId} sleutel=\"header\" dirty={dirty} /></div>\n <div className=\"grid gap-4\">\n <Field label=\"Logo\" hint=\"Liefst een PNG of SVG met transparante achtergrond. Leeg = de naam van je site.\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {h.logo && (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={h.logo} alt=\"\" className=\"h-10 max-w-48 rounded-md border border-slate-200 bg-white object-contain p-1\" />\n )}\n <MediaPickerButton siteId={siteId} label={h.logo ? \"Ander logo\" : \"Kies een logo\"} onSelect={(m) => zet({ logo: m.url })} />\n {h.logo && (\n <button type=\"button\" className=\"xicms-admin-btn rounded px-2 py-1 text-sm text-slate-600 hover:bg-slate-100\" onClick={() => zet({ logo: undefined })}>\n Weghalen\n </button>\n )}\n </div>\n </Field>\n {h.logo && (\n <Field label=\"Hoogte van het logo\">\n <div role=\"radiogroup\" aria-label=\"Hoogte van het logo\" className=\"inline-flex gap-1\">\n {[32, 40, 52, 64].map((px) => (\n <button\n key={px}\n type=\"button\"\n role=\"radio\"\n aria-checked={(h.logoHoogte ?? 40) === px}\n onClick={() => zet({ logoHoogte: px })}\n className=\"xicms-admin-btn h-9 rounded-md border border-slate-200 px-3 text-sm aria-[checked=true]:border-slate-900 aria-[checked=true]:bg-slate-900 aria-[checked=true]:text-white\"\n >\n {px === 32 ? \"Klein\" : px === 40 ? \"Normaal\" : px === 52 ? \"Groot\" : \"Extra groot\"}\n </button>\n ))}\n </div>\n </Field>\n )}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Knoptekst rechtsboven\" hint=\"Bv. ‘Afspraak maken’ of ‘Offerte aanvragen’. Leeg = geen knop.\">\n <Input value={h.cta?.label ?? \"\"} maxLength={40} onChange={(e) => zet({ cta: { label: e.target.value, href: h.cta?.href ?? \"/contact\" } })} />\n </Field>\n <Field label=\"Waar gaat de knop heen?\">\n <LinkVeld siteId={siteId} value={h.cta?.href} onChange={(href) => zet({ cta: { label: h.cta?.label ?? \"\", href } })} />\n </Field>\n </div>\n <Switch checked={h.sticky !== false} onCheckedChange={(v) => zet({ sticky: v })} label=\"Kop blijft bovenaan staan bij scrollen\" />\n <Switch checked={Boolean(h.mobielBalk)} onCheckedChange={(v) => zet({ mobielBalk: v })} label=\"Op de telefoon een balk onderaan met “Bel ons” en de knop\" />\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/settings/BedrijfInstellingen.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Business, BusinessException } from \"../../schema/business\";\nimport { BEDRIJFSTYPES } from \"../../schema/business\";\nimport type { HeaderSettings } from \"../../schema/settings\";\nimport { Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { LinkVeld } from \"../ui/LinkVeld\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { VorigeTerugzetten } from \"./VorigeTerugzetten\";\nimport { afwijkingDatumTekst, bedrijfsnummerAdvies } from \"../../bedrijf/bedrijf\";\n\n/**\n * Instellingen → Bedrijf (8.13): één plek voor adres, telefoon en openingstijden. De site gebruikt\n * dit voor Google (JSON-LD), het contact- en openingstijdenblok en de footer. Wie hier zijn\n * telefoonnummer wijzigt, wijzigt het overal.\n */\nexport function BedrijfInstellingen({\n siteId,\n waarde,\n onChange,\n dirty,\n}: {\n siteId: string;\n waarde: Business | undefined;\n onChange: (b: Business) => void;\n dirty: boolean;\n}) {\n const b = waarde ?? {};\n const zet = (patch: Partial<Business>) => onChange({ ...b, ...patch });\n const adres = b.address ?? {};\n return (\n <div className=\"grid gap-4\">\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Bedrijf</h3>\n <p className=\"mb-1 text-xs text-slate-500\">\n Eén keer invullen, overal gebruikt: in Google, in het contact- en openingstijdenblok en onderaan je site.\n </p>\n <div className=\"mb-3\"><VorigeTerugzetten siteId={siteId} sleutel=\"business\" dirty={dirty} /></div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Soort bedrijf\" hint=\"Helpt Google te begrijpen wat je doet.\">\n <Select\n value={b.type ?? \"LocalBusiness\"}\n onValueChange={(v) => zet({ type: v })}\n options={BEDRIJFSTYPES.map((t) => ({ value: t.value, label: t.label }))}\n />\n </Field>\n <Field label=\"Officiële naam (optioneel)\" hint=\"Alleen als die afwijkt van de sitenaam, bv. met B.V.\">\n <Input value={b.name ?? \"\"} onChange={(e) => zet({ name: e.target.value || undefined })} />\n </Field>\n <Field label=\"Telefoonnummer\"><Input value={b.phone ?? \"\"} autoComplete=\"tel\" onChange={(e) => zet({ phone: e.target.value || undefined })} placeholder=\"030 – 123 45 67\" /></Field>\n <Field label=\"E-mailadres\"><Input value={b.email ?? \"\"} autoComplete=\"email\" onChange={(e) => zet({ email: e.target.value || undefined })} placeholder=\"info@…\" /></Field>\n <Field label=\"WhatsApp (optioneel)\" hint=\"Internationaal, bv. +31 6 12345678.\">\n <Input value={b.whatsapp ?? \"\"} onChange={(e) => zet({ whatsapp: e.target.value || undefined })} />\n </Field>\n <Field\n label=\"KvK-nummer\"\n hint={bedrijfsnummerAdvies(\"kvk\", b.kvk) ?? \"Verplicht om op je site te vermelden; het verschijnt onderaan je site en bij contact.\"}\n >\n <Input value={b.kvk ?? \"\"} onChange={(e) => zet({ kvk: e.target.value || undefined })} placeholder=\"12345678\" />\n </Field>\n <Field\n label=\"Btw-nummer\"\n hint={bedrijfsnummerAdvies(\"vat\", b.vat) ?? \"Verplicht als je aan consumenten verkoopt. Komt ook op je facturen.\"}\n >\n <Input value={b.vat ?? \"\"} onChange={(e) => zet({ vat: e.target.value.toUpperCase() || undefined })} placeholder=\"NL123456789B01\" />\n </Field>\n </div>\n <div className=\"mt-3\">\n <Field label=\"Korte omschrijving\" hint=\"Eén of twee zinnen over wat je doet — voor Google.\">\n <Textarea rows={2} value={b.description ?? \"\"} onChange={(e) => zet({ description: e.target.value || undefined })} />\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Adres</h3>\n <div className=\"grid gap-3 md:grid-cols-[2fr_1fr_1.5fr]\">\n <Field label=\"Straat en huisnummer\"><Input value={adres.street ?? \"\"} autoComplete=\"street-address\" onChange={(e) => zet({ address: { ...adres, street: e.target.value || undefined } })} /></Field>\n <Field label=\"Postcode\"><Input value={adres.postalCode ?? \"\"} autoComplete=\"postal-code\" onChange={(e) => zet({ address: { ...adres, postalCode: e.target.value || undefined } })} /></Field>\n <Field label=\"Plaats\"><Input value={adres.city ?? \"\"} autoComplete=\"address-level2\" onChange={(e) => zet({ address: { ...adres, city: e.target.value || undefined } })} /></Field>\n </div>\n <p className=\"mt-2 text-xs text-slate-500\">Bezoekers krijgen een knop “Route plannen” naar dit adres.</p>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Openingstijden</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Gewone taal mag: “Maandag t/m vrijdag” · “09:00 – 17:00”, of “Zondag” · “Gesloten”. Dan klopt ook “Nu geopend” op je site.\n </p>\n <ArrayEditor\n items={b.hours ?? []}\n onChange={(hours) => zet({ hours })}\n newItem={() => ({ day: \"\", hours: \"\" })}\n addLabel=\"+ Regel\"\n emptyLabel=\"Nog geen openingstijden.\"\n renderRow={(r, update) => (\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={r.day} placeholder=\"Maandag t/m vrijdag\" onChange={(e) => update({ day: e.target.value })} aria-label=\"Dag(en)\" />\n <Input value={r.hours} placeholder=\"09:00 – 17:00\" onChange={(e) => update({ hours: e.target.value })} aria-label=\"Tijden\" />\n </div>\n )}\n />\n <div className=\"mt-3\">\n <Field label=\"Toelichting (optioneel)\"><Input value={b.hoursNote ?? \"\"} placeholder=\"Op afspraak ook 's avonds\" onChange={(e) => zet({ hoursNote: e.target.value || undefined })} /></Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Afwijkende dagen</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Feestdagen, vakantie, een koopavond. Ze staan op je site zodra ze binnen drie weken vallen, en Google toont ze als aangepaste tijden.\n </p>\n <ArrayEditor<BusinessException>\n items={b.exceptions ?? []}\n onChange={(exceptions) => zet({ exceptions })}\n newItem={() => ({ date: new Date().toISOString().slice(0, 10), hours: \"Gesloten\" })}\n addLabel=\"+ Afwijkende dag\"\n emptyLabel=\"Geen afwijkende dagen.\"\n renderRow={(a, update) => (\n <div className=\"grid gap-2 md:grid-cols-[auto_auto_1fr_1fr]\">\n <Field label=\"Van\"><Input type=\"date\" value={a.date} onChange={(e) => update({ date: e.target.value })} /></Field>\n <Field label=\"Tot en met (optioneel)\"><Input type=\"date\" value={a.until ?? \"\"} onChange={(e) => update({ until: e.target.value || undefined })} /></Field>\n <Field label=\"Tijden\"><Input value={a.hours} placeholder=\"Gesloten of 10:00 – 14:00\" onChange={(e) => update({ hours: e.target.value })} /></Field>\n <Field label=\"Omschrijving\" hint={/^\\d{4}-\\d{2}-\\d{2}$/.test(a.date) ? afwijkingDatumTekst(a) : undefined}>\n <Input value={a.label ?? \"\"} placeholder=\"Eerste Kerstdag\" onChange={(e) => update({ label: e.target.value || undefined })} />\n </Field>\n </div>\n )}\n />\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-base font-semibold\">Reviews en profielen</h3>\n <div className=\"grid gap-3\">\n <Field label=\"Link om een review achter te laten\" hint=\"Je Google-bedrijfsprofiel → ‘Vraag om reviews’. Het reviewblok kan er een knop ‘Beoordeel ons’ van maken.\">\n <Input value={b.reviewUrl ?? \"\"} placeholder=\"https://g.page/r/…\" onChange={(e) => zet({ reviewUrl: e.target.value || undefined })} />\n </Field>\n <Field label=\"Prijsklasse (optioneel)\" hint=\"Bv. €€ of € 25 – € 45.\">\n <Input value={b.priceRange ?? \"\"} onChange={(e) => zet({ priceRange: e.target.value || undefined })} />\n </Field>\n </div>\n </Card>\n </div>\n );\n}\n\n/** Instellingen → Kop (8.13): logo en actieknop van `SiteHeader`. */\nexport function KopInstellingen({\n siteId,\n waarde,\n onChange,\n dirty,\n}: {\n siteId: string;\n waarde: HeaderSettings | undefined;\n onChange: (h: HeaderSettings) => void;\n dirty: boolean;\n}) {\n const h = waarde ?? {};\n const zet = (patch: Partial<HeaderSettings>) => onChange({ ...h, ...patch });\n return (\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Kop van de site</h3>\n <p className=\"mb-1 text-xs text-slate-500\">Logo en de knop rechtsboven. Het menu zelf beheer je onder Menu.</p>\n <div className=\"mb-3\"><VorigeTerugzetten siteId={siteId} sleutel=\"header\" dirty={dirty} /></div>\n <div className=\"grid gap-4\">\n <Field label=\"Logo\" hint=\"Liefst een PNG of SVG met transparante achtergrond. Leeg = de naam van je site.\">\n <div className=\"flex flex-wrap items-center gap-3\">\n {h.logo && (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={h.logo} alt=\"\" className=\"h-10 max-w-48 rounded-md border border-slate-200 bg-white object-contain p-1\" />\n )}\n <MediaPickerButton siteId={siteId} label={h.logo ? \"Ander logo\" : \"Kies een logo\"} onSelect={(m) => zet({ logo: m.url })} />\n {h.logo && (\n <button type=\"button\" className=\"xicms-admin-btn rounded px-2 py-1 text-sm text-slate-600 hover:bg-slate-100\" onClick={() => zet({ logo: undefined })}>\n Weghalen\n </button>\n )}\n </div>\n </Field>\n {h.logo && (\n <Field label=\"Hoogte van het logo\">\n <div role=\"radiogroup\" aria-label=\"Hoogte van het logo\" className=\"inline-flex gap-1\">\n {[32, 40, 52, 64].map((px) => (\n <button\n key={px}\n type=\"button\"\n role=\"radio\"\n aria-checked={(h.logoHoogte ?? 40) === px}\n onClick={() => zet({ logoHoogte: px })}\n className=\"xicms-admin-btn h-9 rounded-md border border-slate-200 px-3 text-sm aria-[checked=true]:border-slate-900 aria-[checked=true]:bg-slate-900 aria-[checked=true]:text-white\"\n >\n {px === 32 ? \"Klein\" : px === 40 ? \"Normaal\" : px === 52 ? \"Groot\" : \"Extra groot\"}\n </button>\n ))}\n </div>\n </Field>\n )}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Knoptekst rechtsboven\" hint=\"Bv. ‘Afspraak maken’ of ‘Offerte aanvragen’. Leeg = geen knop.\">\n <Input value={h.cta?.label ?? \"\"} maxLength={40} onChange={(e) => zet({ cta: { label: e.target.value, href: h.cta?.href ?? \"/contact\" } })} />\n </Field>\n <Field label=\"Waar gaat de knop heen?\">\n <LinkVeld siteId={siteId} value={h.cta?.href} onChange={(href) => zet({ cta: { label: h.cta?.label ?? \"\", href } })} />\n </Field>\n </div>\n <Switch checked={h.sticky !== false} onCheckedChange={(v) => zet({ sticky: v })} label=\"Kop blijft bovenaan staan bij scrollen\" />\n <Switch checked={Boolean(h.mobielBalk)} onCheckedChange={(v) => zet({ mobielBalk: v })} label=\"Op de telefoon een balk onderaan met “Bel ons” en de knop\" />\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,11 +16,13 @@ function ShopSettingsTab({ siteId }) {
|
|
|
16
16
|
const [giftWrapInput, setGiftWrapInput] = useState("");
|
|
17
17
|
const [busy, setBusy] = useState(false);
|
|
18
18
|
const [zones, setZones] = useState([]);
|
|
19
|
+
const [profiel, setProfiel] = useState({});
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
const db = getDb();
|
|
21
22
|
if (!db) return;
|
|
22
23
|
void getDoc(doc(db, "sites", siteId, "settings", "public")).then((snap) => {
|
|
23
24
|
const modules = snap.data()?.modules ?? {};
|
|
25
|
+
setProfiel(snap.data()?.business ?? {});
|
|
24
26
|
const entry = modules.shop;
|
|
25
27
|
const c = typeof entry === "object" && entry !== null ? entry.config ?? {} : {};
|
|
26
28
|
setCfg(c);
|
|
@@ -70,14 +72,25 @@ function ShopSettingsTab({ siteId }) {
|
|
|
70
72
|
}
|
|
71
73
|
if (!cfg) return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Laden\u2026" }) });
|
|
72
74
|
const set = (k, v) => setCfg((p) => ({ ...p, [k]: v }));
|
|
75
|
+
const uitProfiel = {
|
|
76
|
+
businessName: profiel.name,
|
|
77
|
+
businessAddress: profiel.address?.street,
|
|
78
|
+
businessZip: profiel.address?.postalCode,
|
|
79
|
+
businessCity: profiel.address?.city,
|
|
80
|
+
businessKvk: profiel.kvk,
|
|
81
|
+
businessBtw: profiel.vat,
|
|
82
|
+
businessEmail: profiel.email,
|
|
83
|
+
businessPhone: profiel.phone
|
|
84
|
+
};
|
|
73
85
|
const ontbrekend = [
|
|
74
|
-
["bedrijfsnaam",
|
|
75
|
-
["adres",
|
|
76
|
-
["postcode",
|
|
77
|
-
["plaats",
|
|
78
|
-
["KVK-nummer",
|
|
79
|
-
["btw-nummer",
|
|
80
|
-
].filter(([,
|
|
86
|
+
["bedrijfsnaam", "businessName"],
|
|
87
|
+
["adres", "businessAddress"],
|
|
88
|
+
["postcode", "businessZip"],
|
|
89
|
+
["plaats", "businessCity"],
|
|
90
|
+
["KVK-nummer", "businessKvk"],
|
|
91
|
+
["btw-nummer", "businessBtw"]
|
|
92
|
+
].filter(([, k]) => !String(cfg[k] ?? "").trim() && !uitProfiel[k]?.trim()).map(([label]) => label);
|
|
93
|
+
const veld = (k) => ({ value: String(cfg[k] ?? ""), onChange: (e) => set(k, e.target.value), placeholder: uitProfiel[k] });
|
|
81
94
|
return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [
|
|
82
95
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
83
96
|
/* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold", children: "Verzending & bestellen" }),
|
|
@@ -147,7 +160,7 @@ function ShopSettingsTab({ siteId }) {
|
|
|
147
160
|
] }),
|
|
148
161
|
/* @__PURE__ */ jsxs(Card, { children: [
|
|
149
162
|
/* @__PURE__ */ jsx("h3", { className: "mb-1 text-sm font-semibold", children: "Bedrijfsgegevens (factuur)" }),
|
|
150
|
-
/* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen." }),
|
|
163
|
+
/* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen. Laat je een veld leeg, dan gebruiken we wat er bij Instellingen \u2192 Bedrijf staat (grijs voorgevuld)." }),
|
|
151
164
|
ontbrekend.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mb-3 rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800", children: [
|
|
152
165
|
/* @__PURE__ */ jsx("strong", { children: "Nog invullen:" }),
|
|
153
166
|
" ",
|
|
@@ -155,19 +168,19 @@ function ShopSettingsTab({ siteId }) {
|
|
|
155
168
|
". Zonder deze gegevens staat er geen afzender op je factuur, en dan is hij voor de Belastingdienst niet geldig."
|
|
156
169
|
] }),
|
|
157
170
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
158
|
-
/* @__PURE__ */ jsx(Field, { label: "Bedrijfsnaam", children: /* @__PURE__ */ jsx(Input, {
|
|
159
|
-
/* @__PURE__ */ jsx(Field, { label: "Adres", children: /* @__PURE__ */ jsx(Input, {
|
|
171
|
+
/* @__PURE__ */ jsx(Field, { label: "Bedrijfsnaam", children: /* @__PURE__ */ jsx(Input, { ...veld("businessName") }) }),
|
|
172
|
+
/* @__PURE__ */ jsx(Field, { label: "Adres", children: /* @__PURE__ */ jsx(Input, { ...veld("businessAddress") }) }),
|
|
160
173
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[120px_1fr] gap-3", children: [
|
|
161
|
-
/* @__PURE__ */ jsx(Field, { label: "Postcode", children: /* @__PURE__ */ jsx(Input, {
|
|
162
|
-
/* @__PURE__ */ jsx(Field, { label: "Plaats", children: /* @__PURE__ */ jsx(Input, {
|
|
174
|
+
/* @__PURE__ */ jsx(Field, { label: "Postcode", children: /* @__PURE__ */ jsx(Input, { ...veld("businessZip") }) }),
|
|
175
|
+
/* @__PURE__ */ jsx(Field, { label: "Plaats", children: /* @__PURE__ */ jsx(Input, { ...veld("businessCity") }) })
|
|
163
176
|
] }),
|
|
164
177
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
165
|
-
/* @__PURE__ */ jsx(Field, { label: "KVK-nummer", children: /* @__PURE__ */ jsx(Input, {
|
|
166
|
-
/* @__PURE__ */ jsx(Field, { label: "Btw-nummer", children: /* @__PURE__ */ jsx(Input, {
|
|
178
|
+
/* @__PURE__ */ jsx(Field, { label: "KVK-nummer", children: /* @__PURE__ */ jsx(Input, { ...veld("businessKvk") }) }),
|
|
179
|
+
/* @__PURE__ */ jsx(Field, { label: "Btw-nummer", children: /* @__PURE__ */ jsx(Input, { ...veld("businessBtw") }) })
|
|
167
180
|
] }),
|
|
168
181
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
169
|
-
/* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", hint: "Staat op de factuur en onderaan elke mail", children: /* @__PURE__ */ jsx(Input, {
|
|
170
|
-
/* @__PURE__ */ jsx(Field, { label: "Telefoon (optioneel)", children: /* @__PURE__ */ jsx(Input, {
|
|
182
|
+
/* @__PURE__ */ jsx(Field, { label: "Contact-e-mail", hint: "Staat op de factuur en onderaan elke mail", children: /* @__PURE__ */ jsx(Input, { ...veld("businessEmail") }) }),
|
|
183
|
+
/* @__PURE__ */ jsx(Field, { label: "Telefoon (optioneel)", children: /* @__PURE__ */ jsx(Input, { ...veld("businessPhone") }) })
|
|
171
184
|
] })
|
|
172
185
|
] })
|
|
173
186
|
] }),
|