@xiwebdesign/xicms-core 7.30.0 → 8.15.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.
Files changed (463) hide show
  1. package/dist/XiCMSProvider.js +22 -7
  2. package/dist/XiCMSProvider.js.map +1 -1
  3. package/dist/admin/AdminApp.js +175 -93
  4. package/dist/admin/AdminApp.js.map +1 -1
  5. package/dist/admin/AdminHelp.d.ts +1 -1
  6. package/dist/admin/AdminHelp.js +44 -36
  7. package/dist/admin/AdminHelp.js.map +1 -1
  8. package/dist/admin/CoreVersion.d.ts +6 -3
  9. package/dist/admin/CoreVersion.js +60 -24
  10. package/dist/admin/CoreVersion.js.map +1 -1
  11. package/dist/admin/NewsEditor.d.ts +7 -0
  12. package/dist/admin/NewsEditor.js +117 -75
  13. package/dist/admin/NewsEditor.js.map +1 -1
  14. package/dist/admin/SuperadminPanel.js +1 -1
  15. package/dist/admin/SuperadminPanel.js.map +1 -1
  16. package/dist/admin/ZoekPalet.d.ts +15 -0
  17. package/dist/admin/ZoekPalet.js +123 -0
  18. package/dist/admin/ZoekPalet.js.map +1 -0
  19. package/dist/admin/blockEditors/GenericEditor.js +36 -18
  20. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  21. package/dist/admin/blockEditors/StyleEditor.js +37 -3
  22. package/dist/admin/blockEditors/StyleEditor.js.map +1 -1
  23. package/dist/admin/blockEditors/SymbolRefEditor.js +44 -2
  24. package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -1
  25. package/dist/admin/blockEditors/index.js +154 -73
  26. package/dist/admin/blockEditors/index.js.map +1 -1
  27. package/dist/admin/customBlocks/CustomBlocksPanel.js +18 -7
  28. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  29. package/dist/admin/dashboard/DashboardPanel.js +26 -16
  30. package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
  31. package/dist/admin/editors/RichTextEditor.d.ts +4 -3
  32. package/dist/admin/editors/RichTextEditor.js +19 -56
  33. package/dist/admin/editors/RichTextEditor.js.map +1 -1
  34. package/dist/admin/editors/fontSizeExtension.js +7 -1
  35. package/dist/admin/editors/fontSizeExtension.js.map +1 -1
  36. package/dist/admin/editors/slashMenu.d.ts +18 -0
  37. package/dist/admin/editors/slashMenu.js +34 -0
  38. package/dist/admin/editors/slashMenu.js.map +1 -0
  39. package/dist/admin/editors/useRijkeTekst.d.ts +48 -0
  40. package/dist/admin/editors/useRijkeTekst.js +310 -0
  41. package/dist/admin/editors/useRijkeTekst.js.map +1 -0
  42. package/dist/admin/forms/FormsPanel.d.ts +11 -2
  43. package/dist/admin/forms/FormsPanel.js +218 -65
  44. package/dist/admin/forms/FormsPanel.js.map +1 -1
  45. package/dist/admin/lib/blokInfo.d.ts +17 -0
  46. package/dist/admin/lib/blokInfo.js +80 -0
  47. package/dist/admin/lib/blokInfo.js.map +1 -0
  48. package/dist/admin/lib/foutNaarMens.d.ts +1 -0
  49. package/dist/admin/lib/foutNaarMens.js +22 -0
  50. package/dist/admin/lib/foutNaarMens.js.map +1 -0
  51. package/dist/admin/lib/gewisteVelden.d.ts +13 -0
  52. package/dist/admin/lib/gewisteVelden.js +31 -0
  53. package/dist/admin/lib/gewisteVelden.js.map +1 -0
  54. package/dist/admin/lib/heroHulp.d.ts +11 -0
  55. package/dist/admin/lib/heroHulp.js +18 -0
  56. package/dist/admin/lib/heroHulp.js.map +1 -0
  57. package/dist/admin/lib/kleur.js +2 -3
  58. package/dist/admin/lib/kleur.js.map +1 -1
  59. package/dist/admin/lib/leesbaarheid.d.ts +34 -0
  60. package/dist/admin/lib/leesbaarheid.js +82 -0
  61. package/dist/admin/lib/leesbaarheid.js.map +1 -0
  62. package/dist/admin/lib/lengteAdvies.d.ts +1 -0
  63. package/dist/admin/lib/lengteAdvies.js +16 -0
  64. package/dist/admin/lib/lengteAdvies.js.map +1 -0
  65. package/dist/admin/lib/mediaLijst.d.ts +26 -0
  66. package/dist/admin/lib/mediaLijst.js +42 -0
  67. package/dist/admin/lib/mediaLijst.js.map +1 -0
  68. package/dist/admin/lib/mediaUsage.d.ts +12 -1
  69. package/dist/admin/lib/mediaUsage.js +43 -33
  70. package/dist/admin/lib/mediaUsage.js.map +1 -1
  71. package/dist/admin/lib/menuHulp.d.ts +16 -0
  72. package/dist/admin/lib/menuHulp.js +41 -0
  73. package/dist/admin/lib/menuHulp.js.map +1 -0
  74. package/dist/admin/lib/onopgeslagen.d.ts +10 -0
  75. package/dist/admin/lib/onopgeslagen.js +35 -0
  76. package/dist/admin/lib/onopgeslagen.js.map +1 -0
  77. package/dist/admin/lib/pagesClient.d.ts +55 -2
  78. package/dist/admin/lib/pagesClient.js +126 -28
  79. package/dist/admin/lib/pagesClient.js.map +1 -1
  80. package/dist/admin/lib/paginaGebruik.d.ts +56 -0
  81. package/dist/admin/lib/paginaGebruik.js +109 -0
  82. package/dist/admin/lib/paginaGebruik.js.map +1 -0
  83. package/dist/admin/lib/publicatieCheck.d.ts +25 -0
  84. package/dist/admin/lib/publicatieCheck.js +114 -0
  85. package/dist/admin/lib/publicatieCheck.js.map +1 -0
  86. package/dist/admin/lib/scrollNaarDetail.d.ts +6 -0
  87. package/dist/admin/lib/scrollNaarDetail.js +20 -0
  88. package/dist/admin/lib/scrollNaarDetail.js.map +1 -0
  89. package/dist/admin/lib/symbolsClient.d.ts +10 -0
  90. package/dist/admin/lib/symbolsClient.js +14 -1
  91. package/dist/admin/lib/symbolsClient.js.map +1 -1
  92. package/dist/admin/lib/tekstVeld.d.ts +8 -0
  93. package/dist/admin/lib/tekstVeld.js +10 -0
  94. package/dist/admin/lib/tekstVeld.js.map +1 -0
  95. package/dist/admin/lib/useIsSuperadmin.d.ts +2 -0
  96. package/dist/admin/lib/useIsSuperadmin.js +13 -0
  97. package/dist/admin/lib/useIsSuperadmin.js.map +1 -0
  98. package/dist/admin/lib/veldTaal.d.ts +14 -0
  99. package/dist/admin/lib/veldTaal.js +247 -0
  100. package/dist/admin/lib/veldTaal.js.map +1 -0
  101. package/dist/admin/lib/versieVergelijk.d.ts +17 -0
  102. package/dist/admin/lib/versieVergelijk.js +24 -0
  103. package/dist/admin/lib/versieVergelijk.js.map +1 -0
  104. package/dist/admin/lib/zodFields.js +22 -4
  105. package/dist/admin/lib/zodFields.js.map +1 -1
  106. package/dist/admin/lib/zodFout.d.ts +9 -0
  107. package/dist/admin/lib/zodFout.js +63 -0
  108. package/dist/admin/lib/zodFout.js.map +1 -0
  109. package/dist/admin/media/MediaManager.js +162 -32
  110. package/dist/admin/media/MediaManager.js.map +1 -1
  111. package/dist/admin/media/MediaPicker.d.ts +3 -0
  112. package/dist/admin/media/MediaPicker.js +84 -31
  113. package/dist/admin/media/MediaPicker.js.map +1 -1
  114. package/dist/admin/menu/MenuPanel.js +144 -83
  115. package/dist/admin/menu/MenuPanel.js.map +1 -1
  116. package/dist/admin/navContext.d.ts +3 -0
  117. package/dist/admin/navContext.js +13 -1
  118. package/dist/admin/navContext.js.map +1 -1
  119. package/dist/admin/pages/PageEditor.js +146 -30
  120. package/dist/admin/pages/PageEditor.js.map +1 -1
  121. package/dist/admin/pages/PagesPanel.js +215 -61
  122. package/dist/admin/pages/PagesPanel.js.map +1 -1
  123. package/dist/admin/pages/StarterKiezer.d.ts +12 -0
  124. package/dist/admin/pages/StarterKiezer.js +113 -0
  125. package/dist/admin/pages/StarterKiezer.js.map +1 -0
  126. package/dist/admin/panelRegistry.d.ts +1 -1
  127. package/dist/admin/panelRegistry.js +2 -1
  128. package/dist/admin/panelRegistry.js.map +1 -1
  129. package/dist/admin/patterns/PatternInserter.js +1 -1
  130. package/dist/admin/patterns/PatternInserter.js.map +1 -1
  131. package/dist/admin/settings/BedrijfInstellingen.d.ts +20 -0
  132. package/dist/admin/settings/BedrijfInstellingen.js +144 -0
  133. package/dist/admin/settings/BedrijfInstellingen.js.map +1 -0
  134. package/dist/admin/settings/MailSettings.js +2 -2
  135. package/dist/admin/settings/MailSettings.js.map +1 -1
  136. package/dist/admin/settings/MailTemplates.js +5 -4
  137. package/dist/admin/settings/MailTemplates.js.map +1 -1
  138. package/dist/admin/settings/SettingsPanel.js +120 -78
  139. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  140. package/dist/admin/settings/SnapshotsCard.js +4 -4
  141. package/dist/admin/settings/SnapshotsCard.js.map +1 -1
  142. package/dist/admin/settings/VorigeTerugzetten.d.ts +15 -0
  143. package/dist/admin/settings/VorigeTerugzetten.js +89 -0
  144. package/dist/admin/settings/VorigeTerugzetten.js.map +1 -0
  145. package/dist/admin/shop/CategoriesTab.js +6 -4
  146. package/dist/admin/shop/CategoriesTab.js.map +1 -1
  147. package/dist/admin/shop/DiscountsTab.js +4 -2
  148. package/dist/admin/shop/DiscountsTab.js.map +1 -1
  149. package/dist/admin/shop/OrdersTab.js +57 -19
  150. package/dist/admin/shop/OrdersTab.js.map +1 -1
  151. package/dist/admin/shop/ProductPaneel.js +2 -1
  152. package/dist/admin/shop/ProductPaneel.js.map +1 -1
  153. package/dist/admin/shop/ProductsTab.js +40 -19
  154. package/dist/admin/shop/ProductsTab.js.map +1 -1
  155. package/dist/admin/shop/ShopPanel.js +13 -1
  156. package/dist/admin/shop/ShopPanel.js.map +1 -1
  157. package/dist/admin/shop/ShopSettingsTab.js +27 -1
  158. package/dist/admin/shop/ShopSettingsTab.js.map +1 -1
  159. package/dist/admin/shop/productClient.js +7 -3
  160. package/dist/admin/shop/productClient.js.map +1 -1
  161. package/dist/admin/stats/NietGevonden.d.ts +10 -0
  162. package/dist/admin/stats/NietGevonden.js +78 -0
  163. package/dist/admin/stats/NietGevonden.js.map +1 -0
  164. package/dist/admin/stats/StatsPanel.js +6 -1
  165. package/dist/admin/stats/StatsPanel.js.map +1 -1
  166. package/dist/admin/stats/lib.d.ts +2 -0
  167. package/dist/admin/stats/lib.js.map +1 -1
  168. package/dist/admin/ui/ConfirmDialog.js +10 -1
  169. package/dist/admin/ui/ConfirmDialog.js.map +1 -1
  170. package/dist/admin/ui/Icon.d.ts +34 -0
  171. package/dist/admin/ui/Icon.js +44 -0
  172. package/dist/admin/ui/Icon.js.map +1 -0
  173. package/dist/admin/ui/ImageField.d.ts +15 -0
  174. package/dist/admin/ui/ImageField.js +67 -0
  175. package/dist/admin/ui/ImageField.js.map +1 -0
  176. package/dist/admin/ui/KleurVeld.d.ts +25 -0
  177. package/dist/admin/ui/KleurVeld.js +76 -0
  178. package/dist/admin/ui/KleurVeld.js.map +1 -0
  179. package/dist/admin/ui/LaadStaat.d.ts +21 -0
  180. package/dist/admin/ui/LaadStaat.js +43 -0
  181. package/dist/admin/ui/LaadStaat.js.map +1 -0
  182. package/dist/admin/ui/LeesbaarheidPaneel.d.ts +10 -0
  183. package/dist/admin/ui/LeesbaarheidPaneel.js +68 -0
  184. package/dist/admin/ui/LeesbaarheidPaneel.js.map +1 -0
  185. package/dist/admin/ui/LinkDialog.d.ts +22 -0
  186. package/dist/admin/ui/LinkDialog.js +131 -0
  187. package/dist/admin/ui/LinkDialog.js.map +1 -0
  188. package/dist/admin/ui/LinkVeld.d.ts +14 -0
  189. package/dist/admin/ui/LinkVeld.js +49 -0
  190. package/dist/admin/ui/LinkVeld.js.map +1 -0
  191. package/dist/admin/ui/PreviewLinkResultaat.d.ts +14 -0
  192. package/dist/admin/ui/PreviewLinkResultaat.js +52 -0
  193. package/dist/admin/ui/PreviewLinkResultaat.js.map +1 -0
  194. package/dist/admin/ui/charts.js +5 -5
  195. package/dist/admin/ui/charts.js.map +1 -1
  196. package/dist/admin/ui/primitives.d.ts +16 -6
  197. package/dist/admin/ui/primitives.js +98 -26
  198. package/dist/admin/ui/primitives.js.map +1 -1
  199. package/dist/admin/ui/select.js +5 -2
  200. package/dist/admin/ui/select.js.map +1 -1
  201. package/dist/admin/ui/switch.d.ts +3 -1
  202. package/dist/admin/ui/switch.js +6 -3
  203. package/dist/admin/ui/switch.js.map +1 -1
  204. package/dist/admin/ui/toast.d.ts +2 -1
  205. package/dist/admin/ui/toast.js +17 -11
  206. package/dist/admin/ui/toast.js.map +1 -1
  207. package/dist/admin/visual/BlokKiezer.d.ts +34 -0
  208. package/dist/admin/visual/BlokKiezer.js +225 -0
  209. package/dist/admin/visual/BlokKiezer.js.map +1 -0
  210. package/dist/admin/visual/InlineGereedschap.d.ts +52 -0
  211. package/dist/admin/visual/InlineGereedschap.js +356 -0
  212. package/dist/admin/visual/InlineGereedschap.js.map +1 -0
  213. package/dist/admin/visual/InlineRijkeTekst.d.ts +23 -0
  214. package/dist/admin/visual/InlineRijkeTekst.js +152 -0
  215. package/dist/admin/visual/InlineRijkeTekst.js.map +1 -0
  216. package/dist/admin/visual/VisualEditor.js +273 -125
  217. package/dist/admin/visual/VisualEditor.js.map +1 -1
  218. package/dist/admin.css +1 -1
  219. package/dist/bedrijf/bedrijf.d.ts +36 -0
  220. package/dist/bedrijf/bedrijf.js +110 -0
  221. package/dist/bedrijf/bedrijf.js.map +1 -0
  222. package/dist/blocks/BlockRenderer.js +11 -1
  223. package/dist/blocks/BlockRenderer.js.map +1 -1
  224. package/dist/blocks/blockStyle.d.ts +7 -0
  225. package/dist/blocks/blockStyle.js +16 -3
  226. package/dist/blocks/blockStyle.js.map +1 -1
  227. package/dist/blocks/builtinSchemas.js +6 -0
  228. package/dist/blocks/builtinSchemas.js.map +1 -1
  229. package/dist/blocks/builtins/AgendaRender.d.ts +22 -0
  230. package/dist/blocks/builtins/AgendaRender.js +69 -9
  231. package/dist/blocks/builtins/AgendaRender.js.map +1 -1
  232. package/dist/blocks/builtins/Availability.d.ts +1 -0
  233. package/dist/blocks/builtins/Availability.js +23 -0
  234. package/dist/blocks/builtins/Availability.js.map +1 -0
  235. package/dist/blocks/builtins/AvailabilityRender.d.ts +12 -0
  236. package/dist/blocks/builtins/AvailabilityRender.js +105 -0
  237. package/dist/blocks/builtins/AvailabilityRender.js.map +1 -0
  238. package/dist/blocks/builtins/BeforeAfterRender.js +4 -3
  239. package/dist/blocks/builtins/BeforeAfterRender.js.map +1 -1
  240. package/dist/blocks/builtins/Booking.js +1 -1
  241. package/dist/blocks/builtins/Booking.js.map +1 -1
  242. package/dist/blocks/builtins/BreadcrumbsRender.d.ts +4 -3
  243. package/dist/blocks/builtins/BreadcrumbsRender.js +14 -8
  244. package/dist/blocks/builtins/BreadcrumbsRender.js.map +1 -1
  245. package/dist/blocks/builtins/ColumnsRender.d.ts +1 -1
  246. package/dist/blocks/builtins/ColumnsRender.js +3 -1
  247. package/dist/blocks/builtins/ColumnsRender.js.map +1 -1
  248. package/dist/blocks/builtins/Contact.js +1 -1
  249. package/dist/blocks/builtins/Contact.js.map +1 -1
  250. package/dist/blocks/builtins/ContactRender.d.ts +1 -1
  251. package/dist/blocks/builtins/ContactRender.js +29 -8
  252. package/dist/blocks/builtins/ContactRender.js.map +1 -1
  253. package/dist/blocks/builtins/Cta.js +1 -1
  254. package/dist/blocks/builtins/Cta.js.map +1 -1
  255. package/dist/blocks/builtins/CtaRender.d.ts +1 -1
  256. package/dist/blocks/builtins/CtaRender.js +5 -4
  257. package/dist/blocks/builtins/CtaRender.js.map +1 -1
  258. package/dist/blocks/builtins/Embed.js +1 -1
  259. package/dist/blocks/builtins/Embed.js.map +1 -1
  260. package/dist/blocks/builtins/FormBlock.js +1 -0
  261. package/dist/blocks/builtins/FormBlock.js.map +1 -1
  262. package/dist/blocks/builtins/FormBlockRender.d.ts +6 -0
  263. package/dist/blocks/builtins/FormBlockRender.js +88 -16
  264. package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
  265. package/dist/blocks/builtins/GalleryRender.js +3 -2
  266. package/dist/blocks/builtins/GalleryRender.js.map +1 -1
  267. package/dist/blocks/builtins/Hero.js +1 -1
  268. package/dist/blocks/builtins/Hero.js.map +1 -1
  269. package/dist/blocks/builtins/HeroRender.js +7 -3
  270. package/dist/blocks/builtins/HeroRender.js.map +1 -1
  271. package/dist/blocks/builtins/OpenNowBadge.d.ts +4 -2
  272. package/dist/blocks/builtins/OpenNowBadge.js +6 -6
  273. package/dist/blocks/builtins/OpenNowBadge.js.map +1 -1
  274. package/dist/blocks/builtins/OpeningHoursRender.d.ts +8 -2
  275. package/dist/blocks/builtins/OpeningHoursRender.js +27 -6
  276. package/dist/blocks/builtins/OpeningHoursRender.js.map +1 -1
  277. package/dist/blocks/builtins/PriceList.d.ts +1 -0
  278. package/dist/blocks/builtins/PriceList.js +25 -0
  279. package/dist/blocks/builtins/PriceList.js.map +1 -0
  280. package/dist/blocks/builtins/PriceListRender.d.ts +17 -0
  281. package/dist/blocks/builtins/PriceListRender.js +87 -0
  282. package/dist/blocks/builtins/PriceListRender.js.map +1 -0
  283. package/dist/blocks/builtins/RichTextBlockRender.js +6 -1
  284. package/dist/blocks/builtins/RichTextBlockRender.js.map +1 -1
  285. package/dist/blocks/builtins/SectionHeading.d.ts +1 -0
  286. package/dist/blocks/builtins/SectionHeading.js +14 -0
  287. package/dist/blocks/builtins/SectionHeading.js.map +1 -0
  288. package/dist/blocks/builtins/SectionHeadingRender.d.ts +7 -0
  289. package/dist/blocks/builtins/SectionHeadingRender.js +14 -0
  290. package/dist/blocks/builtins/SectionHeadingRender.js.map +1 -0
  291. package/dist/blocks/builtins/SliderRender.js +3 -2
  292. package/dist/blocks/builtins/SliderRender.js.map +1 -1
  293. package/dist/blocks/builtins/SymbolRef.js +1 -1
  294. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  295. package/dist/blocks/builtins/TeamRender.js +6 -1
  296. package/dist/blocks/builtins/TeamRender.js.map +1 -1
  297. package/dist/blocks/builtins/Testimonials.js +3 -1
  298. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  299. package/dist/blocks/builtins/TestimonialsRender.js +4 -0
  300. package/dist/blocks/builtins/TestimonialsRender.js.map +1 -1
  301. package/dist/blocks/builtins/WhatsappChatRender.d.ts +1 -1
  302. package/dist/blocks/builtins/WhatsappChatRender.js +1 -1
  303. package/dist/blocks/builtins/WhatsappChatRender.js.map +1 -1
  304. package/dist/blocks/builtins/Widgets.js +2 -2
  305. package/dist/blocks/builtins/Widgets.js.map +1 -1
  306. package/dist/blocks/builtins/WidgetsRender.js +3 -1
  307. package/dist/blocks/builtins/WidgetsRender.js.map +1 -1
  308. package/dist/blocks/builtins/index.d.ts +3 -0
  309. package/dist/blocks/builtins/index.js +6 -0
  310. package/dist/blocks/builtins/index.js.map +1 -1
  311. package/dist/blocks/builtins/lib/autoComplete.d.ts +7 -0
  312. package/dist/blocks/builtins/lib/autoComplete.js +20 -0
  313. package/dist/blocks/builtins/lib/autoComplete.js.map +1 -0
  314. package/dist/blocks/builtins/lib/bookingUrl.d.ts +2 -2
  315. package/dist/blocks/builtins/lib/bookingUrl.js +1 -1
  316. package/dist/blocks/builtins/lib/bookingUrl.js.map +1 -1
  317. package/dist/blocks/builtins/lib/ical.d.ts +24 -0
  318. package/dist/blocks/builtins/lib/ical.js +82 -0
  319. package/dist/blocks/builtins/lib/ical.js.map +1 -0
  320. package/dist/blocks/builtins/lib/vandaag.d.ts +8 -0
  321. package/dist/blocks/builtins/lib/vandaag.js +22 -0
  322. package/dist/blocks/builtins/lib/vandaag.js.map +1 -0
  323. package/dist/blocks/registry.d.ts +6 -0
  324. package/dist/blocks/registry.js.map +1 -1
  325. package/dist/blocks/ui/index.js +1 -0
  326. package/dist/blocks/ui/index.js.map +1 -1
  327. package/dist/components/EditBar.js +2 -4
  328. package/dist/components/EditBar.js.map +1 -1
  329. package/dist/components/News.js +26 -18
  330. package/dist/components/News.js.map +1 -1
  331. package/dist/components/PageView.d.ts +7 -1
  332. package/dist/components/PageView.js +17 -0
  333. package/dist/components/PageView.js.map +1 -1
  334. package/dist/components/ResponsiveImage.js +18 -20
  335. package/dist/components/ResponsiveImage.js.map +1 -1
  336. package/dist/components/site/SiteFooter.d.ts +6 -1
  337. package/dist/components/site/SiteFooter.js +61 -9
  338. package/dist/components/site/SiteFooter.js.map +1 -1
  339. package/dist/components/site/SiteHeader.d.ts +15 -0
  340. package/dist/components/site/SiteHeader.js +70 -0
  341. package/dist/components/site/SiteHeader.js.map +1 -0
  342. package/dist/components/site/SiteNav.d.ts +2 -0
  343. package/dist/components/site/SiteNav.js +29 -5
  344. package/dist/components/site/SiteNav.js.map +1 -1
  345. package/dist/demo/DemoStudio.js +2 -2
  346. package/dist/demo/DemoStudio.js.map +1 -1
  347. package/dist/demo/demoStore.d.ts +13 -0
  348. package/dist/demo/demoStore.js +36 -0
  349. package/dist/demo/demoStore.js.map +1 -1
  350. package/dist/editor/EditorProvider.js +15 -4
  351. package/dist/editor/EditorProvider.js.map +1 -1
  352. package/dist/index.d.ts +4 -1
  353. package/dist/index.js +16 -1
  354. package/dist/index.js.map +1 -1
  355. package/dist/media/ImageContext.d.ts +17 -0
  356. package/dist/media/ImageContext.js +65 -20
  357. package/dist/media/ImageContext.js.map +1 -1
  358. package/dist/media/altInHtml.d.ts +9 -0
  359. package/dist/media/altInHtml.js +17 -0
  360. package/dist/media/altInHtml.js.map +1 -0
  361. package/dist/media/srcset.d.ts +12 -0
  362. package/dist/media/srcset.js +13 -0
  363. package/dist/media/srcset.js.map +1 -0
  364. package/dist/modules/builtins.d.ts +1 -0
  365. package/dist/modules/builtins.js +16 -3
  366. package/dist/modules/builtins.js.map +1 -1
  367. package/dist/modules/shop/blocks/Cart.js +5 -3
  368. package/dist/modules/shop/blocks/Cart.js.map +1 -1
  369. package/dist/modules/shop/blocks/Checkout.js +56 -35
  370. package/dist/modules/shop/blocks/Checkout.js.map +1 -1
  371. package/dist/modules/shop/blocks/Donation.js +4 -4
  372. package/dist/modules/shop/blocks/Donation.js.map +1 -1
  373. package/dist/modules/shop/blocks/ProductDetail.js +19 -3
  374. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
  375. package/dist/modules/shop/blocks/ProductGrid.d.ts +3 -1
  376. package/dist/modules/shop/blocks/ProductGrid.js +8 -4
  377. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
  378. package/dist/modules/shop/blocks/ShopCategories.js +4 -3
  379. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -1
  380. package/dist/modules/shop/lib.d.ts +4 -0
  381. package/dist/modules/shop/lib.js +14 -0
  382. package/dist/modules/shop/lib.js.map +1 -1
  383. package/dist/modules/shop/settings.d.ts +3 -0
  384. package/dist/modules/shop/settings.js +14 -0
  385. package/dist/modules/shop/settings.js.map +1 -1
  386. package/dist/modules/shop/strings.d.ts +4 -0
  387. package/dist/modules/shop/strings.js +8 -0
  388. package/dist/modules/shop/strings.js.map +1 -1
  389. package/dist/next/envCheck.d.ts +42 -0
  390. package/dist/next/envCheck.js +106 -0
  391. package/dist/next/envCheck.js.map +1 -0
  392. package/dist/next/index.cjs +185 -2
  393. package/dist/next/index.d.ts +2 -0
  394. package/dist/next/index.js +8 -1
  395. package/dist/next/index.js.map +1 -1
  396. package/dist/next/securityHeaders.d.ts +36 -0
  397. package/dist/next/securityHeaders.js +74 -0
  398. package/dist/next/securityHeaders.js.map +1 -0
  399. package/dist/patterns/builtins.d.ts +7 -0
  400. package/dist/patterns/builtins.js +138 -3
  401. package/dist/patterns/builtins.js.map +1 -1
  402. package/dist/releaseNotes.js +140 -0
  403. package/dist/releaseNotes.js.map +1 -1
  404. package/dist/render/PublishedPage.d.ts +2 -1
  405. package/dist/render/PublishedPage.js +10 -4
  406. package/dist/render/PublishedPage.js.map +1 -1
  407. package/dist/schema/blocks.d.ts +470 -0
  408. package/dist/schema/blocks.js +102 -4
  409. package/dist/schema/blocks.js.map +1 -1
  410. package/dist/schema/business.d.ts +117 -0
  411. package/dist/schema/business.js +73 -0
  412. package/dist/schema/business.js.map +1 -0
  413. package/dist/schema/fields.d.ts +2 -1
  414. package/dist/schema/fields.js.map +1 -1
  415. package/dist/schema/forms.d.ts +18 -0
  416. package/dist/schema/forms.js +18 -3
  417. package/dist/schema/forms.js.map +1 -1
  418. package/dist/schema/mail.d.ts +10 -0
  419. package/dist/schema/mail.js +7 -0
  420. package/dist/schema/mail.js.map +1 -1
  421. package/dist/schema/settings.d.ts +101 -0
  422. package/dist/schema/settings.js +24 -3
  423. package/dist/schema/settings.js.map +1 -1
  424. package/dist/schema/shop.d.ts +2 -0
  425. package/dist/schema/shop.js +4 -0
  426. package/dist/schema/shop.js.map +1 -1
  427. package/dist/schema/shopGeld.d.ts +1 -0
  428. package/dist/schema/shopGeld.js +1 -0
  429. package/dist/schema/shopGeld.js.map +1 -1
  430. package/dist/schema/siteConfig.d.ts +68 -1
  431. package/dist/schema/siteConfig.js +7 -2
  432. package/dist/schema/siteConfig.js.map +1 -1
  433. package/dist/server/blockData.d.ts +1 -1
  434. package/dist/server/blockData.js +74 -18
  435. package/dist/server/blockData.js.map +1 -1
  436. package/dist/server/cspRapport.d.ts +20 -0
  437. package/dist/server/cspRapport.js +56 -0
  438. package/dist/server/cspRapport.js.map +1 -0
  439. package/dist/server/fonts.d.ts +3 -0
  440. package/dist/server/fonts.js +39 -0
  441. package/dist/server/fonts.js.map +1 -0
  442. package/dist/server/index.d.ts +3 -0
  443. package/dist/server/index.js +8 -0
  444. package/dist/server/index.js.map +1 -1
  445. package/dist/server/sanitize.js +15 -2
  446. package/dist/server/sanitize.js.map +1 -1
  447. package/dist/settings/useSiteSettings.js +4 -1
  448. package/dist/settings/useSiteSettings.js.map +1 -1
  449. package/dist/starters/starters.d.ts +69 -0
  450. package/dist/starters/starters.js +171 -0
  451. package/dist/starters/starters.js.map +1 -0
  452. package/dist/styles.css +1149 -208
  453. package/dist/theme/color.d.ts +10 -0
  454. package/dist/theme/color.js +17 -1
  455. package/dist/theme/color.js.map +1 -1
  456. package/dist/theme/fontFallback.d.ts +23 -0
  457. package/dist/theme/fontFallback.js +58 -0
  458. package/dist/theme/fontFallback.js.map +1 -0
  459. package/dist/theme/fonts.d.ts +11 -0
  460. package/dist/theme/fonts.js +16 -0
  461. package/dist/theme/fonts.js.map +1 -0
  462. package/dist/version.js +1 -1
  463. package/package.json +6 -4
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/shop/ShopSettingsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { centsToEuroInput, eurosToCents } from \"./lib\";\n\n/**\n * Winkel-instellingen — leest/schrijft DEZELFDE module-config als Instellingen → Modules\n * (settings/public.modules.shop.config; geen tweede bron van waarheid), maar met winkel-\n * presentatie: bedragen in euro's en uitleg per veld. Siteadmin-only (rules).\n */\ninterface ShopCfg {\n shippingFlatCents?: number;\n shippingFreeFromCents?: number;\n orderPrefix?: string;\n currency?: string;\n businessName?: string;\n businessAddress?: string;\n businessZip?: string;\n businessCity?: string;\n businessKvk?: string;\n businessBtw?: string;\n giftWrapEnabled?: boolean;\n giftWrapPriceCents?: number;\n [k: string]: unknown;\n}\n\nexport function ShopSettingsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [cfg, setCfg] = useState<ShopCfg | null>(null);\n const [shipInput, setShipInput] = useState(\"\");\n const [freeInput, setFreeInput] = useState(\"\");\n const [giftWrapInput, setGiftWrapInput] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [zones, setZones] = useState<{ naam: string; landen: string; flat: string; free: string }[]>([]);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")).then((snap) => {\n const modules = (snap.data()?.modules ?? {}) as Record<string, { config?: ShopCfg } | boolean>;\n const entry = modules.shop;\n const c = typeof entry === \"object\" && entry !== null ? (entry.config ?? {}) : {};\n setCfg(c);\n setShipInput(centsToEuroInput(c.shippingFlatCents ?? 495));\n setFreeInput(c.shippingFreeFromCents ? centsToEuroInput(c.shippingFreeFromCents) : \"\");\n setGiftWrapInput(centsToEuroInput(c.giftWrapPriceCents ?? 0));\n setZones(\n ((c.shippingZones as { naam: string; landen?: string[]; flatCents: number; freeFromCents?: number }[] | undefined) ?? []).map((z) => ({\n naam: z.naam,\n landen: (z.landen ?? []).join(\", \"),\n flat: centsToEuroInput(z.flatCents),\n free: z.freeFromCents ? centsToEuroInput(z.freeFromCents) : \"\",\n })),\n );\n });\n }, [siteId]);\n\n async function save() {\n const app = getFirebaseApp();\n if (!app || !cfg) return;\n setBusy(true);\n try {\n const next: ShopCfg = {\n ...cfg,\n shippingFlatCents: eurosToCents(shipInput) ?? 495,\n shippingFreeFromCents: freeInput.trim() === \"\" ? 0 : (eurosToCents(freeInput) ?? 0),\n giftWrapPriceCents: eurosToCents(giftWrapInput) ?? 0,\n shippingZones: zones\n .filter((z) => z.naam.trim())\n .map((z) => ({\n naam: z.naam.trim(),\n // \"BE, DE\" → [\"BE\",\"DE\"]; leeg = de \"overige landen\"-zone.\n landen: z.landen.split(/[,;\\s]+/).map((l) => l.trim().toUpperCase()).filter(Boolean).slice(0, 60),\n flatCents: eurosToCents(z.flat) ?? 0,\n freeFromCents: z.free.trim() === \"\" ? 0 : (eurosToCents(z.free) ?? 0),\n })),\n };\n /**\n * Via de saveSiteSettings-callable, NIET rechtstreeks naar Firestore. Deze tab deed nog een\n * directe setDoc op settings/public, maar die deur zit sinds de settings-verstrakking in de\n * rules voor iederéén dicht (ook superadmin) — de klant kreeg letterlijk \"Missing or\n * insufficient permissions\" bij het opslaan van verzendkosten. De callable is bovendien de\n * enige route die site/public consistent houdt én de cache-tag pusht, zodat de nieuwe\n * verzendkosten binnen seconden op de live winkel staan i.p.v. na het ISR-vangnet.\n * set+merge in de callable merged diep: de enabled-vlag en andere modules blijven staan.\n */\n const fn = callable(app, \"saveSiteSettings\");\n await fn({ siteId, settings: { modules: { shop: { config: next } } } });\n setCfg(next);\n toast({ title: \"Instellingen opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n if (!cfg) return <Card><p className=\"text-sm text-slate-500\">Laden…</p></Card>;\n\n const set = (k: string, v: string) => setCfg((p) => ({ ...p, [k]: v }));\n\n /** Wettelijk minimum op een factuur: wie ben je, waar zit je, en onder welke nummers. */\n const ontbrekend = (\n [\n [\"bedrijfsnaam\", cfg.businessName],\n [\"adres\", cfg.businessAddress],\n [\"postcode\", cfg.businessZip],\n [\"plaats\", cfg.businessCity],\n [\"KVK-nummer\", cfg.businessKvk],\n [\"btw-nummer\", cfg.businessBtw],\n ] as [string, unknown][]\n )\n .filter(([, v]) => !String(v ?? \"\").trim())\n .map(([label]) => label);\n\n return (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Verzending & bestellen</h3>\n <div className=\"grid gap-4\">\n <Field label=\"Verzendkosten (€)\" hint=\"Vast bedrag per bestelling binnen Nederland\">\n <Input value={shipInput} onChange={(e) => setShipInput(e.target.value)} placeholder=\"4,95\" className=\"w-32\" />\n </Field>\n <Field label=\"Gratis verzending vanaf (€)\" hint=\"Leeg = nooit gratis\">\n <Input value={freeInput} onChange={(e) => setFreeInput(e.target.value)} placeholder=\"50,00\" className=\"w-32\" />\n </Field>\n <Field label=\"Ordernummer-prefix\" hint=\"Bijv. CKC → CKC-2026-0001\">\n <Input value={String(cfg.orderPrefix ?? \"ORDER\")} onChange={(e) => set(\"orderPrefix\", e.target.value.toUpperCase().slice(0, 12))} className=\"w-32\" />\n </Field>\n\n {/* Zones: pas nodig zodra je buiten Nederland verzendt. Zonder zones geldt het tarief\n hierboven voor alles — precies zoals het altijd werkte. */}\n <div className=\"border-t border-slate-100 pt-4\">\n <p className=\"text-sm font-medium text-slate-800\">Verzenden naar het buitenland</p>\n <p className=\"mt-1 mb-3 text-xs text-slate-500\">\n Zonder zones geldt het tarief hierboven voor élke bestelling. Voeg een zone toe om per\n land een eigen tarief te rekenen. Een zone <strong>zonder landen</strong> is \"alle\n overige landen\"; laat je die weg, dan verzend je alléén naar de genoemde landen — de\n afrekenpagina toont dan precies die keuze.\n </p>\n <div className=\"grid gap-2\">\n {zones.map((z, i) => {\n const zet = (patch: Partial<(typeof zones)[number]>) =>\n setZones((p) => p.map((x, j) => (j === i ? { ...x, ...patch } : x)));\n return (\n <div key={i} className=\"grid grid-cols-[1fr_1fr_90px_90px_auto] items-center gap-2\">\n <Input value={z.naam} onChange={(e) => zet({ naam: e.target.value })} placeholder=\"Naam (bv. België)\" />\n <Input value={z.landen} onChange={(e) => zet({ landen: e.target.value })} placeholder=\"BE, DE (leeg = overige)\" />\n <Input value={z.flat} onChange={(e) => zet({ flat: e.target.value })} placeholder=\"Tarief €\" />\n <Input value={z.free} onChange={(e) => zet({ free: e.target.value })} placeholder=\"Gratis v.a. €\" />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setZones((p) => p.filter((_, j) => j !== i))}>✕</Button>\n </div>\n );\n })}\n <div>\n <Button size=\"sm\" variant=\"secondary\" disabled={zones.length >= 10} onClick={() => setZones((p) => [...p, { naam: \"\", landen: \"\", flat: \"\", free: \"\" }])}>\n + Zone\n </Button>\n </div>\n </div>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Cadeauverpakking</h3>\n <div className=\"grid gap-4\">\n <Switch\n checked={cfg.giftWrapEnabled ?? false}\n onCheckedChange={(v) => setCfg((p) => ({ ...p, giftWrapEnabled: v }))}\n label=\"Cadeauverpakking aanbieden bij het afrekenen\"\n />\n {cfg.giftWrapEnabled && (\n <Field label=\"Meerprijs (€)\" hint=\"Komt als aparte regel op de bestelling en de factuur; de klant kan een kaartjestekst + opmerking meegeven\">\n <Input value={giftWrapInput} onChange={(e) => setGiftWrapInput(e.target.value)} placeholder=\"3,95\" className=\"w-32\" />\n </Field>\n )}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-sm font-semibold\">Bedrijfsgegevens (factuur)</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen.</p>\n {/* Zonder deze gegevens gaat er wél een factuur uit, maar zonder afzender — en een factuur\n zonder bedrijfsnaam, adres en KVK/btw is fiscaal niet geldig. Dat gebeurde stil. */}\n {ontbrekend.length > 0 && (\n <p className=\"mb-3 rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800\">\n <strong>Nog invullen:</strong> {ontbrekend.join(\", \")}. Zonder deze gegevens staat er geen\n afzender op je factuur, en dan is hij voor de Belastingdienst niet geldig.\n </p>\n )}\n <div className=\"grid gap-3\">\n <Field label=\"Bedrijfsnaam\"><Input value={String(cfg.businessName ?? \"\")} onChange={(e) => set(\"businessName\", e.target.value)} /></Field>\n <Field label=\"Adres\"><Input value={String(cfg.businessAddress ?? \"\")} onChange={(e) => set(\"businessAddress\", e.target.value)} /></Field>\n <div className=\"grid grid-cols-[120px_1fr] gap-3\">\n <Field label=\"Postcode\"><Input value={String(cfg.businessZip ?? \"\")} onChange={(e) => set(\"businessZip\", e.target.value)} /></Field>\n <Field label=\"Plaats\"><Input value={String(cfg.businessCity ?? \"\")} onChange={(e) => set(\"businessCity\", e.target.value)} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"KVK-nummer\"><Input value={String(cfg.businessKvk ?? \"\")} onChange={(e) => set(\"businessKvk\", e.target.value)} /></Field>\n <Field label=\"Btw-nummer\"><Input value={String(cfg.businessBtw ?? \"\")} onChange={(e) => set(\"businessBtw\", e.target.value)} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Contact-e-mail\" hint=\"Staat op de factuur en onderaan elke mail\">\n <Input value={String(cfg.businessEmail ?? \"\")} onChange={(e) => set(\"businessEmail\", e.target.value)} />\n </Field>\n <Field label=\"Telefoon (optioneel)\">\n <Input value={String(cfg.businessPhone ?? \"\")} onChange={(e) => set(\"businessPhone\", e.target.value)} />\n </Field>\n </div>\n </div>\n </Card>\n\n <div className=\"lg:col-span-2\">\n <Button onClick={save} disabled={busy}>Opslaan</Button>\n <p className=\"mt-2 text-xs text-slate-500\">\n Paden en valuta van de winkel staan onder Instellingen → Modules → Webwinkel.\n </p>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/shop/ShopSettingsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb, getFirebaseApp } from \"../../firebase/client\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { Button, Card, Field, Input, Textarea } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { centsToEuroInput, eurosToCents } from \"./lib\";\n\n/**\n * Winkel-instellingen — leest/schrijft DEZELFDE module-config als Instellingen → Modules\n * (settings/public.modules.shop.config; geen tweede bron van waarheid), maar met winkel-\n * presentatie: bedragen in euro's en uitleg per veld. Siteadmin-only (rules).\n */\ninterface ShopCfg {\n shippingFlatCents?: number;\n shippingFreeFromCents?: number;\n orderPrefix?: string;\n currency?: string;\n businessName?: string;\n businessAddress?: string;\n businessZip?: string;\n businessCity?: string;\n businessKvk?: string;\n businessBtw?: string;\n giftWrapEnabled?: boolean;\n giftWrapPriceCents?: number;\n pickupEnabled?: boolean;\n pickupNote?: string;\n [k: string]: unknown;\n}\n\nexport function ShopSettingsTab({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [cfg, setCfg] = useState<ShopCfg | null>(null);\n const [shipInput, setShipInput] = useState(\"\");\n const [freeInput, setFreeInput] = useState(\"\");\n const [giftWrapInput, setGiftWrapInput] = useState(\"\");\n const [busy, setBusy] = useState(false);\n const [zones, setZones] = useState<{ naam: string; landen: string; flat: string; free: string }[]>([]);\n\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\")).then((snap) => {\n const modules = (snap.data()?.modules ?? {}) as Record<string, { config?: ShopCfg } | boolean>;\n const entry = modules.shop;\n const c = typeof entry === \"object\" && entry !== null ? (entry.config ?? {}) : {};\n setCfg(c);\n setShipInput(centsToEuroInput(c.shippingFlatCents ?? 495));\n setFreeInput(c.shippingFreeFromCents ? centsToEuroInput(c.shippingFreeFromCents) : \"\");\n setGiftWrapInput(centsToEuroInput(c.giftWrapPriceCents ?? 0));\n setZones(\n ((c.shippingZones as { naam: string; landen?: string[]; flatCents: number; freeFromCents?: number }[] | undefined) ?? []).map((z) => ({\n naam: z.naam,\n landen: (z.landen ?? []).join(\", \"),\n flat: centsToEuroInput(z.flatCents),\n free: z.freeFromCents ? centsToEuroInput(z.freeFromCents) : \"\",\n })),\n );\n }).catch((e) => {\n // 8.2: zonder catch bleef dit tabblad eeuwig op \"Laden…\" staan bij een leesfout.\n console.warn(\"[xicms] winkelinstellingen niet geladen:\", (e as Error).message);\n setCfg({});\n });\n }, [siteId]);\n\n async function save() {\n const app = getFirebaseApp();\n if (!app || !cfg) return;\n setBusy(true);\n try {\n const next: ShopCfg = {\n ...cfg,\n shippingFlatCents: eurosToCents(shipInput) ?? 495,\n shippingFreeFromCents: freeInput.trim() === \"\" ? 0 : (eurosToCents(freeInput) ?? 0),\n giftWrapPriceCents: eurosToCents(giftWrapInput) ?? 0,\n shippingZones: zones\n .filter((z) => z.naam.trim())\n .map((z) => ({\n naam: z.naam.trim(),\n // \"BE, DE\" → [\"BE\",\"DE\"]; leeg = de \"overige landen\"-zone.\n landen: z.landen.split(/[,;\\s]+/).map((l) => l.trim().toUpperCase()).filter(Boolean).slice(0, 60),\n flatCents: eurosToCents(z.flat) ?? 0,\n freeFromCents: z.free.trim() === \"\" ? 0 : (eurosToCents(z.free) ?? 0),\n })),\n };\n /**\n * Via de saveSiteSettings-callable, NIET rechtstreeks naar Firestore. Deze tab deed nog een\n * directe setDoc op settings/public, maar die deur zit sinds de settings-verstrakking in de\n * rules voor iederéén dicht (ook superadmin) — de klant kreeg letterlijk \"Missing or\n * insufficient permissions\" bij het opslaan van verzendkosten. De callable is bovendien de\n * enige route die site/public consistent houdt én de cache-tag pusht, zodat de nieuwe\n * verzendkosten binnen seconden op de live winkel staan i.p.v. na het ISR-vangnet.\n * set+merge in de callable merged diep: de enabled-vlag en andere modules blijven staan.\n */\n const fn = callable(app, \"saveSiteSettings\");\n await fn({ siteId, settings: { modules: { shop: { config: next } } } });\n setCfg(next);\n toast({ title: \"Instellingen opgeslagen\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n if (!cfg) return <Card><p className=\"text-sm text-slate-500\">Laden…</p></Card>;\n\n const set = (k: string, v: string) => setCfg((p) => ({ ...p, [k]: v }));\n\n /** Wettelijk minimum op een factuur: wie ben je, waar zit je, en onder welke nummers. */\n const ontbrekend = (\n [\n [\"bedrijfsnaam\", cfg.businessName],\n [\"adres\", cfg.businessAddress],\n [\"postcode\", cfg.businessZip],\n [\"plaats\", cfg.businessCity],\n [\"KVK-nummer\", cfg.businessKvk],\n [\"btw-nummer\", cfg.businessBtw],\n ] as [string, unknown][]\n )\n .filter(([, v]) => !String(v ?? \"\").trim())\n .map(([label]) => label);\n\n return (\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Verzending & bestellen</h3>\n <div className=\"grid gap-4\">\n <Field label=\"Verzendkosten (€)\" hint=\"Vast bedrag per bestelling binnen Nederland\">\n <Input value={shipInput} onChange={(e) => setShipInput(e.target.value)} placeholder=\"4,95\" className=\"w-32\" />\n </Field>\n <Field label=\"Gratis verzending vanaf (€)\" hint=\"Leeg = nooit gratis\">\n <Input value={freeInput} onChange={(e) => setFreeInput(e.target.value)} placeholder=\"50,00\" className=\"w-32\" />\n </Field>\n <Field label=\"Ordernummer-prefix\" hint=\"Bijv. CKC → CKC-2026-0001\">\n <Input value={String(cfg.orderPrefix ?? \"ORDER\")} onChange={(e) => set(\"orderPrefix\", e.target.value.toUpperCase().slice(0, 12))} className=\"w-32\" />\n </Field>\n\n {/* Zones: pas nodig zodra je buiten Nederland verzendt. Zonder zones geldt het tarief\n hierboven voor alles — precies zoals het altijd werkte. */}\n <div className=\"border-t border-slate-100 pt-4\">\n <p className=\"text-sm font-medium text-slate-800\">Verzenden naar het buitenland</p>\n <p className=\"mt-1 mb-3 text-xs text-slate-500\">\n Zonder zones geldt het tarief hierboven voor élke bestelling. Voeg een zone toe om per\n land een eigen tarief te rekenen. Een zone <strong>zonder landen</strong> is \"alle\n overige landen\"; laat je die weg, dan verzend je alléén naar de genoemde landen — de\n afrekenpagina toont dan precies die keuze.\n </p>\n <div className=\"grid gap-2\">\n {zones.map((z, i) => {\n const zet = (patch: Partial<(typeof zones)[number]>) =>\n setZones((p) => p.map((x, j) => (j === i ? { ...x, ...patch } : x)));\n return (\n <div key={i} className=\"grid grid-cols-[1fr_1fr_90px_90px_auto] items-center gap-2\">\n <Input value={z.naam} onChange={(e) => zet({ naam: e.target.value })} placeholder=\"Naam (bv. België)\" />\n <Input value={z.landen} onChange={(e) => zet({ landen: e.target.value })} placeholder=\"BE, DE (leeg = overige)\" />\n <Input value={z.flat} onChange={(e) => zet({ flat: e.target.value })} placeholder=\"Tarief €\" />\n <Input value={z.free} onChange={(e) => zet({ free: e.target.value })} placeholder=\"Gratis v.a. €\" />\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setZones((p) => p.filter((_, j) => j !== i))}>✕</Button>\n </div>\n );\n })}\n <div>\n <Button size=\"sm\" variant=\"secondary\" disabled={zones.length >= 10} onClick={() => setZones((p) => [...p, { naam: \"\", landen: \"\", flat: \"\", free: \"\" }])}>\n + Zone\n </Button>\n </div>\n </div>\n </div>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Afhalen</h3>\n <div className=\"grid gap-4\">\n <Switch\n checked={cfg.pickupEnabled ?? false}\n onCheckedChange={(v) => setCfg((p) => ({ ...p, pickupEnabled: v }))}\n label=\"Klanten kunnen kiezen voor afhalen (geen verzendkosten)\"\n />\n {cfg.pickupEnabled && (\n <Field label=\"Toelichting bij afhalen\" hint=\"Bijv. afhaaladres en tijden. De klant ziet dit bij het afrekenen en in de bevestigingsmail.\">\n <Textarea\n rows={4}\n maxLength={1000}\n value={String(cfg.pickupNote ?? \"\")}\n onChange={(e) => set(\"pickupNote\", e.target.value)}\n placeholder=\"Afhalen kan op afspraak aan de Voorbeeldstraat 1 in Plaats. Je krijgt een mail zodra je bestelling klaarstaat.\"\n />\n </Field>\n )}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-3 text-sm font-semibold\">Cadeauverpakking</h3>\n <div className=\"grid gap-4\">\n <Switch\n checked={cfg.giftWrapEnabled ?? false}\n onCheckedChange={(v) => setCfg((p) => ({ ...p, giftWrapEnabled: v }))}\n label=\"Cadeauverpakking aanbieden bij het afrekenen\"\n />\n {cfg.giftWrapEnabled && (\n <Field label=\"Meerprijs (€)\" hint=\"Komt als aparte regel op de bestelling en de factuur; de klant kan een kaartjestekst + opmerking meegeven\">\n <Input value={giftWrapInput} onChange={(e) => setGiftWrapInput(e.target.value)} placeholder=\"3,95\" className=\"w-32\" />\n </Field>\n )}\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-sm font-semibold\">Bedrijfsgegevens (factuur)</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Deze gegevens komen op de factuur-PDF die klanten bij hun bestelling krijgen.</p>\n {/* Zonder deze gegevens gaat er wél een factuur uit, maar zonder afzender — en een factuur\n zonder bedrijfsnaam, adres en KVK/btw is fiscaal niet geldig. Dat gebeurde stil. */}\n {ontbrekend.length > 0 && (\n <p className=\"mb-3 rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800\">\n <strong>Nog invullen:</strong> {ontbrekend.join(\", \")}. Zonder deze gegevens staat er geen\n afzender op je factuur, en dan is hij voor de Belastingdienst niet geldig.\n </p>\n )}\n <div className=\"grid gap-3\">\n <Field label=\"Bedrijfsnaam\"><Input value={String(cfg.businessName ?? \"\")} onChange={(e) => set(\"businessName\", e.target.value)} /></Field>\n <Field label=\"Adres\"><Input value={String(cfg.businessAddress ?? \"\")} onChange={(e) => set(\"businessAddress\", e.target.value)} /></Field>\n <div className=\"grid grid-cols-[120px_1fr] gap-3\">\n <Field label=\"Postcode\"><Input value={String(cfg.businessZip ?? \"\")} onChange={(e) => set(\"businessZip\", e.target.value)} /></Field>\n <Field label=\"Plaats\"><Input value={String(cfg.businessCity ?? \"\")} onChange={(e) => set(\"businessCity\", e.target.value)} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"KVK-nummer\"><Input value={String(cfg.businessKvk ?? \"\")} onChange={(e) => set(\"businessKvk\", e.target.value)} /></Field>\n <Field label=\"Btw-nummer\"><Input value={String(cfg.businessBtw ?? \"\")} onChange={(e) => set(\"businessBtw\", e.target.value)} /></Field>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Contact-e-mail\" hint=\"Staat op de factuur en onderaan elke mail\">\n <Input value={String(cfg.businessEmail ?? \"\")} onChange={(e) => set(\"businessEmail\", e.target.value)} />\n </Field>\n <Field label=\"Telefoon (optioneel)\">\n <Input value={String(cfg.businessPhone ?? \"\")} onChange={(e) => set(\"businessPhone\", e.target.value)} />\n </Field>\n </div>\n </div>\n </Card>\n\n <div className=\"lg:col-span-2\">\n <Button onClick={save} disabled={busy}>Opslaan</Button>\n <p className=\"mt-2 text-xs text-slate-500\">\n Paden en valuta van de winkel staan onder Instellingen → Modules → Webwinkel.\n </p>\n </div>\n </div>\n );\n}\n"],"mappings":";AA6GyB,cAqCb,YArCa;AA3GzB,SAAS,WAAW,gBAAgB;AACpC,SAAS,KAAK,cAAc;AAC5B,SAAS,OAAO,sBAAsB;AACtC,SAAS,gBAAgB;AACzB,SAAS,QAAQ,MAAM,OAAO,OAAO,gBAAgB;AACrD,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB,oBAAoB;AAyBxC,SAAS,gBAAgB,EAAE,OAAO,GAAuB;AAC9D,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,CAAC,KAAK,MAAM,IAAI,SAAyB,IAAI;AACnD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,EAAE;AAC7C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,EAAE;AAC7C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,EAAE;AACrD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyE,CAAC,CAAC;AAErG,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI;AACT,SAAK,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS;AACzE,YAAM,UAAW,KAAK,KAAK,GAAG,WAAW,CAAC;AAC1C,YAAM,QAAQ,QAAQ;AACtB,YAAM,IAAI,OAAO,UAAU,YAAY,UAAU,OAAQ,MAAM,UAAU,CAAC,IAAK,CAAC;AAChF,aAAO,CAAC;AACR,mBAAa,iBAAiB,EAAE,qBAAqB,GAAG,CAAC;AACzD,mBAAa,EAAE,wBAAwB,iBAAiB,EAAE,qBAAqB,IAAI,EAAE;AACrF,uBAAiB,iBAAiB,EAAE,sBAAsB,CAAC,CAAC;AAC5D;AAAA,SACI,EAAE,iBAAkH,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,UACpI,MAAM,EAAE;AAAA,UACR,SAAS,EAAE,UAAU,CAAC,GAAG,KAAK,IAAI;AAAA,UAClC,MAAM,iBAAiB,EAAE,SAAS;AAAA,UAClC,MAAM,EAAE,gBAAgB,iBAAiB,EAAE,aAAa,IAAI;AAAA,QAC9D,EAAE;AAAA,MACJ;AAAA,IACF,CAAC,EAAE,MAAM,CAAC,MAAM;AAEd,cAAQ,KAAK,4CAA6C,EAAY,OAAO;AAC7E,aAAO,CAAC,CAAC;AAAA,IACX,CAAC;AAAA,EACH,GAAG,CAAC,MAAM,CAAC;AAEX,iBAAe,OAAO;AACpB,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,OAAO,CAAC,IAAK;AAClB,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,OAAgB;AAAA,QACpB,GAAG;AAAA,QACH,mBAAmB,aAAa,SAAS,KAAK;AAAA,QAC9C,uBAAuB,UAAU,KAAK,MAAM,KAAK,IAAK,aAAa,SAAS,KAAK;AAAA,QACjF,oBAAoB,aAAa,aAAa,KAAK;AAAA,QACnD,eAAe,MACZ,OAAO,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC,EAC3B,IAAI,CAAC,OAAO;AAAA,UACX,MAAM,EAAE,KAAK,KAAK;AAAA;AAAA,UAElB,QAAQ,EAAE,OAAO,MAAM,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,OAAO,EAAE,MAAM,GAAG,EAAE;AAAA,UAChG,WAAW,aAAa,EAAE,IAAI,KAAK;AAAA,UACnC,eAAe,EAAE,KAAK,KAAK,MAAM,KAAK,IAAK,aAAa,EAAE,IAAI,KAAK;AAAA,QACrE,EAAE;AAAA,MACN;AAUA,YAAM,KAAK,SAAS,KAAK,kBAAkB;AAC3C,YAAM,GAAG,EAAE,QAAQ,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,KAAK,EAAE,EAAE,EAAE,CAAC;AACtE,aAAO,IAAI;AACX,YAAM,EAAE,OAAO,2BAA2B,MAAM,UAAU,CAAC;AAAA,IAC7D,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,mBAAmB,aAAa,aAAa,QAAQ,EAAE,UAAU,QAAW,MAAM,QAAQ,CAAC;AAAA,IAC5G,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,MAAI,CAAC,IAAK,QAAO,oBAAC,QAAK,8BAAC,OAAE,WAAU,0BAAyB,yBAAM,GAAI;AAEvE,QAAM,MAAM,CAAC,GAAW,MAAc,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;AAGtE,QAAM,aACJ;AAAA,IACE,CAAC,gBAAgB,IAAI,YAAY;AAAA,IACjC,CAAC,SAAS,IAAI,eAAe;AAAA,IAC7B,CAAC,YAAY,IAAI,WAAW;AAAA,IAC5B,CAAC,UAAU,IAAI,YAAY;AAAA,IAC3B,CAAC,cAAc,IAAI,WAAW;AAAA,IAC9B,CAAC,cAAc,IAAI,WAAW;AAAA,EAChC,EAEC,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,KAAK,EAAE,EAAE,KAAK,CAAC,EACzC,IAAI,CAAC,CAAC,KAAK,MAAM,KAAK;AAEzB,SACE,qBAAC,SAAI,WAAU,6BACb;AAAA,yBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,8BAA6B,oCAAsB;AAAA,MACjE,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,OAAM,0BAAoB,MAAK,+CACpC,8BAAC,SAAM,OAAO,WAAW,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK,GAAG,aAAY,QAAO,WAAU,QAAO,GAC9G;AAAA,QACA,oBAAC,SAAM,OAAM,oCAA8B,MAAK,uBAC9C,8BAAC,SAAM,OAAO,WAAW,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK,GAAG,aAAY,SAAQ,WAAU,QAAO,GAC/G;AAAA,QACA,oBAAC,SAAM,OAAM,sBAAqB,MAAK,kCACrC,8BAAC,SAAM,OAAO,OAAO,IAAI,eAAe,OAAO,GAAG,UAAU,CAAC,MAAM,IAAI,eAAe,EAAE,OAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,WAAU,QAAO,GACrJ;AAAA,QAIA,qBAAC,SAAI,WAAU,kCACb;AAAA,8BAAC,OAAE,WAAU,sCAAqC,2CAA6B;AAAA,UAC/E,qBAAC,OAAE,WAAU,oCAAmC;AAAA;AAAA,YAEH,oBAAC,YAAO,2BAAa;AAAA,YAAS;AAAA,aAG3E;AAAA,UACA,qBAAC,SAAI,WAAU,cACZ;AAAA,kBAAM,IAAI,CAAC,GAAG,MAAM;AACnB,oBAAM,MAAM,CAAC,UACX,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,MAAO,MAAM,IAAI,EAAE,GAAG,GAAG,GAAG,MAAM,IAAI,CAAE,CAAC;AACrE,qBACE,qBAAC,SAAY,WAAU,8DACrB;AAAA,oCAAC,SAAM,OAAO,EAAE,MAAM,UAAU,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC,GAAG,aAAY,wBAAoB;AAAA,gBACtG,oBAAC,SAAM,OAAO,EAAE,QAAQ,UAAU,CAAC,MAAM,IAAI,EAAE,QAAQ,EAAE,OAAO,MAAM,CAAC,GAAG,aAAY,2BAA0B;AAAA,gBAChH,oBAAC,SAAM,OAAO,EAAE,MAAM,UAAU,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC,GAAG,aAAY,iBAAW;AAAA,gBAC7F,oBAAC,SAAM,OAAO,EAAE,MAAM,UAAU,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC,GAAG,aAAY,sBAAgB;AAAA,gBAClG,oBAAC,UAAO,MAAK,MAAK,SAAQ,SAAQ,SAAS,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,GAAG,oBAAC;AAAA,mBALxF,CAMV;AAAA,YAEJ,CAAC;AAAA,YACD,oBAAC,SACC,8BAAC,UAAO,MAAK,MAAK,SAAQ,aAAY,UAAU,MAAM,UAAU,IAAI,SAAS,MAAM,SAAS,CAAC,MAAM,CAAC,GAAG,GAAG,EAAE,MAAM,IAAI,QAAQ,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC,GAAG,oBAE1J,GACF;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,8BAA6B,qBAAO;AAAA,MAClD,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,IAAI,iBAAiB;AAAA,YAC9B,iBAAiB,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,eAAe,EAAE,EAAE;AAAA,YAClE,OAAM;AAAA;AAAA,QACR;AAAA,QACC,IAAI,iBACH,oBAAC,SAAM,OAAM,2BAA0B,MAAK,+FAC1C;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,WAAW;AAAA,YACX,OAAO,OAAO,IAAI,cAAc,EAAE;AAAA,YAClC,UAAU,CAAC,MAAM,IAAI,cAAc,EAAE,OAAO,KAAK;AAAA,YACjD,aAAY;AAAA;AAAA,QACd,GACF;AAAA,SAEJ;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,8BAA6B,8BAAgB;AAAA,MAC3D,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,IAAI,mBAAmB;AAAA,YAChC,iBAAiB,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,iBAAiB,EAAE,EAAE;AAAA,YACpE,OAAM;AAAA;AAAA,QACR;AAAA,QACC,IAAI,mBACH,oBAAC,SAAM,OAAM,sBAAgB,MAAK,6GAChC,8BAAC,SAAM,OAAO,eAAe,UAAU,CAAC,MAAM,iBAAiB,EAAE,OAAO,KAAK,GAAG,aAAY,QAAO,WAAU,QAAO,GACtH;AAAA,SAEJ;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,8BAA6B,wCAA0B;AAAA,MACrE,oBAAC,OAAE,WAAU,+BAA8B,2FAA6E;AAAA,MAGvH,WAAW,SAAS,KACnB,qBAAC,OAAE,WAAU,gEACX;AAAA,4BAAC,YAAO,2BAAa;AAAA,QAAS;AAAA,QAAE,WAAW,KAAK,IAAI;AAAA,QAAE;AAAA,SAExD;AAAA,MAEF,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,OAAM,gBAAe,8BAAC,SAAM,OAAO,OAAO,IAAI,gBAAgB,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,gBAAgB,EAAE,OAAO,KAAK,GAAG,GAAE;AAAA,QAClI,oBAAC,SAAM,OAAM,SAAQ,8BAAC,SAAM,OAAO,OAAO,IAAI,mBAAmB,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,mBAAmB,EAAE,OAAO,KAAK,GAAG,GAAE;AAAA,QACjI,qBAAC,SAAI,WAAU,oCACb;AAAA,8BAAC,SAAM,OAAM,YAAW,8BAAC,SAAM,OAAO,OAAO,IAAI,eAAe,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,eAAe,EAAE,OAAO,KAAK,GAAG,GAAE;AAAA,UAC5H,oBAAC,SAAM,OAAM,UAAS,8BAAC,SAAM,OAAO,OAAO,IAAI,gBAAgB,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,gBAAgB,EAAE,OAAO,KAAK,GAAG,GAAE;AAAA,WAC9H;AAAA,QACA,qBAAC,SAAI,WAAU,0BACb;AAAA,8BAAC,SAAM,OAAM,cAAa,8BAAC,SAAM,OAAO,OAAO,IAAI,eAAe,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,eAAe,EAAE,OAAO,KAAK,GAAG,GAAE;AAAA,UAC9H,oBAAC,SAAM,OAAM,cAAa,8BAAC,SAAM,OAAO,OAAO,IAAI,eAAe,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,eAAe,EAAE,OAAO,KAAK,GAAG,GAAE;AAAA,WAChI;AAAA,QACA,qBAAC,SAAI,WAAU,0BACb;AAAA,8BAAC,SAAM,OAAM,kBAAiB,MAAK,6CACjC,8BAAC,SAAM,OAAO,OAAO,IAAI,iBAAiB,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,iBAAiB,EAAE,OAAO,KAAK,GAAG,GACxG;AAAA,UACA,oBAAC,SAAM,OAAM,wBACX,8BAAC,SAAM,OAAO,OAAO,IAAI,iBAAiB,EAAE,GAAG,UAAU,CAAC,MAAM,IAAI,iBAAiB,EAAE,OAAO,KAAK,GAAG,GACxG;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,iBACb;AAAA,0BAAC,UAAO,SAAS,MAAM,UAAU,MAAM,qBAAO;AAAA,MAC9C,oBAAC,OAAE,WAAU,+BAA8B,qGAE3C;AAAA,OACF;AAAA,KACF;AAEJ;","names":[]}
@@ -9,9 +9,13 @@ function subscribeProduct(siteId, productId, cb) {
9
9
  return () => {
10
10
  };
11
11
  }
12
- return onSnapshot(doc(db, "sites", siteId, "products", productId), (snap) => {
13
- cb(snap.exists() ? { id: snap.id, ...snap.data() } : null);
14
- });
12
+ return onSnapshot(
13
+ doc(db, "sites", siteId, "products", productId),
14
+ (snap) => {
15
+ cb(snap.exists() ? { id: snap.id, ...snap.data() } : null);
16
+ },
17
+ (e) => console.warn("[xicms] live-lijst niet geladen:", e.message)
18
+ );
15
19
  }
16
20
  const saveProductCallable = shopCallable("saveProduct");
17
21
  const deleteProductCallable = shopCallable("deleteProduct");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/shop/productClient.ts"],"sourcesContent":["\"use client\";\n\nimport { doc, getDoc, onSnapshot, setDoc, updateDoc, deleteDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { shopCallable } from \"./lib\";\nimport type { Product } from \"../../schema/shop\";\n\nexport type StoredProduct = Product & { id: string };\n\n/**\n * Eén product, live — voor het paneel dat de bewerkmodus opent bij een productblok.\n *\n * Een abonnement en geen eenmalige lezing: hetzelfde product kan tegelijk openstaan in het\n * Webwinkel-paneel of in een tweede tabblad, en dan hoort dit paneel niet met een oude prijs te\n * blijven staan.\n */\nexport function subscribeProduct(\n siteId: string,\n productId: string,\n cb: (p: StoredProduct | null) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb(null);\n return () => {};\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"products\", productId), (snap) => {\n cb(snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null);\n });\n}\n\n/**\n * Alle product-writes lopen sinds 7.9 door de `saveProduct`-callable — niet om de rechten (die\n * regelen de rules ook), maar om de CACHE. De shoppagina's bewaren hun server-reads onder de tag\n * `xicms:{site}:products`, en alleen de server kan die tag na een write ongeldig verklaren. Een\n * rechtstreekse client-write stond daardoor meteen in Firestore en pas na het ISR-vangnet (tot\n * een uur) op de site — voor een winkel is een uur oude prijs geen detail. Zelfde les als bij\n * news/symbolen/instellingen (SITE-AUDIT §11); producten waren de vergeten laatste.\n *\n * ── DE TERUGVAL ────────────────────────────────────────────────────────────────────────────\n *\n * Core (npm) en functions (aparte deploy) bewegen niet in lockstep. Draait er nog een backend\n * zonder `saveProduct`, dan antwoordt die \"not-found\" — en dan is de rechtstreekse write het oude,\n * werkende gedrag in plaats van een kapotte opslaan-knop. De cache-ping ontbreekt dan, precies\n * zoals vóór 7.9. De terugval kan weg zodra alle omgevingen op functions ≥ 3.11 staan.\n */\nconst saveProductCallable = shopCallable<\n { siteId: string; productId?: string; patch?: boolean; product: Record<string, unknown> },\n { id: string }\n>(\"saveProduct\");\nconst deleteProductCallable = shopCallable<{ siteId: string; productId: string }, { message: string }>(\"deleteProduct\");\n\nfunction isMissingCallable(err: unknown): boolean {\n return (err as { code?: string } | null)?.code === \"functions/not-found\";\n}\n\n/**\n * Volledige save (het formulier): VERVANGT het document, zodat een gewiste variant ook echt weg is.\n */\nexport async function saveProductDoc(siteId: string, productId: string | undefined, product: Product): Promise<string> {\n try {\n const res = await saveProductCallable({ siteId, productId, product: product as unknown as Record<string, unknown> });\n return res.id;\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n if (productId) {\n await setDoc(doc(db, \"sites\", siteId, \"products\", productId), product);\n return productId;\n }\n const ref = doc(db, \"sites\", siteId, \"products\", crypto.randomUUID());\n await setDoc(ref, product);\n return ref.id;\n }\n}\n\n/**\n * Eén veld (het inline potlood op de pagina) — een patch, zodat de rest van het product met al\n * zijn velden (varianten, voorraad, personalisatie) onaangeroerd blijft.\n *\n * Een blokveld gaat naar de WERKVERSIE van die ene pagina en wordt pas zichtbaar als je\n * publiceert. Een product heeft geen werkversie: wie hier de naam wijzigt, wijzigt hem METEEN en\n * OVERAL. Dat staat expliciet in de tekst bij het potlood, zodat het een keuze is en geen\n * verrassing.\n */\nexport async function setProductField(\n siteId: string,\n productId: string,\n field: string,\n value: unknown,\n): Promise<void> {\n try {\n await saveProductCallable({ siteId, productId, patch: true, product: { [field]: value } });\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await updateDoc(doc(db, \"sites\", siteId, \"products\", productId), { [field]: value });\n }\n}\n\n/** Verwijderen — zelfde route, zelfde terugval. */\nexport async function removeProduct(siteId: string, productId: string): Promise<void> {\n try {\n await deleteProductCallable({ siteId, productId });\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await deleteDoc(doc(db, \"sites\", siteId, \"products\", productId));\n }\n}\n\n/** Eenmalige lezing — voor het moment waarop de editor moet weten wat er nú staat. */\nexport async function getProduct(siteId: string, productId: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"products\", productId));\n return snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null;\n}\n"],"mappings":";AAEA,SAAS,KAAK,QAAQ,YAAY,QAAQ,WAAW,iBAAiB;AACtE,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAYtB,SAAS,iBACd,QACA,WACA,IACY;AACZ,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,IAAI;AACP,OAAG,IAAI;AACP,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,SAAO,WAAW,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,CAAC,SAAS;AAC3E,OAAG,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK,IAAI;AAAA,EAC1E,CAAC;AACH;AAiBA,MAAM,sBAAsB,aAG1B,aAAa;AACf,MAAM,wBAAwB,aAAyE,eAAe;AAEtH,SAAS,kBAAkB,KAAuB;AAChD,SAAQ,KAAkC,SAAS;AACrD;AAKA,eAAsB,eAAe,QAAgB,WAA+B,SAAmC;AACrH,MAAI;AACF,UAAM,MAAM,MAAM,oBAAoB,EAAE,QAAQ,WAAW,QAAuD,CAAC;AACnH,WAAO,IAAI;AAAA,EACb,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,QAAI,WAAW;AACb,YAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,OAAO;AACrE,aAAO;AAAA,IACT;AACA,UAAM,MAAM,IAAI,IAAI,SAAS,QAAQ,YAAY,OAAO,WAAW,CAAC;AACpE,UAAM,OAAO,KAAK,OAAO;AACzB,WAAO,IAAI;AAAA,EACb;AACF;AAWA,eAAsB,gBACpB,QACA,WACA,OACA,OACe;AACf,MAAI;AACF,UAAM,oBAAoB,EAAE,QAAQ,WAAW,OAAO,MAAM,SAAS,EAAE,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;AAAA,EAC3F,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC;AAAA,EACrF;AACF;AAGA,eAAsB,cAAc,QAAgB,WAAkC;AACpF,MAAI;AACF,UAAM,sBAAsB,EAAE,QAAQ,UAAU,CAAC;AAAA,EACnD,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AAAA,EACjE;AACF;AAGA,eAAsB,WAAW,QAAgB,WAAkD;AACjG,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AACzE,SAAO,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK;AAC1E;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/shop/productClient.ts"],"sourcesContent":["\"use client\";\n\nimport { doc, getDoc, onSnapshot, setDoc, updateDoc, deleteDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { shopCallable } from \"./lib\";\nimport type { Product } from \"../../schema/shop\";\n\nexport type StoredProduct = Product & { id: string };\n\n/**\n * Eén product, live — voor het paneel dat de bewerkmodus opent bij een productblok.\n *\n * Een abonnement en geen eenmalige lezing: hetzelfde product kan tegelijk openstaan in het\n * Webwinkel-paneel of in een tweede tabblad, en dan hoort dit paneel niet met een oude prijs te\n * blijven staan.\n */\nexport function subscribeProduct(\n siteId: string,\n productId: string,\n cb: (p: StoredProduct | null) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb(null);\n return () => {};\n }\n return onSnapshot(\n doc(db, \"sites\", siteId, \"products\", productId),\n (snap) => {\n cb(snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null);\n },\n (e) => console.warn(\"[xicms] live-lijst niet geladen:\", e.message),\n );\n}\n\n/**\n * Alle product-writes lopen sinds 7.9 door de `saveProduct`-callable — niet om de rechten (die\n * regelen de rules ook), maar om de CACHE. De shoppagina's bewaren hun server-reads onder de tag\n * `xicms:{site}:products`, en alleen de server kan die tag na een write ongeldig verklaren. Een\n * rechtstreekse client-write stond daardoor meteen in Firestore en pas na het ISR-vangnet (tot\n * een uur) op de site — voor een winkel is een uur oude prijs geen detail. Zelfde les als bij\n * news/symbolen/instellingen (SITE-AUDIT §11); producten waren de vergeten laatste.\n *\n * ── DE TERUGVAL ────────────────────────────────────────────────────────────────────────────\n *\n * Core (npm) en functions (aparte deploy) bewegen niet in lockstep. Draait er nog een backend\n * zonder `saveProduct`, dan antwoordt die \"not-found\" — en dan is de rechtstreekse write het oude,\n * werkende gedrag in plaats van een kapotte opslaan-knop. De cache-ping ontbreekt dan, precies\n * zoals vóór 7.9. De terugval kan weg zodra alle omgevingen op functions ≥ 3.11 staan.\n */\nconst saveProductCallable = shopCallable<\n { siteId: string; productId?: string; patch?: boolean; product: Record<string, unknown> },\n { id: string }\n>(\"saveProduct\");\nconst deleteProductCallable = shopCallable<{ siteId: string; productId: string }, { message: string }>(\"deleteProduct\");\n\nfunction isMissingCallable(err: unknown): boolean {\n return (err as { code?: string } | null)?.code === \"functions/not-found\";\n}\n\n/**\n * Volledige save (het formulier): VERVANGT het document, zodat een gewiste variant ook echt weg is.\n */\nexport async function saveProductDoc(siteId: string, productId: string | undefined, product: Product): Promise<string> {\n try {\n const res = await saveProductCallable({ siteId, productId, product: product as unknown as Record<string, unknown> });\n return res.id;\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n if (productId) {\n await setDoc(doc(db, \"sites\", siteId, \"products\", productId), product);\n return productId;\n }\n const ref = doc(db, \"sites\", siteId, \"products\", crypto.randomUUID());\n await setDoc(ref, product);\n return ref.id;\n }\n}\n\n/**\n * Eén veld (het inline potlood op de pagina) — een patch, zodat de rest van het product met al\n * zijn velden (varianten, voorraad, personalisatie) onaangeroerd blijft.\n *\n * Een blokveld gaat naar de WERKVERSIE van die ene pagina en wordt pas zichtbaar als je\n * publiceert. Een product heeft geen werkversie: wie hier de naam wijzigt, wijzigt hem METEEN en\n * OVERAL. Dat staat expliciet in de tekst bij het potlood, zodat het een keuze is en geen\n * verrassing.\n */\nexport async function setProductField(\n siteId: string,\n productId: string,\n field: string,\n value: unknown,\n): Promise<void> {\n try {\n await saveProductCallable({ siteId, productId, patch: true, product: { [field]: value } });\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await updateDoc(doc(db, \"sites\", siteId, \"products\", productId), { [field]: value });\n }\n}\n\n/** Verwijderen — zelfde route, zelfde terugval. */\nexport async function removeProduct(siteId: string, productId: string): Promise<void> {\n try {\n await deleteProductCallable({ siteId, productId });\n } catch (err) {\n if (!isMissingCallable(err)) throw err;\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await deleteDoc(doc(db, \"sites\", siteId, \"products\", productId));\n }\n}\n\n/** Eenmalige lezing — voor het moment waarop de editor moet weten wat er nú staat. */\nexport async function getProduct(siteId: string, productId: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"products\", productId));\n return snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null;\n}\n"],"mappings":";AAEA,SAAS,KAAK,QAAQ,YAAY,QAAQ,WAAW,iBAAiB;AACtE,SAAS,aAAa;AACtB,SAAS,oBAAoB;AAYtB,SAAS,iBACd,QACA,WACA,IACY;AACZ,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,IAAI;AACP,OAAG,IAAI;AACP,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,SAAO;AAAA,IACL,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS;AAAA,IAC9C,CAAC,SAAS;AACR,SAAG,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK,IAAI;AAAA,IAC1E;AAAA,IACA,CAAC,MAAM,QAAQ,KAAK,oCAAoC,EAAE,OAAO;AAAA,EACnE;AACF;AAiBA,MAAM,sBAAsB,aAG1B,aAAa;AACf,MAAM,wBAAwB,aAAyE,eAAe;AAEtH,SAAS,kBAAkB,KAAuB;AAChD,SAAQ,KAAkC,SAAS;AACrD;AAKA,eAAsB,eAAe,QAAgB,WAA+B,SAAmC;AACrH,MAAI;AACF,UAAM,MAAM,MAAM,oBAAoB,EAAE,QAAQ,WAAW,QAAuD,CAAC;AACnH,WAAO,IAAI;AAAA,EACb,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,QAAI,WAAW;AACb,YAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,OAAO;AACrE,aAAO;AAAA,IACT;AACA,UAAM,MAAM,IAAI,IAAI,SAAS,QAAQ,YAAY,OAAO,WAAW,CAAC;AACpE,UAAM,OAAO,KAAK,OAAO;AACzB,WAAO,IAAI;AAAA,EACb;AACF;AAWA,eAAsB,gBACpB,QACA,WACA,OACA,OACe;AACf,MAAI;AACF,UAAM,oBAAoB,EAAE,QAAQ,WAAW,OAAO,MAAM,SAAS,EAAE,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;AAAA,EAC3F,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC;AAAA,EACrF;AACF;AAGA,eAAsB,cAAc,QAAgB,WAAkC;AACpF,MAAI;AACF,UAAM,sBAAsB,EAAE,QAAQ,UAAU,CAAC;AAAA,EACnD,SAAS,KAAK;AACZ,QAAI,CAAC,kBAAkB,GAAG,EAAG,OAAM;AACnC,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,UAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AAAA,EACjE;AACF;AAGA,eAAsB,WAAW,QAAgB,WAAkD;AACjG,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AACzE,SAAO,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK;AAC1E;","names":[]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 404-monitor (8.4): adressen waar bezoekers op uitkwamen maar die niet bestaan — een oude link in
3
+ * Google, op drukwerk of in een nieuwsbrief. Met één keuze wordt het een doorverwijzing naar een
4
+ * bestaande pagina (siteadmin; voor een redacteur alleen de lijst).
5
+ */
6
+ export declare function NietGevondenKaart({ siteId, notFound, magBeheren }: {
7
+ siteId: string;
8
+ notFound?: Record<string, number>;
9
+ magBeheren: boolean;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { doc, getDoc } from "firebase/firestore";
5
+ import { getDb } from "../../firebase/client.js";
6
+ import { Button, Card } from "../ui/primitives.js";
7
+ import { Select } from "../ui/select.js";
8
+ import { useToast } from "../ui/toast.js";
9
+ import { subscribePages } from "../lib/pagesClient.js";
10
+ import { voegRedirectToe } from "../lib/menuHulp.js";
11
+ import { padLabel } from "./lib.js";
12
+ function NietGevondenKaart({ siteId, notFound, magBeheren }) {
13
+ const { toast } = useToast();
14
+ const [pages, setPages] = useState([]);
15
+ const [omgeleid, setOmgeleid] = useState(/* @__PURE__ */ new Set());
16
+ const [open, setOpen] = useState(null);
17
+ const [doel, setDoel] = useState("/");
18
+ const [bezig, setBezig] = useState(false);
19
+ useEffect(() => magBeheren ? subscribePages(siteId, setPages) : void 0, [siteId, magBeheren]);
20
+ useEffect(() => {
21
+ const db = getDb();
22
+ if (!db) return;
23
+ void getDoc(doc(db, "sites", siteId, "settings", "public")).then((s) => setOmgeleid(new Set((s.get("redirects") ?? []).map((r) => r.from)))).catch(() => void 0);
24
+ }, [siteId]);
25
+ const rijen = useMemo(
26
+ () => Object.entries(notFound ?? {}).map(([k, n]) => ({ pad: padLabel(k), n })).filter((r) => !omgeleid.has(r.pad)).sort((a, b) => b.n - a.n).slice(0, 10),
27
+ [notFound, omgeleid]
28
+ );
29
+ const opties = useMemo(
30
+ () => pages.filter((p) => !p.trashed).sort((a, b) => a.path < b.path ? -1 : 1).map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` })),
31
+ [pages]
32
+ );
33
+ async function bewaar(from) {
34
+ setBezig(true);
35
+ try {
36
+ await voegRedirectToe(siteId, from, doel);
37
+ setOmgeleid((s) => /* @__PURE__ */ new Set([...s, from]));
38
+ setOpen(null);
39
+ toast({ title: "Doorverwijzing gemaakt", description: `${from} \u2192 ${doel}`, tone: "success" });
40
+ } catch (e) {
41
+ toast({ title: "Doorverwijzen mislukt", description: e.message, tone: "error" });
42
+ } finally {
43
+ setBezig(false);
44
+ }
45
+ }
46
+ return /* @__PURE__ */ jsxs(Card, { children: [
47
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-sm font-semibold text-slate-800", children: "Niet gevonden" }),
48
+ /* @__PURE__ */ jsxs("p", { className: "mb-3 text-xs text-slate-500", children: [
49
+ "Adressen die bezoekers probeerden maar die niet (meer) bestaan \u2014 vaak een oude link in Google of op drukwerk.",
50
+ magBeheren ? " Stuur ze door naar een bestaande pagina." : ""
51
+ ] }),
52
+ rijen.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Niets gemist deze maand." }) : /* @__PURE__ */ jsx("ul", { className: "grid gap-2", children: rijen.map((r) => /* @__PURE__ */ jsxs("li", { className: "rounded-lg border border-slate-200 p-2 text-sm", children: [
53
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
54
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-mono text-xs", title: r.pad, children: r.pad }),
55
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
56
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-slate-500", children: [
57
+ r.n,
58
+ "\xD7"
59
+ ] }),
60
+ magBeheren && open !== r.pad && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: () => {
61
+ setOpen(r.pad);
62
+ setDoel(opties[0]?.value ?? "/");
63
+ }, children: "Doorverwijzen" })
64
+ ] })
65
+ ] }),
66
+ open === r.pad && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [
67
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-600", children: "naar" }),
68
+ /* @__PURE__ */ jsx("div", { className: "min-w-48 flex-1", children: /* @__PURE__ */ jsx(Select, { value: doel, onValueChange: setDoel, options: opties, placeholder: "Kies een pagina\u2026" }) }),
69
+ /* @__PURE__ */ jsx(Button, { loading: bezig, size: "sm", disabled: bezig, onClick: () => void bewaar(r.pad), children: "Opslaan" }),
70
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setOpen(null), children: "Annuleren" })
71
+ ] })
72
+ ] }, r.pad)) })
73
+ ] });
74
+ }
75
+ export {
76
+ NietGevondenKaart
77
+ };
78
+ //# sourceMappingURL=NietGevonden.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/stats/NietGevonden.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { doc, getDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { Button, Card } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { useToast } from \"../ui/toast\";\nimport { subscribePages, type DraftPage } from \"../lib/pagesClient\";\nimport { voegRedirectToe } from \"../lib/menuHulp\";\nimport { padLabel } from \"./lib\";\n\n/**\n * 404-monitor (8.4): adressen waar bezoekers op uitkwamen maar die niet bestaan — een oude link in\n * Google, op drukwerk of in een nieuwsbrief. Met één keuze wordt het een doorverwijzing naar een\n * bestaande pagina (siteadmin; voor een redacteur alleen de lijst).\n */\nexport function NietGevondenKaart({ siteId, notFound, magBeheren }: { siteId: string; notFound?: Record<string, number>; magBeheren: boolean }) {\n const { toast } = useToast();\n const [pages, setPages] = useState<DraftPage[]>([]);\n const [omgeleid, setOmgeleid] = useState<Set<string>>(new Set());\n const [open, setOpen] = useState<string | null>(null);\n const [doel, setDoel] = useState(\"/\");\n const [bezig, setBezig] = useState(false);\n\n useEffect(() => (magBeheren ? subscribePages(siteId, setPages) : undefined), [siteId, magBeheren]);\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n void getDoc(doc(db, \"sites\", siteId, \"settings\", \"public\"))\n .then((s) => setOmgeleid(new Set(((s.get(\"redirects\") as { from: string }[] | undefined) ?? []).map((r) => r.from))))\n .catch(() => undefined);\n }, [siteId]);\n\n const rijen = useMemo(\n () =>\n Object.entries(notFound ?? {})\n .map(([k, n]) => ({ pad: padLabel(k), n }))\n .filter((r) => !omgeleid.has(r.pad))\n .sort((a, b) => b.n - a.n)\n .slice(0, 10),\n [notFound, omgeleid],\n );\n const opties = useMemo(\n () => pages.filter((p) => !p.trashed).sort((a, b) => (a.path < b.path ? -1 : 1)).map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` })),\n [pages],\n );\n\n async function bewaar(from: string) {\n setBezig(true);\n try {\n await voegRedirectToe(siteId, from, doel);\n setOmgeleid((s) => new Set([...s, from]));\n setOpen(null);\n toast({ title: \"Doorverwijzing gemaakt\", description: `${from} → ${doel}`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Doorverwijzen mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBezig(false);\n }\n }\n\n return (\n <Card>\n <h3 className=\"mb-1 text-sm font-semibold text-slate-800\">Niet gevonden</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Adressen die bezoekers probeerden maar die niet (meer) bestaan — vaak een oude link in Google of op\n drukwerk.{magBeheren ? \" Stuur ze door naar een bestaande pagina.\" : \"\"}\n </p>\n {rijen.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Niets gemist deze maand.</p>\n ) : (\n <ul className=\"grid gap-2\">\n {rijen.map((r) => (\n <li key={r.pad} className=\"rounded-lg border border-slate-200 p-2 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"min-w-0 truncate font-mono text-xs\" title={r.pad}>{r.pad}</span>\n <span className=\"flex shrink-0 items-center gap-2\">\n <span className=\"text-xs text-slate-500\">{r.n}×</span>\n {magBeheren && open !== r.pad && (\n <Button size=\"sm\" variant=\"secondary\" onClick={() => { setOpen(r.pad); setDoel(opties[0]?.value ?? \"/\"); }}>\n Doorverwijzen\n </Button>\n )}\n </span>\n </div>\n {open === r.pad && (\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <span className=\"text-xs text-slate-600\">naar</span>\n <div className=\"min-w-48 flex-1\">\n <Select value={doel} onValueChange={setDoel} options={opties} placeholder=\"Kies een pagina…\" />\n </div>\n <Button loading={bezig} size=\"sm\" disabled={bezig} onClick={() => void bewaar(r.pad)}>{\"Opslaan\"}</Button>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setOpen(null)}>Annuleren</Button>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
@@ -4,6 +4,9 @@ import { useEffect, useMemo, useState } from "react";
4
4
  import { collection, getDocs } from "firebase/firestore";
5
5
  import { getDb } from "../../firebase/client.js";
6
6
  import { Card } from "../ui/primitives.js";
7
+ import { NietGevondenKaart } from "./NietGevonden.js";
8
+ import { useAuth } from "../../auth/AuthProvider.js";
9
+ import { canManageSite } from "../../auth/roles.js";
7
10
  import { Select } from "../ui/select.js";
8
11
  import { AlsTabel, BalkjesLijst, DagStaven, MaandLijn, StatTegel } from "../ui/charts.js";
9
12
  import {
@@ -19,6 +22,7 @@ import {
19
22
  gebeurtenisLabel
20
23
  } from "./lib.js";
21
24
  function StatsPanel({ siteId }) {
25
+ const { role } = useAuth();
22
26
  const [months, setMonths] = useState([]);
23
27
  const [loading, setLoading] = useState(true);
24
28
  const [gekozen, setGekozen] = useState(huidigMaandId());
@@ -154,7 +158,8 @@ function StatsPanel({ siteId }) {
154
158
  ] })
155
159
  ] })
156
160
  ] }),
157
- /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-400", children: "Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) \u2014 \xE9\xE9n telling per bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm." })
161
+ /* @__PURE__ */ jsx(NietGevondenKaart, { siteId, notFound: maand?.notFound, magBeheren: canManageSite(role) }),
162
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) \u2014 \xE9\xE9n telling per bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm." })
158
163
  ] });
159
164
  }
160
165
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/stats/StatsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { collection, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { Card } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { AlsTabel, BalkjesLijst, DagStaven, MaandLijn, StatTegel } from \"../ui/charts\";\nimport {\n dagenInMaand,\n dagReeks,\n deltaTovVorige,\n huidigMaandId,\n maandLabel,\n topPaden,\n topVan,\n verwijzerLabel,\n apparaatLabel,\n gebeurtenisLabel,\n type MaandStat,\n} from \"./lib\";\n\n/**\n * Bezoekersstatistieken (7.17) — van drie kale tellers naar een leesbaar verhaal: tegels met\n * context (delta t.o.v. vorige maand, t/m dezelfde dag), een dag-grafiek met hover, de\n * maandtrend als lijn, en de toppagina's als balkjes. Zelfde privacy-vriendelijke bron\n * (`sites/{siteId}/stats`, eigen teller, geen cookies) — er is alleen beter naar gekeken.\n */\nexport function StatsPanel({ siteId }: { siteId: string }) {\n const [months, setMonths] = useState<MaandStat[]>([]);\n const [loading, setLoading] = useState(true);\n const [gekozen, setGekozen] = useState<string>(huidigMaandId());\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoading(false);\n return;\n }\n let active = true;\n getDocs(collection(db, \"sites\", siteId, \"stats\"))\n .then((snap) => {\n if (!active) return;\n const uit = snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as Omit<MaandStat, \"id\">) }))\n .filter((m) => /^\\d{4}-\\d{2}$/.test(m.id))\n .sort((a, b) => a.id.localeCompare(b.id));\n setMonths(uit);\n // Standaard de nieuwste maand mét data (meestal de huidige).\n if (uit.length && !uit.some((m) => m.id === huidigMaandId())) setGekozen(uit[uit.length - 1].id);\n setLoading(false);\n })\n .catch(() => active && setLoading(false));\n return () => {\n active = false;\n };\n }, [siteId]);\n\n const maand = months.find((m) => m.id === gekozen);\n const vorigeId = useMemo(() => {\n const [j, m] = gekozen.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 2, 1));\n return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, \"0\")}`;\n }, [gekozen]);\n const vorige = months.find((m) => m.id === vorigeId);\n\n const isHuidige = gekozen === huidigMaandId();\n const nDagen = dagenInMaand(gekozen);\n const vandaagDag = new Date().getUTCDate();\n const dagen = useMemo(() => dagReeks(maand, nDagen), [maand, nDagen]);\n const totaalMaand = maand?.total ?? 0;\n const totaalOoit = useMemo(() => months.reduce((s, m) => s + (m.total ?? 0), 0), [months]);\n // Gemiddelde over de dagen die er al ZIJN: een lopende maand door 31 delen praat het cijfer omlaag.\n const verstreken = isHuidige ? Math.max(1, vandaagDag) : nDagen;\n const delta = deltaTovVorige(maand, vorige, isHuidige ? vandaagDag : nDagen);\n const paden = useMemo(() => topPaden(maand, 8), [maand]);\n const verwijzers = useMemo(() => topVan(maand?.refs, 8, verwijzerLabel), [maand]);\n const apparaten = useMemo(() => topVan(maand?.devices, 3, apparaatLabel), [maand]);\n const gebeurtenissen = useMemo(() => topVan(maand?.events, 6, gebeurtenisLabel), [maand]);\n const conversie = maand?.events?.formulier || maand?.events?.bestelling\n ? (((maand?.events?.formulier ?? 0) + (maand?.events?.bestelling ?? 0)) / Math.max(1, totaalMaand)) * 100\n : null;\n const trend = useMemo(\n () => months.slice(-12).map((m) => ({ label: maandLabel(m.id), waarde: m.total ?? 0 })),\n [months],\n );\n\n if (loading) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n if (months.length === 0) {\n return (\n <Card>\n <p className=\"text-sm text-slate-600\">\n Nog geen bezoekersdata. Zodra de live site bezocht wordt, verschijnen hier de cijfers.\n </p>\n </Card>\n );\n }\n\n return (\n <div className=\"grid gap-4\">\n {/* Eén filterregel boven de inhoud: de maand waar alles hieronder over gaat. */}\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm text-slate-500\">\n Bezoek in <span className=\"font-medium text-slate-700\">{maandLabel(gekozen, true)}</span>\n {isHuidige ? \" (loopt nog)\" : \"\"}\n </p>\n <Select\n value={gekozen}\n onValueChange={setGekozen}\n placeholder=\"Maand\"\n className=\"w-44\"\n options={[...months].reverse().map((m) => ({ value: m.id, label: maandLabel(m.id, true) }))}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatTegel\n label=\"Weergaven deze maand\"\n waarde={totaalMaand}\n delta={delta}\n deltaLabel={`t.o.v. ${maandLabel(vorigeId)}${isHuidige ? \" (t/m vandaag)\" : \"\"}`}\n />\n {isHuidige ? (\n <StatTegel label=\"Vandaag\" waarde={dagen[vandaagDag - 1] ?? 0} />\n ) : (\n <StatTegel label=\"Drukste dag\" waarde={Math.max(0, ...dagen)} />\n )}\n <StatTegel label=\"Gemiddeld per dag\" waarde={Math.round(totaalMaand / verstreken)} />\n <StatTegel label=\"Totaal gemeten\" waarde={totaalOoit} />\n </div>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Per dag</h3>\n {totaalMaand === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <>\n <DagStaven\n waarden={dagen}\n accentIndex={isHuidige ? vandaagDag - 1 : undefined}\n hoogte={170}\n titel={`Weergaven per dag in ${maandLabel(gekozen, true)}`}\n />\n <AlsTabel\n kop={[\"Dag\", \"Weergaven\"]}\n rijen={dagen.map((v, i) => [`${i + 1} ${maandLabel(gekozen)}`, v] as [string, number]).filter(([, v]) => v > 0)}\n />\n </>\n )}\n </Card>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Maand op maand</h3>\n {trend.length < 2 ? (\n <p className=\"text-sm text-slate-500\">Nog te weinig maanden voor een trend — die groeit hier vanzelf.</p>\n ) : (\n <>\n <MaandLijn punten={trend} hoogte={170} titel=\"Weergaven per maand, laatste twaalf maanden\" />\n <AlsTabel kop={[\"Maand\", \"Weergaven\"]} rijen={trend.map((p) => [p.label, p.waarde] as [string, number])} />\n </>\n )}\n </Card>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Best bezochte pagina&apos;s</h3>\n {paden.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <BalkjesLijst items={paden} />\n )}\n </Card>\n </div>\n\n {/* 7.24: waar komen bezoekers vandaan, waarop kijken ze, en wat dóén ze — de drie vragen\n die klanten stellen zodra ze een cijfer zien. Verzameld sinds 7.24; oudere maanden\n tonen deze kaarten niet. */}\n {(verwijzers.length > 0 || apparaten.length > 0 || gebeurtenissen.length > 0) && (\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Waar bezoekers vandaan komen</h3>\n {verwijzers.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={verwijzers} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Apparaat</h3>\n {apparaten.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={apparaten} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Wat bezoekers doen</h3>\n {gebeurtenissen.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen formulieren of bestellingen deze maand.</p>\n ) : (\n <>\n <BalkjesLijst items={gebeurtenissen} />\n {conversie !== null && (\n <p className=\"mt-3 text-xs text-slate-500\">\n {Math.round(conversie * 10) / 10}% van de weergaven leidde tot een formulier of bestelling.\n </p>\n )}\n </>\n )}\n </Card>\n </div>\n )}\n\n <p className=\"text-xs text-slate-400\">\n Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) — één telling per\n bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm.\n </p>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/stats/StatsPanel.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { collection, getDocs } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport { Card } from \"../ui/primitives\";\nimport { NietGevondenKaart } from \"./NietGevonden\";\nimport { useAuth } from \"../../auth/AuthProvider\";\nimport { canManageSite } from \"../../auth/roles\";\nimport { Select } from \"../ui/select\";\nimport { AlsTabel, BalkjesLijst, DagStaven, MaandLijn, StatTegel } from \"../ui/charts\";\nimport {\n dagenInMaand,\n dagReeks,\n deltaTovVorige,\n huidigMaandId,\n maandLabel,\n topPaden,\n topVan,\n verwijzerLabel,\n apparaatLabel,\n gebeurtenisLabel,\n type MaandStat,\n} from \"./lib\";\n\n/**\n * Bezoekersstatistieken (7.17) — van drie kale tellers naar een leesbaar verhaal: tegels met\n * context (delta t.o.v. vorige maand, t/m dezelfde dag), een dag-grafiek met hover, de\n * maandtrend als lijn, en de toppagina's als balkjes. Zelfde privacy-vriendelijke bron\n * (`sites/{siteId}/stats`, eigen teller, geen cookies) — er is alleen beter naar gekeken.\n */\nexport function StatsPanel({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n const [months, setMonths] = useState<MaandStat[]>([]);\n const [loading, setLoading] = useState(true);\n const [gekozen, setGekozen] = useState<string>(huidigMaandId());\n\n useEffect(() => {\n const db = getDb();\n if (!db) {\n setLoading(false);\n return;\n }\n let active = true;\n getDocs(collection(db, \"sites\", siteId, \"stats\"))\n .then((snap) => {\n if (!active) return;\n const uit = snap.docs\n .map((d) => ({ id: d.id, ...(d.data() as Omit<MaandStat, \"id\">) }))\n .filter((m) => /^\\d{4}-\\d{2}$/.test(m.id))\n .sort((a, b) => a.id.localeCompare(b.id));\n setMonths(uit);\n // Standaard de nieuwste maand mét data (meestal de huidige).\n if (uit.length && !uit.some((m) => m.id === huidigMaandId())) setGekozen(uit[uit.length - 1].id);\n setLoading(false);\n })\n .catch(() => active && setLoading(false));\n return () => {\n active = false;\n };\n }, [siteId]);\n\n const maand = months.find((m) => m.id === gekozen);\n const vorigeId = useMemo(() => {\n const [j, m] = gekozen.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 2, 1));\n return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, \"0\")}`;\n }, [gekozen]);\n const vorige = months.find((m) => m.id === vorigeId);\n\n const isHuidige = gekozen === huidigMaandId();\n const nDagen = dagenInMaand(gekozen);\n const vandaagDag = new Date().getUTCDate();\n const dagen = useMemo(() => dagReeks(maand, nDagen), [maand, nDagen]);\n const totaalMaand = maand?.total ?? 0;\n const totaalOoit = useMemo(() => months.reduce((s, m) => s + (m.total ?? 0), 0), [months]);\n // Gemiddelde over de dagen die er al ZIJN: een lopende maand door 31 delen praat het cijfer omlaag.\n const verstreken = isHuidige ? Math.max(1, vandaagDag) : nDagen;\n const delta = deltaTovVorige(maand, vorige, isHuidige ? vandaagDag : nDagen);\n const paden = useMemo(() => topPaden(maand, 8), [maand]);\n const verwijzers = useMemo(() => topVan(maand?.refs, 8, verwijzerLabel), [maand]);\n const apparaten = useMemo(() => topVan(maand?.devices, 3, apparaatLabel), [maand]);\n const gebeurtenissen = useMemo(() => topVan(maand?.events, 6, gebeurtenisLabel), [maand]);\n const conversie = maand?.events?.formulier || maand?.events?.bestelling\n ? (((maand?.events?.formulier ?? 0) + (maand?.events?.bestelling ?? 0)) / Math.max(1, totaalMaand)) * 100\n : null;\n const trend = useMemo(\n () => months.slice(-12).map((m) => ({ label: maandLabel(m.id), waarde: m.total ?? 0 })),\n [months],\n );\n\n if (loading) return <p className=\"text-sm text-slate-500\">Laden…</p>;\n\n if (months.length === 0) {\n return (\n <Card>\n <p className=\"text-sm text-slate-600\">\n Nog geen bezoekersdata. Zodra de live site bezocht wordt, verschijnen hier de cijfers.\n </p>\n </Card>\n );\n }\n\n return (\n <div className=\"grid gap-4\">\n {/* Eén filterregel boven de inhoud: de maand waar alles hieronder over gaat. */}\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"text-sm text-slate-500\">\n Bezoek in <span className=\"font-medium text-slate-700\">{maandLabel(gekozen, true)}</span>\n {isHuidige ? \" (loopt nog)\" : \"\"}\n </p>\n <Select\n value={gekozen}\n onValueChange={setGekozen}\n placeholder=\"Maand\"\n className=\"w-44\"\n options={[...months].reverse().map((m) => ({ value: m.id, label: maandLabel(m.id, true) }))}\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatTegel\n label=\"Weergaven deze maand\"\n waarde={totaalMaand}\n delta={delta}\n deltaLabel={`t.o.v. ${maandLabel(vorigeId)}${isHuidige ? \" (t/m vandaag)\" : \"\"}`}\n />\n {isHuidige ? (\n <StatTegel label=\"Vandaag\" waarde={dagen[vandaagDag - 1] ?? 0} />\n ) : (\n <StatTegel label=\"Drukste dag\" waarde={Math.max(0, ...dagen)} />\n )}\n <StatTegel label=\"Gemiddeld per dag\" waarde={Math.round(totaalMaand / verstreken)} />\n <StatTegel label=\"Totaal gemeten\" waarde={totaalOoit} />\n </div>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Per dag</h3>\n {totaalMaand === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <>\n <DagStaven\n waarden={dagen}\n accentIndex={isHuidige ? vandaagDag - 1 : undefined}\n hoogte={170}\n titel={`Weergaven per dag in ${maandLabel(gekozen, true)}`}\n />\n <AlsTabel\n kop={[\"Dag\", \"Weergaven\"]}\n rijen={dagen.map((v, i) => [`${i + 1} ${maandLabel(gekozen)}`, v] as [string, number]).filter(([, v]) => v > 0)}\n />\n </>\n )}\n </Card>\n\n <div className=\"grid gap-4 lg:grid-cols-2\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Maand op maand</h3>\n {trend.length < 2 ? (\n <p className=\"text-sm text-slate-500\">Nog te weinig maanden voor een trend — die groeit hier vanzelf.</p>\n ) : (\n <>\n <MaandLijn punten={trend} hoogte={170} titel=\"Weergaven per maand, laatste twaalf maanden\" />\n <AlsTabel kop={[\"Maand\", \"Weergaven\"]} rijen={trend.map((p) => [p.label, p.waarde] as [string, number])} />\n </>\n )}\n </Card>\n\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Best bezochte pagina&apos;s</h3>\n {paden.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen data in deze maand.</p>\n ) : (\n <BalkjesLijst items={paden} />\n )}\n </Card>\n </div>\n\n {/* 7.24: waar komen bezoekers vandaan, waarop kijken ze, en wat dóén ze — de drie vragen\n die klanten stellen zodra ze een cijfer zien. Verzameld sinds 7.24; oudere maanden\n tonen deze kaarten niet. */}\n {(verwijzers.length > 0 || apparaten.length > 0 || gebeurtenissen.length > 0) && (\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Waar bezoekers vandaan komen</h3>\n {verwijzers.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={verwijzers} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Apparaat</h3>\n {apparaten.length === 0 ? <p className=\"text-sm text-slate-500\">Nog geen data.</p> : <BalkjesLijst items={apparaten} />}\n </Card>\n <Card>\n <h3 className=\"mb-4 text-sm font-semibold text-slate-800\">Wat bezoekers doen</h3>\n {gebeurtenissen.length === 0 ? (\n <p className=\"text-sm text-slate-500\">Nog geen formulieren of bestellingen deze maand.</p>\n ) : (\n <>\n <BalkjesLijst items={gebeurtenissen} />\n {conversie !== null && (\n <p className=\"mt-3 text-xs text-slate-500\">\n {Math.round(conversie * 10) / 10}% van de weergaven leidde tot een formulier of bestelling.\n </p>\n )}\n </>\n )}\n </Card>\n </div>\n )}\n\n <NietGevondenKaart siteId={siteId} notFound={maand?.notFound} magBeheren={canManageSite(role)} />\n\n <p className=\"text-xs text-slate-500\">\n Privacy-vriendelijk geteld (geen cookies, geen persoonsgegevens) — één telling per\n bezoeker-sessie per pagina. Van de verwijzer wordt alleen de website bewaard, nooit de pagina of zoekterm.\n </p>\n </div>\n );\n}\n"],"mappings":";AA2FsB,SAkDZ,UAlDY,KAgBd,YAhBc;AAzFtB,SAAS,WAAW,SAAS,gBAAgB;AAC7C,SAAS,YAAY,eAAe;AACpC,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,yBAAyB;AAClC,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AACvB,SAAS,UAAU,cAAc,WAAW,WAAW,iBAAiB;AACxE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAQA,SAAS,WAAW,EAAE,OAAO,GAAuB;AACzD,QAAM,EAAE,KAAK,IAAI,QAAQ;AACzB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsB,CAAC,CAAC;AACpD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAiB,cAAc,CAAC;AAE9D,YAAU,MAAM;AACd,UAAM,KAAK,MAAM;AACjB,QAAI,CAAC,IAAI;AACP,iBAAW,KAAK;AAChB;AAAA,IACF;AACA,QAAI,SAAS;AACb,YAAQ,WAAW,IAAI,SAAS,QAAQ,OAAO,CAAC,EAC7C,KAAK,CAAC,SAAS;AACd,UAAI,CAAC,OAAQ;AACb,YAAM,MAAM,KAAK,KACd,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,KAAK,EAA4B,EAAE,EACjE,OAAO,CAAC,MAAM,gBAAgB,KAAK,EAAE,EAAE,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AAC1C,gBAAU,GAAG;AAEb,UAAI,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,cAAc,CAAC,EAAG,YAAW,IAAI,IAAI,SAAS,CAAC,EAAE,EAAE;AAC/F,iBAAW,KAAK;AAAA,IAClB,CAAC,EACA,MAAM,MAAM,UAAU,WAAW,KAAK,CAAC;AAC1C,WAAO,MAAM;AACX,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO;AACjD,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,CAAC,GAAG,CAAC,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAC5C,UAAM,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,GAAG,CAAC,CAAC;AAC/C,WAAO,GAAG,EAAE,eAAe,CAAC,IAAI,OAAO,EAAE,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAC9E,GAAG,CAAC,OAAO,CAAC;AACZ,QAAM,SAAS,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ;AAEnD,QAAM,YAAY,YAAY,cAAc;AAC5C,QAAM,SAAS,aAAa,OAAO;AACnC,QAAM,cAAa,oBAAI,KAAK,GAAE,WAAW;AACzC,QAAM,QAAQ,QAAQ,MAAM,SAAS,OAAO,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC;AACpE,QAAM,cAAc,OAAO,SAAS;AACpC,QAAM,aAAa,QAAQ,MAAM,OAAO,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,SAAS,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;AAEzF,QAAM,aAAa,YAAY,KAAK,IAAI,GAAG,UAAU,IAAI;AACzD,QAAM,QAAQ,eAAe,OAAO,QAAQ,YAAY,aAAa,MAAM;AAC3E,QAAM,QAAQ,QAAQ,MAAM,SAAS,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AACvD,QAAM,aAAa,QAAQ,MAAM,OAAO,OAAO,MAAM,GAAG,cAAc,GAAG,CAAC,KAAK,CAAC;AAChF,QAAM,YAAY,QAAQ,MAAM,OAAO,OAAO,SAAS,GAAG,aAAa,GAAG,CAAC,KAAK,CAAC;AACjF,QAAM,iBAAiB,QAAQ,MAAM,OAAO,OAAO,QAAQ,GAAG,gBAAgB,GAAG,CAAC,KAAK,CAAC;AACxF,QAAM,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,eACtD,OAAO,QAAQ,aAAa,MAAM,OAAO,QAAQ,cAAc,MAAM,KAAK,IAAI,GAAG,WAAW,IAAK,MACpG;AACJ,QAAM,QAAQ;AAAA,IACZ,MAAM,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,WAAW,EAAE,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,EAAE;AAAA,IACtF,CAAC,MAAM;AAAA,EACT;AAEA,MAAI,QAAS,QAAO,oBAAC,OAAE,WAAU,0BAAyB,yBAAM;AAEhE,MAAI,OAAO,WAAW,GAAG;AACvB,WACE,oBAAC,QACC,8BAAC,OAAE,WAAU,0BAAyB,oGAEtC,GACF;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAU,cAEb;AAAA,yBAAC,SAAI,WAAU,2CACb;AAAA,2BAAC,OAAE,WAAU,0BAAyB;AAAA;AAAA,QAC1B,oBAAC,UAAK,WAAU,8BAA8B,qBAAW,SAAS,IAAI,GAAE;AAAA,QACjF,YAAY,iBAAiB;AAAA,SAChC;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,eAAe;AAAA,UACf,aAAY;AAAA,UACZ,WAAU;AAAA,UACV,SAAS,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,WAAW,EAAE,IAAI,IAAI,EAAE,EAAE;AAAA;AAAA,MAC5F;AAAA,OACF;AAAA,IAEA,qBAAC,SAAI,WAAU,yCACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,UACA,YAAY,UAAU,WAAW,QAAQ,CAAC,GAAG,YAAY,mBAAmB,EAAE;AAAA;AAAA,MAChF;AAAA,MACC,YACC,oBAAC,aAAU,OAAM,WAAU,QAAQ,MAAM,aAAa,CAAC,KAAK,GAAG,IAE/D,oBAAC,aAAU,OAAM,eAAc,QAAQ,KAAK,IAAI,GAAG,GAAG,KAAK,GAAG;AAAA,MAEhE,oBAAC,aAAU,OAAM,qBAAoB,QAAQ,KAAK,MAAM,cAAc,UAAU,GAAG;AAAA,MACnF,oBAAC,aAAU,OAAM,kBAAiB,QAAQ,YAAY;AAAA,OACxD;AAAA,IAEA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,6CAA4C,qBAAO;AAAA,MAChE,gBAAgB,IACf,oBAAC,OAAE,WAAU,0BAAyB,0CAA4B,IAElE,iCACE;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,aAAa,YAAY,aAAa,IAAI;AAAA,YAC1C,QAAQ;AAAA,YACR,OAAO,wBAAwB,WAAW,SAAS,IAAI,CAAC;AAAA;AAAA,QAC1D;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK,CAAC,OAAO,WAAW;AAAA,YACxB,OAAO,MAAM,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,WAAW,OAAO,CAAC,IAAI,CAAC,CAAqB,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC;AAAA;AAAA,QAChH;AAAA,SACF;AAAA,OAEJ;AAAA,IAEA,qBAAC,SAAI,WAAU,6BACb;AAAA,2BAAC,QACC;AAAA,4BAAC,QAAG,WAAU,6CAA4C,4BAAc;AAAA,QACvE,MAAM,SAAS,IACd,oBAAC,OAAE,WAAU,0BAAyB,kFAA+D,IAErG,iCACE;AAAA,8BAAC,aAAU,QAAQ,OAAO,QAAQ,KAAK,OAAM,+CAA8C;AAAA,UAC3F,oBAAC,YAAS,KAAK,CAAC,SAAS,WAAW,GAAG,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAqB,GAAG;AAAA,WAC3G;AAAA,SAEJ;AAAA,MAEA,qBAAC,QACC;AAAA,4BAAC,QAAG,WAAU,6CAA4C,oCAA2B;AAAA,QACpF,MAAM,WAAW,IAChB,oBAAC,OAAE,WAAU,0BAAyB,0CAA4B,IAElE,oBAAC,gBAAa,OAAO,OAAO;AAAA,SAEhC;AAAA,OACF;AAAA,KAKE,WAAW,SAAS,KAAK,UAAU,SAAS,KAAK,eAAe,SAAS,MACzE,qBAAC,SAAI,WAAU,6BACb;AAAA,2BAAC,QACC;AAAA,4BAAC,QAAG,WAAU,6CAA4C,0CAA4B;AAAA,QACrF,WAAW,WAAW,IAAI,oBAAC,OAAE,WAAU,0BAAyB,4BAAc,IAAO,oBAAC,gBAAa,OAAO,YAAY;AAAA,SACzH;AAAA,MACA,qBAAC,QACC;AAAA,4BAAC,QAAG,WAAU,6CAA4C,sBAAQ;AAAA,QACjE,UAAU,WAAW,IAAI,oBAAC,OAAE,WAAU,0BAAyB,4BAAc,IAAO,oBAAC,gBAAa,OAAO,WAAW;AAAA,SACvH;AAAA,MACA,qBAAC,QACC;AAAA,4BAAC,QAAG,WAAU,6CAA4C,gCAAkB;AAAA,QAC3E,eAAe,WAAW,IACzB,oBAAC,OAAE,WAAU,0BAAyB,8DAAgD,IAEtF,iCACE;AAAA,8BAAC,gBAAa,OAAO,gBAAgB;AAAA,UACpC,cAAc,QACb,qBAAC,OAAE,WAAU,+BACV;AAAA,iBAAK,MAAM,YAAY,EAAE,IAAI;AAAA,YAAG;AAAA,aACnC;AAAA,WAEJ;AAAA,SAEJ;AAAA,OACF;AAAA,IAGF,oBAAC,qBAAkB,QAAgB,UAAU,OAAO,UAAU,YAAY,cAAc,IAAI,GAAG;AAAA,IAE/F,oBAAC,OAAE,WAAU,0BAAyB,sNAGtC;AAAA,KACF;AAEJ;","names":[]}
@@ -11,6 +11,8 @@ export interface MaandStat {
11
11
  refs?: Record<string, number>;
12
12
  devices?: Record<string, number>;
13
13
  events?: Record<string, number>;
14
+ /** 8.4: niet-gevonden paden (404-monitor), sleutel zoals `paths`. */
15
+ notFound?: Record<string, number>;
14
16
  }
15
17
  /** trackView slaat paden veilig op: "home" voor "/", percent-encoded tekens. Terug naar leesbaar. */
16
18
  export declare function padLabel(sleutel: string): string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/stats/lib.ts"],"sourcesContent":["/**\n * Gedeelde stats-helpers — gebruikt door het Statistieken-paneel én de Start-widget, zodat\n * paden en maanden op beide plekken hetzelfde heten.\n */\n\nexport interface MaandStat {\n id: string; // \"YYYY-MM\"\n total?: number;\n days?: Record<string, number>;\n paths?: Record<string, number>;\n /** 7.24: verwijzers (host of gebundelde naam zoals \"google\"), apparaten en gebeurtenissen. */\n refs?: Record<string, number>;\n devices?: Record<string, number>;\n events?: Record<string, number>;\n}\n\n/** trackView slaat paden veilig op: \"home\" voor \"/\", percent-encoded tekens. Terug naar leesbaar. */\nexport function padLabel(sleutel: string): string {\n if (sleutel === \"home\") return \"/\";\n let s = sleutel;\n try {\n s = decodeURIComponent(sleutel.replace(/%(?![0-9a-fA-F]{2})/g, \"%25\"));\n } catch {\n /* oude, niet-gecodeerde sleutel: laat staan */\n }\n return `/${s.replace(/_/g, \"/\")}`;\n}\n\n/** Verwijzer-sleutel → leesbaar (\"google\" → \"Google\", \"nieuws%2enl\" → \"nieuws.nl\"). */\nexport function verwijzerLabel(sleutel: string): string {\n const vast: Record<string, string> = {\n direct: \"Direct / onbekend\",\n google: \"Google\",\n bing: \"Bing\",\n duckduckgo: \"DuckDuckGo\",\n facebook: \"Facebook\",\n instagram: \"Instagram\",\n linkedin: \"LinkedIn\",\n x: \"X (Twitter)\",\n };\n if (vast[sleutel]) return vast[sleutel];\n try {\n return decodeURIComponent(sleutel);\n } catch {\n return sleutel;\n }\n}\n\nexport function apparaatLabel(sleutel: string): string {\n return ({ mobiel: \"Mobiel\", tablet: \"Tablet\", desktop: \"Desktop\" } as Record<string, string>)[sleutel] ?? sleutel;\n}\n\nexport function gebeurtenisLabel(sleutel: string): string {\n return (\n ({\n formulier: \"Formulier verstuurd\",\n bestelling: \"Bestelling betaald\",\n uitgaand: \"Uitgaande link\",\n telefoon: \"Telefoonnummer geklikt\",\n mail: \"E-mailadres geklikt\",\n whatsapp: \"WhatsApp geopend\",\n }) as Record<string, string>\n )[sleutel] ?? sleutel;\n}\n\n/** Top-N van een teller-record, gelabeld en gesorteerd. */\nexport function topVan(record: Record<string, number> | undefined, n: number, label: (k: string) => string): { label: string; waarde: number }[] {\n return Object.entries(record ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([k, w]) => ({ label: label(k), waarde: w }));\n}\n\n/** \"2026-09\" → \"sep 2026\" (kort, voor as-labels en de maandkiezer). */\nexport function maandLabel(id: string, lang = false): string {\n const [j, m] = id.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 1, 1));\n return d.toLocaleDateString(\"nl-NL\", { month: lang ? \"long\" : \"short\", year: \"numeric\", timeZone: \"UTC\" });\n}\n\n/** Het maand-id van nu (UTC — trackView telt in UTC). */\nexport function huidigMaandId(): string {\n const nu = new Date();\n return `${nu.getUTCFullYear()}-${String(nu.getUTCMonth() + 1).padStart(2, \"0\")}`;\n}\n\n/** Aantal dagen in een \"YYYY-MM\"-maand. */\nexport function dagenInMaand(id: string): number {\n const [j, m] = id.split(\"-\").map(Number);\n return new Date(Date.UTC(j, m ?? 1, 0)).getUTCDate();\n}\n\n/** days-record → array per dag (index 0 = dag 1), met nullen voor ontbrekende dagen. */\nexport function dagReeks(stat: MaandStat | undefined, nDagen: number): number[] {\n return Array.from({ length: nDagen }, (_, i) => stat?.days?.[String(i + 1).padStart(2, \"0\")] ?? 0);\n}\n\n/** Top-N paden van een maand, genormaliseerd en gesorteerd. */\nexport function topPaden(stat: MaandStat | undefined, n: number): { label: string; waarde: number }[] {\n return Object.entries(stat?.paths ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([p, w]) => ({ label: padLabel(p), waarde: w }));\n}\n\n/**\n * Eerlijke maand-op-maand-delta: vergelijk t/m dezelfde dag. Halverwege de maand het hele\n * vorige maandtotaal ernaast leggen leest als \"−50%\" terwijl het gewoon de 15e is.\n */\nexport function deltaTovVorige(huidig: MaandStat | undefined, vorige: MaandStat | undefined, totDag: number): number | null {\n if (!huidig || !vorige) return null;\n const som = (s: MaandStat) =>\n Object.entries(s.days ?? {}).reduce((acc, [d, n]) => (Number(d) <= totDag ? acc + n : acc), 0);\n const nu = som(huidig);\n const toen = som(vorige);\n if (toen === 0) return null;\n return ((nu - toen) / toen) * 100;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/stats/lib.ts"],"sourcesContent":["/**\n * Gedeelde stats-helpers — gebruikt door het Statistieken-paneel én de Start-widget, zodat\n * paden en maanden op beide plekken hetzelfde heten.\n */\n\nexport interface MaandStat {\n id: string; // \"YYYY-MM\"\n total?: number;\n days?: Record<string, number>;\n paths?: Record<string, number>;\n /** 7.24: verwijzers (host of gebundelde naam zoals \"google\"), apparaten en gebeurtenissen. */\n refs?: Record<string, number>;\n devices?: Record<string, number>;\n events?: Record<string, number>;\n /** 8.4: niet-gevonden paden (404-monitor), sleutel zoals `paths`. */\n notFound?: Record<string, number>;\n}\n\n/** trackView slaat paden veilig op: \"home\" voor \"/\", percent-encoded tekens. Terug naar leesbaar. */\nexport function padLabel(sleutel: string): string {\n if (sleutel === \"home\") return \"/\";\n let s = sleutel;\n try {\n s = decodeURIComponent(sleutel.replace(/%(?![0-9a-fA-F]{2})/g, \"%25\"));\n } catch {\n /* oude, niet-gecodeerde sleutel: laat staan */\n }\n return `/${s.replace(/_/g, \"/\")}`;\n}\n\n/** Verwijzer-sleutel → leesbaar (\"google\" → \"Google\", \"nieuws%2enl\" → \"nieuws.nl\"). */\nexport function verwijzerLabel(sleutel: string): string {\n const vast: Record<string, string> = {\n direct: \"Direct / onbekend\",\n google: \"Google\",\n bing: \"Bing\",\n duckduckgo: \"DuckDuckGo\",\n facebook: \"Facebook\",\n instagram: \"Instagram\",\n linkedin: \"LinkedIn\",\n x: \"X (Twitter)\",\n };\n if (vast[sleutel]) return vast[sleutel];\n try {\n return decodeURIComponent(sleutel);\n } catch {\n return sleutel;\n }\n}\n\nexport function apparaatLabel(sleutel: string): string {\n return ({ mobiel: \"Mobiel\", tablet: \"Tablet\", desktop: \"Desktop\" } as Record<string, string>)[sleutel] ?? sleutel;\n}\n\nexport function gebeurtenisLabel(sleutel: string): string {\n return (\n ({\n formulier: \"Formulier verstuurd\",\n bestelling: \"Bestelling betaald\",\n uitgaand: \"Uitgaande link\",\n telefoon: \"Telefoonnummer geklikt\",\n mail: \"E-mailadres geklikt\",\n whatsapp: \"WhatsApp geopend\",\n }) as Record<string, string>\n )[sleutel] ?? sleutel;\n}\n\n/** Top-N van een teller-record, gelabeld en gesorteerd. */\nexport function topVan(record: Record<string, number> | undefined, n: number, label: (k: string) => string): { label: string; waarde: number }[] {\n return Object.entries(record ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([k, w]) => ({ label: label(k), waarde: w }));\n}\n\n/** \"2026-09\" → \"sep 2026\" (kort, voor as-labels en de maandkiezer). */\nexport function maandLabel(id: string, lang = false): string {\n const [j, m] = id.split(\"-\").map(Number);\n const d = new Date(Date.UTC(j, (m ?? 1) - 1, 1));\n return d.toLocaleDateString(\"nl-NL\", { month: lang ? \"long\" : \"short\", year: \"numeric\", timeZone: \"UTC\" });\n}\n\n/** Het maand-id van nu (UTC — trackView telt in UTC). */\nexport function huidigMaandId(): string {\n const nu = new Date();\n return `${nu.getUTCFullYear()}-${String(nu.getUTCMonth() + 1).padStart(2, \"0\")}`;\n}\n\n/** Aantal dagen in een \"YYYY-MM\"-maand. */\nexport function dagenInMaand(id: string): number {\n const [j, m] = id.split(\"-\").map(Number);\n return new Date(Date.UTC(j, m ?? 1, 0)).getUTCDate();\n}\n\n/** days-record → array per dag (index 0 = dag 1), met nullen voor ontbrekende dagen. */\nexport function dagReeks(stat: MaandStat | undefined, nDagen: number): number[] {\n return Array.from({ length: nDagen }, (_, i) => stat?.days?.[String(i + 1).padStart(2, \"0\")] ?? 0);\n}\n\n/** Top-N paden van een maand, genormaliseerd en gesorteerd. */\nexport function topPaden(stat: MaandStat | undefined, n: number): { label: string; waarde: number }[] {\n return Object.entries(stat?.paths ?? {})\n .sort((a, b) => b[1] - a[1])\n .slice(0, n)\n .map(([p, w]) => ({ label: padLabel(p), waarde: w }));\n}\n\n/**\n * Eerlijke maand-op-maand-delta: vergelijk t/m dezelfde dag. Halverwege de maand het hele\n * vorige maandtotaal ernaast leggen leest als \"−50%\" terwijl het gewoon de 15e is.\n */\nexport function deltaTovVorige(huidig: MaandStat | undefined, vorige: MaandStat | undefined, totDag: number): number | null {\n if (!huidig || !vorige) return null;\n const som = (s: MaandStat) =>\n Object.entries(s.days ?? {}).reduce((acc, [d, n]) => (Number(d) <= totDag ? acc + n : acc), 0);\n const nu = som(huidig);\n const toen = som(vorige);\n if (toen === 0) return null;\n return ((nu - toen) / toen) * 100;\n}\n"],"mappings":"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","names":[]}
@@ -2,6 +2,8 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useRef, useState } from "react";
4
4
  import { AlertDialog } from "@astryxdesign/core/AlertDialog";
5
+ import { Dialog, DialogContent, DialogFooter } from "./dialog.js";
6
+ import { Button } from "./primitives.js";
5
7
  const ConfirmContext = createContext(null);
6
8
  function ConfirmHost({ children }) {
7
9
  const [open, setOpen] = useState(false);
@@ -21,7 +23,14 @@ function ConfirmHost({ children }) {
21
23
  }, []);
22
24
  return /* @__PURE__ */ jsxs(ConfirmContext.Provider, { value: confirm, children: [
23
25
  children,
24
- opts && /* @__PURE__ */ jsx(
26
+ opts && typeof opts.description !== "string" && opts.description != null && /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (o) => !o && close(false), children: /* @__PURE__ */ jsxs(DialogContent, { title: opts.title, width: "min(92vw, 34rem)", children: [
27
+ /* @__PURE__ */ jsx("div", { role: "alert", className: "max-h-[60vh] overflow-y-auto text-sm text-slate-700", children: opts.description }),
28
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
29
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => close(false), children: opts.cancelLabel ?? "Annuleren" }),
30
+ /* @__PURE__ */ jsx(Button, { variant: opts.tone === "danger" ? "danger" : "primary", onClick: () => close(true), children: opts.confirmLabel ?? "Bevestigen" })
31
+ ] })
32
+ ] }) }),
33
+ opts && (typeof opts.description === "string" || opts.description == null) && /* @__PURE__ */ jsx(
25
34
  AlertDialog,
26
35
  {
27
36
  isOpen: open,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/ui/ConfirmDialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useRef, useState, type ReactNode } from \"react\";\nimport { AlertDialog } from \"@astryxdesign/core/AlertDialog\";\n\n/**\n * Eén bevestigingsdialoog voor de hele admin, op de **Astryx AlertDialog** (role=\"alertdialog\",\n * niet weg te klikken). `useConfirm()` geeft een functie die een Promise<bool> teruggeeft — zo\n * vervang je een onveilige directe verwijdering door een nette \"weet je het zeker?\". Monteer\n * `<ConfirmHost/>` één keer in `AdminApp`. De API blijft gelijk (geen call-site-wijziging).\n */\nexport interface ConfirmOptions {\n title: string;\n description?: ReactNode;\n confirmLabel?: string;\n cancelLabel?: string;\n /** \"danger\" = destructieve (rode) bevestigingsknop (verwijderen e.d.). */\n tone?: \"default\" | \"danger\";\n}\n\ntype ConfirmFn = (opts: ConfirmOptions) => Promise<boolean>;\n\nconst ConfirmContext = createContext<ConfirmFn | null>(null);\n\nexport function ConfirmHost({ children }: { children: ReactNode }) {\n const [open, setOpen] = useState(false);\n const [opts, setOpts] = useState<ConfirmOptions | null>(null);\n const resolver = useRef<((v: boolean) => void) | null>(null);\n\n const confirm = useCallback<ConfirmFn>((o) => {\n setOpts(o);\n setOpen(true);\n return new Promise<boolean>((resolve) => {\n resolver.current = resolve;\n });\n }, []);\n\n const close = useCallback((value: boolean) => {\n setOpen(false);\n resolver.current?.(value);\n resolver.current = null;\n }, []);\n\n return (\n <ConfirmContext.Provider value={confirm}>\n {children}\n {opts && (\n <AlertDialog\n isOpen={open}\n onOpenChange={(o) => !o && close(false)}\n title={opts.title}\n description={typeof opts.description === \"string\" ? opts.description : \" \"}\n cancelLabel={opts.cancelLabel ?? \"Annuleren\"}\n actionLabel={opts.confirmLabel ?? \"Bevestigen\"}\n actionVariant={opts.tone === \"danger\" ? \"destructive\" : \"primary\"}\n onAction={() => close(true)}\n />\n )}\n </ConfirmContext.Provider>\n );\n}\n\n/** Geeft een `confirm(opts) => Promise<boolean>`. Buiten een `ConfirmHost` valt het terug op `false`. */\nexport function useConfirm(): ConfirmFn {\n const ctx = useContext(ConfirmContext);\n return ctx ?? (async () => false);\n}\n"],"mappings":";AA4CI,SAGI,KAHJ;AA1CJ,SAAS,eAAe,aAAa,YAAY,QAAQ,gBAAgC;AACzF,SAAS,mBAAmB;AAmB5B,MAAM,iBAAiB,cAAgC,IAAI;AAEpD,SAAS,YAAY,EAAE,SAAS,GAA4B;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAgC,IAAI;AAC5D,QAAM,WAAW,OAAsC,IAAI;AAE3D,QAAM,UAAU,YAAuB,CAAC,MAAM;AAC5C,YAAQ,CAAC;AACT,YAAQ,IAAI;AACZ,WAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,eAAS,UAAU;AAAA,IACrB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,YAAY,CAAC,UAAmB;AAC5C,YAAQ,KAAK;AACb,aAAS,UAAU,KAAK;AACxB,aAAS,UAAU;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,SACE,qBAAC,eAAe,UAAf,EAAwB,OAAO,SAC7B;AAAA;AAAA,IACA,QACC;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ;AAAA,QACR,cAAc,CAAC,MAAM,CAAC,KAAK,MAAM,KAAK;AAAA,QACtC,OAAO,KAAK;AAAA,QACZ,aAAa,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc;AAAA,QACvE,aAAa,KAAK,eAAe;AAAA,QACjC,aAAa,KAAK,gBAAgB;AAAA,QAClC,eAAe,KAAK,SAAS,WAAW,gBAAgB;AAAA,QACxD,UAAU,MAAM,MAAM,IAAI;AAAA;AAAA,IAC5B;AAAA,KAEJ;AAEJ;AAGO,SAAS,aAAwB;AACtC,QAAM,MAAM,WAAW,cAAc;AACrC,SAAO,QAAQ,YAAY;AAC7B;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/ui/ConfirmDialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useRef, useState, type ReactNode } from \"react\";\nimport { AlertDialog } from \"@astryxdesign/core/AlertDialog\";\nimport { Dialog, DialogContent, DialogFooter } from \"./dialog\";\nimport { Button } from \"./primitives\";\n\n/**\n * Eén bevestigingsdialoog voor de hele admin, op de **Astryx AlertDialog** (role=\"alertdialog\",\n * niet weg te klikken). `useConfirm()` geeft een functie die een Promise<bool> teruggeeft — zo\n * vervang je een onveilige directe verwijdering door een nette \"weet je het zeker?\". Monteer\n * `<ConfirmHost/>` één keer in `AdminApp`. De API blijft gelijk (geen call-site-wijziging).\n */\nexport interface ConfirmOptions {\n title: string;\n description?: ReactNode;\n confirmLabel?: string;\n cancelLabel?: string;\n /** \"danger\" = destructieve (rode) bevestigingsknop (verwijderen e.d.). */\n tone?: \"default\" | \"danger\";\n}\n\ntype ConfirmFn = (opts: ConfirmOptions) => Promise<boolean>;\n\nconst ConfirmContext = createContext<ConfirmFn | null>(null);\n\nexport function ConfirmHost({ children }: { children: ReactNode }) {\n const [open, setOpen] = useState(false);\n const [opts, setOpts] = useState<ConfirmOptions | null>(null);\n const resolver = useRef<((v: boolean) => void) | null>(null);\n\n const confirm = useCallback<ConfirmFn>((o) => {\n setOpts(o);\n setOpen(true);\n return new Promise<boolean>((resolve) => {\n resolver.current = resolve;\n });\n }, []);\n\n const close = useCallback((value: boolean) => {\n setOpen(false);\n resolver.current?.(value);\n resolver.current = null;\n }, []);\n\n return (\n <ConfirmContext.Provider value={confirm}>\n {children}\n {/* Een beschrijving met opmaak (lijstje wijzigingen, waarschuwingen vóór publiceren): de\n Astryx-AlertDialog neemt alleen een string — tot 8.10 werd zo'n beschrijving daarom als\n een spatie getoond, en zag niemand de waarschuwingen. Dan een gewone dialoog met dezelfde\n twee knoppen. */}\n {opts && typeof opts.description !== \"string\" && opts.description != null && (\n <Dialog open={open} onOpenChange={(o) => !o && close(false)}>\n <DialogContent title={opts.title} width=\"min(92vw, 34rem)\">\n <div role=\"alert\" className=\"max-h-[60vh] overflow-y-auto text-sm text-slate-700\">{opts.description}</div>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={() => close(false)}>{opts.cancelLabel ?? \"Annuleren\"}</Button>\n <Button variant={opts.tone === \"danger\" ? \"danger\" : \"primary\"} onClick={() => close(true)}>\n {opts.confirmLabel ?? \"Bevestigen\"}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )}\n {opts && (typeof opts.description === \"string\" || opts.description == null) && (\n <AlertDialog\n isOpen={open}\n onOpenChange={(o) => !o && close(false)}\n title={opts.title}\n description={typeof opts.description === \"string\" ? opts.description : \" \"}\n cancelLabel={opts.cancelLabel ?? \"Annuleren\"}\n actionLabel={opts.confirmLabel ?? \"Bevestigen\"}\n actionVariant={opts.tone === \"danger\" ? \"destructive\" : \"primary\"}\n onAction={() => close(true)}\n />\n )}\n </ConfirmContext.Provider>\n );\n}\n\n/** Geeft een `confirm(opts) => Promise<boolean>`. Buiten een `ConfirmHost` valt het terug op `false`. */\nexport function useConfirm(): ConfirmFn {\n const ctx = useContext(ConfirmContext);\n return ctx ?? (async () => false);\n}\n"],"mappings":";AAuDY,cACA,YADA;AArDZ,SAAS,eAAe,aAAa,YAAY,QAAQ,gBAAgC;AACzF,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,eAAe,oBAAoB;AACpD,SAAS,cAAc;AAmBvB,MAAM,iBAAiB,cAAgC,IAAI;AAEpD,SAAS,YAAY,EAAE,SAAS,GAA4B;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAgC,IAAI;AAC5D,QAAM,WAAW,OAAsC,IAAI;AAE3D,QAAM,UAAU,YAAuB,CAAC,MAAM;AAC5C,YAAQ,CAAC;AACT,YAAQ,IAAI;AACZ,WAAO,IAAI,QAAiB,CAAC,YAAY;AACvC,eAAS,UAAU;AAAA,IACrB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,YAAY,CAAC,UAAmB;AAC5C,YAAQ,KAAK;AACb,aAAS,UAAU,KAAK;AACxB,aAAS,UAAU;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,SACE,qBAAC,eAAe,UAAf,EAAwB,OAAO,SAC7B;AAAA;AAAA,IAKA,QAAQ,OAAO,KAAK,gBAAgB,YAAY,KAAK,eAAe,QACnE,oBAAC,UAAO,MAAY,cAAc,CAAC,MAAM,CAAC,KAAK,MAAM,KAAK,GACxD,+BAAC,iBAAc,OAAO,KAAK,OAAO,OAAM,oBACtC;AAAA,0BAAC,SAAI,MAAK,SAAQ,WAAU,uDAAuD,eAAK,aAAY;AAAA,MACpG,qBAAC,gBACC;AAAA,4BAAC,UAAO,SAAQ,SAAQ,SAAS,MAAM,MAAM,KAAK,GAAI,eAAK,eAAe,aAAY;AAAA,QACtF,oBAAC,UAAO,SAAS,KAAK,SAAS,WAAW,WAAW,WAAW,SAAS,MAAM,MAAM,IAAI,GACtF,eAAK,gBAAgB,cACxB;AAAA,SACF;AAAA,OACF,GACF;AAAA,IAED,SAAS,OAAO,KAAK,gBAAgB,YAAY,KAAK,eAAe,SACpE;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ;AAAA,QACR,cAAc,CAAC,MAAM,CAAC,KAAK,MAAM,KAAK;AAAA,QACtC,OAAO,KAAK;AAAA,QACZ,aAAa,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc;AAAA,QACvE,aAAa,KAAK,eAAe;AAAA,QACjC,aAAa,KAAK,gBAAgB;AAAA,QAClC,eAAe,KAAK,SAAS,WAAW,gBAAgB;AAAA,QACxD,UAAU,MAAM,MAAM,IAAI;AAAA;AAAA,IAC5B;AAAA,KAEJ;AAEJ;AAGO,SAAS,aAAwB;AACtC,QAAM,MAAM,WAAW,cAAc;AACrC,SAAO,QAAQ,YAAY;AAC7B;","names":[]}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Eén lijn-icoonset voor beheer en editor (8.12).
3
+ *
4
+ * Er liepen drie idiomen door elkaar: SVG in de zijbalk, Unicode-tekens (⚙ ↶ ↷ ↗) in de balk en
5
+ * emoji (🔍 🌐 🔗) op knoppen. Emoji zien er per besturingssysteem anders uit, kleuren niet mee met
6
+ * de tekst en ogen op 14px als een vlek. Zelfde stijl als `PanelIcon` (1.6 lijn, currentColor),
7
+ * zonder icoonpakket: het zijn een paar paden.
8
+ */
9
+ declare const PADEN: {
10
+ readonly zoek: "M11 18a7 7 0 100-14 7 7 0 000 14z M20 20l-4-4";
11
+ readonly extern: "M14 4h6v6 M20 4l-9 9 M18 14v5a1 1 0 01-1 1H5a1 1 0 01-1-1V7a1 1 0 011-1h5";
12
+ readonly ongedaan: "M9 14L4 9l5-5 M4 9h11a5 5 0 010 10h-3";
13
+ readonly opnieuw: "M15 14l5-5-5-5 M20 9H9a5 5 0 000 10h3";
14
+ readonly tandwiel: "M12 15a3 3 0 100-6 3 3 0 000 6z M19.4 15a1.6 1.6 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.6 1.6 0 00-2.7 1.1V21a2 2 0 11-4 0v-.1A1.6 1.6 0 007.5 19.4l-.1.1a2 2 0 11-2.8-2.8l.1-.1A1.6 1.6 0 003 14.6H3a2 2 0 110-4h.1A1.6 1.6 0 004.6 7.5l-.1-.1a2 2 0 112.8-2.8l.1.1a1.6 1.6 0 001.8.3H9.4a1.6 1.6 0 001-1.5V3a2 2 0 114 0v.1a1.6 1.6 0 001 1.5 1.6 1.6 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.6 1.6 0 00-.3 1.8v.1a1.6 1.6 0 001.5 1H21a2 2 0 110 4h-.1a1.6 1.6 0 00-1.5 1z";
15
+ readonly potlood: "M4 20h4L19.5 8.5a2.1 2.1 0 00-3-3L5 17v3z M14.5 6.5l3 3";
16
+ readonly plus: "M12 5v14 M5 12h14";
17
+ readonly prullenbak: "M3 6h18 M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2 M6 6l1 14a2 2 0 002 2h6a2 2 0 002-2l1-14";
18
+ readonly link: "M10 14a4 4 0 005.7 0l3-3a4 4 0 00-5.7-5.7l-1 1 M14 10a4 4 0 00-5.7 0l-3 3a4 4 0 005.7 5.7l1-1";
19
+ readonly foto: "M3 5h18v14H3z M3 15l5-5 4 4 3-3 6 6 M15.5 9.5a1 1 0 100-2 1 1 0 000 2z";
20
+ readonly wereld: "M12 21a9 9 0 100-18 9 9 0 000 18z M3 12h18 M12 3a14 14 0 010 18 M12 3a14 14 0 000 18";
21
+ readonly vink: "M20 6L9 17l-5-5";
22
+ readonly kruis: "M6 6l12 12 M18 6L6 18";
23
+ readonly info: "M12 21a9 9 0 100-18 9 9 0 000 18z M12 11v5 M12 8h.01";
24
+ readonly let: "M12 3l10 18H2z M12 10v4 M12 17h.01";
25
+ readonly leeg: "M4 7h16v12H4z M4 7l2-3h12l2 3 M9 12h6";
26
+ };
27
+ export type IconNaam = keyof typeof PADEN;
28
+ export declare function Icon({ naam, grootte, className, label }: {
29
+ naam: IconNaam;
30
+ grootte?: number;
31
+ className?: string;
32
+ label?: string;
33
+ }): import("react").JSX.Element;
34
+ export {};
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ const PADEN = {
4
+ zoek: "M11 18a7 7 0 100-14 7 7 0 000 14z M20 20l-4-4",
5
+ extern: "M14 4h6v6 M20 4l-9 9 M18 14v5a1 1 0 01-1 1H5a1 1 0 01-1-1V7a1 1 0 011-1h5",
6
+ ongedaan: "M9 14L4 9l5-5 M4 9h11a5 5 0 010 10h-3",
7
+ opnieuw: "M15 14l5-5-5-5 M20 9H9a5 5 0 000 10h3",
8
+ tandwiel: "M12 15a3 3 0 100-6 3 3 0 000 6z M19.4 15a1.6 1.6 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.6 1.6 0 00-2.7 1.1V21a2 2 0 11-4 0v-.1A1.6 1.6 0 007.5 19.4l-.1.1a2 2 0 11-2.8-2.8l.1-.1A1.6 1.6 0 003 14.6H3a2 2 0 110-4h.1A1.6 1.6 0 004.6 7.5l-.1-.1a2 2 0 112.8-2.8l.1.1a1.6 1.6 0 001.8.3H9.4a1.6 1.6 0 001-1.5V3a2 2 0 114 0v.1a1.6 1.6 0 001 1.5 1.6 1.6 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.6 1.6 0 00-.3 1.8v.1a1.6 1.6 0 001.5 1H21a2 2 0 110 4h-.1a1.6 1.6 0 00-1.5 1z",
9
+ potlood: "M4 20h4L19.5 8.5a2.1 2.1 0 00-3-3L5 17v3z M14.5 6.5l3 3",
10
+ plus: "M12 5v14 M5 12h14",
11
+ prullenbak: "M3 6h18 M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2 M6 6l1 14a2 2 0 002 2h6a2 2 0 002-2l1-14",
12
+ link: "M10 14a4 4 0 005.7 0l3-3a4 4 0 00-5.7-5.7l-1 1 M14 10a4 4 0 00-5.7 0l-3 3a4 4 0 005.7 5.7l1-1",
13
+ foto: "M3 5h18v14H3z M3 15l5-5 4 4 3-3 6 6 M15.5 9.5a1 1 0 100-2 1 1 0 000 2z",
14
+ wereld: "M12 21a9 9 0 100-18 9 9 0 000 18z M3 12h18 M12 3a14 14 0 010 18 M12 3a14 14 0 000 18",
15
+ vink: "M20 6L9 17l-5-5",
16
+ kruis: "M6 6l12 12 M18 6L6 18",
17
+ info: "M12 21a9 9 0 100-18 9 9 0 000 18z M12 11v5 M12 8h.01",
18
+ let: "M12 3l10 18H2z M12 10v4 M12 17h.01",
19
+ leeg: "M4 7h16v12H4z M4 7l2-3h12l2 3 M9 12h6"
20
+ };
21
+ function Icon({ naam, grootte = 16, className, label }) {
22
+ return /* @__PURE__ */ jsx(
23
+ "svg",
24
+ {
25
+ "aria-hidden": label ? void 0 : true,
26
+ "aria-label": label,
27
+ role: label ? "img" : void 0,
28
+ viewBox: "0 0 24 24",
29
+ width: grootte,
30
+ height: grootte,
31
+ fill: "none",
32
+ stroke: "currentColor",
33
+ strokeWidth: "1.6",
34
+ strokeLinecap: "round",
35
+ strokeLinejoin: "round",
36
+ className: ["shrink-0", className].filter(Boolean).join(" "),
37
+ children: /* @__PURE__ */ jsx("path", { d: PADEN[naam] })
38
+ }
39
+ );
40
+ }
41
+ export {
42
+ Icon
43
+ };
44
+ //# sourceMappingURL=Icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/ui/Icon.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Eén lijn-icoonset voor beheer en editor (8.12).\n *\n * Er liepen drie idiomen door elkaar: SVG in de zijbalk, Unicode-tekens (⚙ ↶ ↷ ↗) in de balk en\n * emoji (🔍 🌐 🔗) op knoppen. Emoji zien er per besturingssysteem anders uit, kleuren niet mee met\n * de tekst en ogen op 14px als een vlek. Zelfde stijl als `PanelIcon` (1.6 lijn, currentColor),\n * zonder icoonpakket: het zijn een paar paden.\n */\nconst PADEN = {\n zoek: \"M11 18a7 7 0 100-14 7 7 0 000 14z M20 20l-4-4\",\n extern: \"M14 4h6v6 M20 4l-9 9 M18 14v5a1 1 0 01-1 1H5a1 1 0 01-1-1V7a1 1 0 011-1h5\",\n ongedaan: \"M9 14L4 9l5-5 M4 9h11a5 5 0 010 10h-3\",\n opnieuw: \"M15 14l5-5-5-5 M20 9H9a5 5 0 000 10h3\",\n tandwiel: \"M12 15a3 3 0 100-6 3 3 0 000 6z M19.4 15a1.6 1.6 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.6 1.6 0 00-2.7 1.1V21a2 2 0 11-4 0v-.1A1.6 1.6 0 007.5 19.4l-.1.1a2 2 0 11-2.8-2.8l.1-.1A1.6 1.6 0 003 14.6H3a2 2 0 110-4h.1A1.6 1.6 0 004.6 7.5l-.1-.1a2 2 0 112.8-2.8l.1.1a1.6 1.6 0 001.8.3H9.4a1.6 1.6 0 001-1.5V3a2 2 0 114 0v.1a1.6 1.6 0 001 1.5 1.6 1.6 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.6 1.6 0 00-.3 1.8v.1a1.6 1.6 0 001.5 1H21a2 2 0 110 4h-.1a1.6 1.6 0 00-1.5 1z\",\n potlood: \"M4 20h4L19.5 8.5a2.1 2.1 0 00-3-3L5 17v3z M14.5 6.5l3 3\",\n plus: \"M12 5v14 M5 12h14\",\n prullenbak: \"M3 6h18 M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2 M6 6l1 14a2 2 0 002 2h6a2 2 0 002-2l1-14\",\n link: \"M10 14a4 4 0 005.7 0l3-3a4 4 0 00-5.7-5.7l-1 1 M14 10a4 4 0 00-5.7 0l-3 3a4 4 0 005.7 5.7l1-1\",\n foto: \"M3 5h18v14H3z M3 15l5-5 4 4 3-3 6 6 M15.5 9.5a1 1 0 100-2 1 1 0 000 2z\",\n wereld: \"M12 21a9 9 0 100-18 9 9 0 000 18z M3 12h18 M12 3a14 14 0 010 18 M12 3a14 14 0 000 18\",\n vink: \"M20 6L9 17l-5-5\",\n kruis: \"M6 6l12 12 M18 6L6 18\",\n info: \"M12 21a9 9 0 100-18 9 9 0 000 18z M12 11v5 M12 8h.01\",\n let: \"M12 3l10 18H2z M12 10v4 M12 17h.01\",\n leeg: \"M4 7h16v12H4z M4 7l2-3h12l2 3 M9 12h6\",\n} as const;\n\nexport type IconNaam = keyof typeof PADEN;\n\nexport function Icon({ naam, grootte = 16, className, label }: { naam: IconNaam; grootte?: number; className?: string; label?: string }) {\n return (\n <svg\n aria-hidden={label ? undefined : true}\n aria-label={label}\n role={label ? \"img\" : undefined}\n viewBox=\"0 0 24 24\"\n width={grootte}\n height={grootte}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={[\"shrink-0\", className].filter(Boolean).join(\" \")}\n >\n <path d={PADEN[naam]} />\n </svg>\n );\n}\n"],"mappings":";AA+CM;AArCN,MAAM,QAAQ;AAAA,EACZ,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,MAAM;AACR;AAIO,SAAS,KAAK,EAAE,MAAM,UAAU,IAAI,WAAW,MAAM,GAA6E;AACvI,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAa,QAAQ,SAAY;AAAA,MACjC,cAAY;AAAA,MACZ,MAAM,QAAQ,QAAQ;AAAA,MACtB,SAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAY;AAAA,MACZ,eAAc;AAAA,MACd,gBAAe;AAAA,MACf,WAAW,CAAC,YAAY,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,MAE3D,8BAAC,UAAK,GAAG,MAAM,IAAI,GAAG;AAAA;AAAA,EACxB;AAEJ;","names":[]}
@@ -0,0 +1,15 @@
1
+ import { type PickedMedia } from "../media/MediaPicker.js";
2
+ /**
3
+ * Fotoveld (8.2): wat de klant ziet is de FOTO, niet een URL. Eerst was elk beeldveld een
4
+ * tekstvak met "URL of kies →" en een knop ernaast — de meest gebruikte handeling ("foto
5
+ * wijzigen") begon met een onleesbaar adres. Nu: een miniatuur, "Wijzigen" via de mediabibliotheek
6
+ * en "Weghalen". Een adres plakken kan nog onder "Ander adres" (voor een externe afbeelding).
7
+ */
8
+ export declare function ImageField({ siteId, value, onChange, wat, verwijderbaar, }: {
9
+ siteId: string;
10
+ value: string | undefined;
11
+ onChange: (url: string | undefined, media?: PickedMedia) => void;
12
+ /** Hoe het beeld heet in knoppen ("foto", "logo", "achtergrond"). */
13
+ wat?: string;
14
+ verwijderbaar?: boolean;
15
+ }): import("react").JSX.Element;