@xiwebdesign/xicms-core 5.3.0 → 5.5.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 (135) hide show
  1. package/dist/XiCMSProvider.d.ts +11 -2
  2. package/dist/XiCMSProvider.js +10 -5
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/blockEditors/GenericEditor.js +18 -8
  5. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  6. package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
  7. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  8. package/dist/admin/lib/pagesClient.js +4 -1
  9. package/dist/admin/lib/pagesClient.js.map +1 -1
  10. package/dist/admin/lib/upload.js +14 -7
  11. package/dist/admin/lib/upload.js.map +1 -1
  12. package/dist/admin/media/MediaManager.js +60 -28
  13. package/dist/admin/media/MediaManager.js.map +1 -1
  14. package/dist/admin/media/MediaPicker.js +25 -15
  15. package/dist/admin/media/MediaPicker.js.map +1 -1
  16. package/dist/admin/pages/PageEditor.d.ts +2 -1
  17. package/dist/admin/pages/PageEditor.js +16 -2
  18. package/dist/admin/pages/PageEditor.js.map +1 -1
  19. package/dist/admin/pages/PagesPanel.js +9 -5
  20. package/dist/admin/pages/PagesPanel.js.map +1 -1
  21. package/dist/admin/panelRegistry.js +3 -0
  22. package/dist/admin/panelRegistry.js.map +1 -1
  23. package/dist/admin/shop/CategoriesTab.d.ts +3 -0
  24. package/dist/admin/shop/CategoriesTab.js +140 -0
  25. package/dist/admin/shop/CategoriesTab.js.map +1 -0
  26. package/dist/admin/shop/ConnectionsTab.d.ts +9 -0
  27. package/dist/admin/shop/ConnectionsTab.js +158 -0
  28. package/dist/admin/shop/ConnectionsTab.js.map +1 -0
  29. package/dist/admin/shop/OrdersTab.d.ts +3 -0
  30. package/dist/admin/shop/OrdersTab.js +236 -0
  31. package/dist/admin/shop/OrdersTab.js.map +1 -0
  32. package/dist/admin/shop/ProductsTab.d.ts +3 -0
  33. package/dist/admin/shop/ProductsTab.js +397 -0
  34. package/dist/admin/shop/ProductsTab.js.map +1 -0
  35. package/dist/admin/shop/ShopPanel.d.ts +4 -0
  36. package/dist/admin/shop/ShopPanel.js +25 -0
  37. package/dist/admin/shop/ShopPanel.js.map +1 -0
  38. package/dist/admin/shop/lib.d.ts +16 -0
  39. package/dist/admin/shop/lib.js +46 -0
  40. package/dist/admin/shop/lib.js.map +1 -0
  41. package/dist/admin/visual/VisualEditor.js +11 -2
  42. package/dist/admin/visual/VisualEditor.js.map +1 -1
  43. package/dist/admin.css +1 -1
  44. package/dist/agentFiles/files.d.ts +15 -3
  45. package/dist/agentFiles/files.js +57 -0
  46. package/dist/agentFiles/files.js.map +1 -1
  47. package/dist/auth/AuthProvider.js +3 -3
  48. package/dist/auth/AuthProvider.js.map +1 -1
  49. package/dist/blocks/builtins/Columns.js +1 -1
  50. package/dist/blocks/builtins/Columns.js.map +1 -1
  51. package/dist/blocks/builtins/Gallery.js +2 -2
  52. package/dist/blocks/builtins/Gallery.js.map +1 -1
  53. package/dist/blocks/builtins/Hero.js +1 -1
  54. package/dist/blocks/builtins/Hero.js.map +1 -1
  55. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  56. package/dist/components/PageView.js +4 -3
  57. package/dist/components/PageView.js.map +1 -1
  58. package/dist/demo/index.d.ts +7 -0
  59. package/dist/demo/index.js +23 -0
  60. package/dist/demo/index.js.map +1 -0
  61. package/dist/index.d.ts +2 -4
  62. package/dist/index.js +0 -20
  63. package/dist/index.js.map +1 -1
  64. package/dist/modules/builtins.d.ts +1 -0
  65. package/dist/modules/builtins.js +4 -0
  66. package/dist/modules/builtins.js.map +1 -1
  67. package/dist/modules/index.d.ts +4 -0
  68. package/dist/modules/index.js +12 -0
  69. package/dist/modules/index.js.map +1 -1
  70. package/dist/modules/registry.d.ts +3 -4
  71. package/dist/modules/registry.js +2 -3
  72. package/dist/modules/registry.js.map +1 -1
  73. package/dist/modules/shop/blocks/Cart.d.ts +2 -0
  74. package/dist/modules/shop/blocks/Cart.js +91 -0
  75. package/dist/modules/shop/blocks/Cart.js.map +1 -0
  76. package/dist/modules/shop/blocks/Checkout.d.ts +2 -0
  77. package/dist/modules/shop/blocks/Checkout.js +126 -0
  78. package/dist/modules/shop/blocks/Checkout.js.map +1 -0
  79. package/dist/modules/shop/blocks/Donation.d.ts +2 -0
  80. package/dist/modules/shop/blocks/Donation.js +145 -0
  81. package/dist/modules/shop/blocks/Donation.js.map +1 -0
  82. package/dist/modules/shop/blocks/OrderStatus.d.ts +2 -0
  83. package/dist/modules/shop/blocks/OrderStatus.js +40 -0
  84. package/dist/modules/shop/blocks/OrderStatus.js.map +1 -0
  85. package/dist/modules/shop/blocks/ProductDetail.d.ts +2 -0
  86. package/dist/modules/shop/blocks/ProductDetail.js +168 -0
  87. package/dist/modules/shop/blocks/ProductDetail.js.map +1 -0
  88. package/dist/modules/shop/blocks/ProductGrid.d.ts +8 -0
  89. package/dist/modules/shop/blocks/ProductGrid.js +136 -0
  90. package/dist/modules/shop/blocks/ProductGrid.js.map +1 -0
  91. package/dist/modules/shop/blocks/ShopCategories.d.ts +2 -0
  92. package/dist/modules/shop/blocks/ShopCategories.js +69 -0
  93. package/dist/modules/shop/blocks/ShopCategories.js.map +1 -0
  94. package/dist/modules/shop/cartStore.d.ts +22 -0
  95. package/dist/modules/shop/cartStore.js +85 -0
  96. package/dist/modules/shop/cartStore.js.map +1 -0
  97. package/dist/modules/shop/lib.d.ts +25 -0
  98. package/dist/modules/shop/lib.js +61 -0
  99. package/dist/modules/shop/lib.js.map +1 -0
  100. package/dist/modules/shop/module.d.ts +1 -0
  101. package/dist/modules/shop/module.js +39 -0
  102. package/dist/modules/shop/module.js.map +1 -0
  103. package/dist/modules/shop/settings.d.ts +41 -0
  104. package/dist/modules/shop/settings.js +18 -0
  105. package/dist/modules/shop/settings.js.map +1 -0
  106. package/dist/schema/blocks.d.ts +27 -12
  107. package/dist/schema/blocks.js +3 -0
  108. package/dist/schema/blocks.js.map +1 -1
  109. package/dist/schema/index.d.ts +1 -0
  110. package/dist/schema/index.js +1 -0
  111. package/dist/schema/index.js.map +1 -1
  112. package/dist/schema/settings.d.ts +1 -34
  113. package/dist/schema/settings.js +2 -8
  114. package/dist/schema/settings.js.map +1 -1
  115. package/dist/schema/shop.d.ts +939 -0
  116. package/dist/schema/shop.js +209 -0
  117. package/dist/schema/shop.js.map +1 -0
  118. package/dist/schema/siteConfig.d.ts +3 -71
  119. package/dist/schema/siteConfig.js +3 -8
  120. package/dist/schema/siteConfig.js.map +1 -1
  121. package/dist/schema/symbols.d.ts +2 -2
  122. package/dist/schema/symbols.js.map +1 -1
  123. package/dist/server/index.d.ts +1 -1
  124. package/dist/server/index.js +2 -0
  125. package/dist/server/index.js.map +1 -1
  126. package/dist/server/pages.d.ts +23 -3
  127. package/dist/server/pages.js +37 -3
  128. package/dist/server/pages.js.map +1 -1
  129. package/dist/server/seo.js +5 -3
  130. package/dist/server/seo.js.map +1 -1
  131. package/dist/settings/useSiteSettings.d.ts +15 -3
  132. package/dist/settings/useSiteSettings.js +26 -5
  133. package/dist/settings/useSiteSettings.js.map +1 -1
  134. package/dist/styles.css +14 -0
  135. package/package.json +6 -2
@@ -0,0 +1,209 @@
1
+ import { z } from "zod";
2
+ import { blockBase } from "./blocks";
3
+ const ShopCategorySchema = z.object({
4
+ name: z.string().min(1).max(120),
5
+ slug: z.string().min(1).max(120).regex(/^[a-z0-9-]+$/, "slug: alleen kleine letters, cijfers en streepjes"),
6
+ description: z.string().max(2e3).optional(),
7
+ imageUrl: z.string().max(2e3).optional(),
8
+ order: z.number().int().min(0).max(9999).default(0),
9
+ visible: z.boolean().default(true)
10
+ });
11
+ const ProductOptionSchema = z.object({
12
+ name: z.string().min(1).max(40),
13
+ values: z.array(z.string().min(1).max(60)).min(1).max(30)
14
+ });
15
+ const ProductVariantSchema = z.object({
16
+ key: z.string().min(1).max(200),
17
+ priceCents: z.number().int().min(0).max(1e8).optional(),
18
+ stock: z.number().int().min(0).max(1e6).optional(),
19
+ sku: z.string().max(60).optional(),
20
+ active: z.boolean().default(true)
21
+ });
22
+ const ProductStatusSchema = z.enum(["draft", "active", "archived"]);
23
+ const ProductSchema = z.object({
24
+ name: z.string().min(1).max(200),
25
+ slug: z.string().min(1).max(120).regex(/^[a-z0-9-]+$/, "slug: alleen kleine letters, cijfers en streepjes"),
26
+ /**
27
+ * Beschrijving als platte tekst (regeleinden blijven behouden). Bewust géén HTML:
28
+ * producten worden client-side door editors geschreven (rules-pad, niet via de
29
+ * publish-sanitizer), dus HTML zou hier een XSS-route zijn.
30
+ */
31
+ description: z.string().max(2e4).default(""),
32
+ categoryIds: z.array(z.string().max(80)).max(20).default([]),
33
+ /** Afbeeldings-URL's (via de mediabibliotheek). Eerste = hoofdbeeld. */
34
+ images: z.array(z.string().max(2e3)).max(12).default([]),
35
+ /** Prijs incl. btw, in centen. */
36
+ priceCents: z.number().int().min(0).max(1e8).default(0),
37
+ /** Doorgestreepte "van"-prijs (optioneel). */
38
+ compareAtCents: z.number().int().min(0).max(1e8).optional(),
39
+ /** Btw-tarief (NL): 21, 9 of 0. Prijzen zijn inclusief. */
40
+ vatRate: z.union([z.literal(21), z.literal(9), z.literal(0)]).default(21),
41
+ options: z.array(ProductOptionSchema).max(3).default([]),
42
+ variants: z.array(ProductVariantSchema).max(200).default([]),
43
+ /** Voorraad bijhouden? false = altijd bestelbaar (op-maat/print-on-demand). */
44
+ trackStock: z.boolean().default(false),
45
+ /** Voorraad voor producten zónder varianten (met varianten telt de variant-stock). */
46
+ stock: z.number().int().min(0).max(1e6).optional(),
47
+ status: ProductStatusSchema.default("draft"),
48
+ /** Sorteervolgorde binnen een categorie (laag = eerst). */
49
+ order: z.number().int().min(0).max(9999).default(0),
50
+ featured: z.boolean().default(false)
51
+ });
52
+ const OrderStatusSchema = z.enum(["pending", "paid", "shipped", "cancelled", "refunded"]);
53
+ const OrderItemSchema = z.object({
54
+ kind: z.enum(["product", "donation"]).default("product"),
55
+ productId: z.string().max(80).optional(),
56
+ name: z.string().min(1).max(240),
57
+ variantKey: z.string().max(200).optional(),
58
+ qty: z.number().int().min(1).max(999),
59
+ unitPriceCents: z.number().int().min(0).max(1e8),
60
+ vatRate: z.union([z.literal(21), z.literal(9), z.literal(0)]).default(21)
61
+ });
62
+ const OrderAddressSchema = z.object({
63
+ address: z.string().min(1).max(200),
64
+ zip: z.string().min(1).max(20),
65
+ city: z.string().min(1).max(120),
66
+ country: z.string().min(2).max(2).default("NL")
67
+ });
68
+ const OrderCustomerSchema = z.object({
69
+ email: z.string().email().max(200),
70
+ name: z.string().min(1).max(200),
71
+ phone: z.string().max(40).optional()
72
+ });
73
+ const OrderSchema = z.object({
74
+ number: z.string().min(1).max(40),
75
+ status: OrderStatusSchema,
76
+ items: z.array(OrderItemSchema).min(1).max(50),
77
+ amount: z.object({
78
+ itemsCents: z.number().int().min(0),
79
+ shippingCents: z.number().int().min(0),
80
+ totalCents: z.number().int().min(0)
81
+ }),
82
+ customer: OrderCustomerSchema,
83
+ shipping: OrderAddressSchema.optional(),
84
+ note: z.string().max(2e3).optional(),
85
+ stripe: z.object({
86
+ sessionId: z.string().max(200).optional(),
87
+ paymentIntentId: z.string().max(200).optional()
88
+ }).default({})
89
+ });
90
+ const CreateOrderItemInputSchema = z.object({
91
+ productId: z.string().min(1).max(80).optional(),
92
+ variantKey: z.string().max(200).optional(),
93
+ qty: z.number().int().min(1).max(999).default(1),
94
+ /** Alleen voor donaties (zonder productId). */
95
+ donationCents: z.number().int().min(50).max(1e8).optional()
96
+ });
97
+ const CreateOrderInputSchema = z.object({
98
+ siteId: z.string().min(1).max(80),
99
+ items: z.array(CreateOrderItemInputSchema).min(1).max(50),
100
+ customer: OrderCustomerSchema,
101
+ shipping: OrderAddressSchema.optional(),
102
+ note: z.string().max(2e3).optional(),
103
+ /** Paden op de klantsite waarheen Stripe terug redirect. */
104
+ successPath: z.string().max(400).default("/bedankt"),
105
+ cancelPath: z.string().max(400).default("/winkelwagen"),
106
+ /** Honeypot — moet leeg zijn (bot-detectie). */
107
+ _hp: z.string().max(0).optional()
108
+ });
109
+ const ShopCategoriesBlockSchema = z.object({
110
+ ...blockBase,
111
+ _type: z.literal("shopCategories"),
112
+ heading: z.string().max(200).optional(),
113
+ columns: z.number().int().min(2).max(4).default(3)
114
+ });
115
+ const ProductGridBlockSchema = z.object({
116
+ ...blockBase,
117
+ _type: z.literal("productGrid"),
118
+ heading: z.string().max(200).optional(),
119
+ /** Vast categorie-filter (slug). Leeg + useQueryCategory → filter via `?cat=` in de URL. */
120
+ categorySlug: z.string().max(120).optional(),
121
+ useQueryCategory: z.boolean().default(true),
122
+ featuredOnly: z.boolean().default(false),
123
+ columns: z.number().int().min(2).max(4).default(3),
124
+ limit: z.number().int().min(1).max(200).default(60),
125
+ sort: z.enum(["order", "name", "priceAsc", "priceDesc"]).default("order")
126
+ });
127
+ const ProductDetailBlockSchema = z.object({
128
+ ...blockBase,
129
+ _type: z.literal("productDetail"),
130
+ /** Vast product (slug); leeg → product uit `?p=` in de URL (één generieke detailpagina). */
131
+ slug: z.string().max(120).optional()
132
+ });
133
+ const CartBlockSchema = z.object({
134
+ ...blockBase,
135
+ _type: z.literal("cart"),
136
+ heading: z.string().max(200).optional()
137
+ });
138
+ const CheckoutBlockSchema = z.object({
139
+ ...blockBase,
140
+ _type: z.literal("checkout"),
141
+ heading: z.string().max(200).optional(),
142
+ introText: z.string().max(2e3).optional()
143
+ });
144
+ const DonationBlockSchema = z.object({
145
+ ...blockBase,
146
+ _type: z.literal("donation"),
147
+ heading: z.string().max(200).optional(),
148
+ introText: z.string().max(2e3).optional(),
149
+ /** Voorgestelde bedragen in centen. */
150
+ amountsCents: z.array(z.number().int().min(50).max(1e8)).max(8).default([500, 1e3, 2500]),
151
+ allowCustom: z.boolean().default(true),
152
+ customMinCents: z.number().int().min(50).default(100),
153
+ customMaxCents: z.number().int().max(1e8).default(1e5),
154
+ buttonLabel: z.string().max(80).default("Doneer")
155
+ });
156
+ const OrderStatusBlockSchema = z.object({
157
+ ...blockBase,
158
+ _type: z.literal("orderStatus"),
159
+ successTitle: z.string().max(200).default("Bedankt voor je bestelling!"),
160
+ successText: z.string().max(2e3).default("Je ontvangt zo een bevestiging per e-mail."),
161
+ cancelledTitle: z.string().max(200).default("Betaling niet afgerond"),
162
+ cancelledText: z.string().max(2e3).default("Je bestelling is niet betaald. Probeer het gerust opnieuw.")
163
+ });
164
+ function variantKeyFor(values) {
165
+ return values.join("|");
166
+ }
167
+ function productPriceCents(product, variantKey) {
168
+ if (variantKey) {
169
+ const v = product.variants?.find((x) => x.key === variantKey);
170
+ if (v?.priceCents !== void 0) return v.priceCents;
171
+ }
172
+ return product.priceCents;
173
+ }
174
+ function shippingCentsFor(itemsCents, cfg, hasShippableItems) {
175
+ if (!hasShippableItems) return 0;
176
+ const flat = cfg.shippingFlatCents ?? 0;
177
+ const freeFrom = cfg.shippingFreeFromCents;
178
+ if (freeFrom !== void 0 && freeFrom > 0 && itemsCents >= freeFrom) return 0;
179
+ return flat;
180
+ }
181
+ function formatCents(cents, currency = "EUR") {
182
+ return new Intl.NumberFormat("nl-NL", { style: "currency", currency }).format(cents / 100);
183
+ }
184
+ export {
185
+ CartBlockSchema,
186
+ CheckoutBlockSchema,
187
+ CreateOrderInputSchema,
188
+ CreateOrderItemInputSchema,
189
+ DonationBlockSchema,
190
+ OrderAddressSchema,
191
+ OrderCustomerSchema,
192
+ OrderItemSchema,
193
+ OrderSchema,
194
+ OrderStatusBlockSchema,
195
+ OrderStatusSchema,
196
+ ProductDetailBlockSchema,
197
+ ProductGridBlockSchema,
198
+ ProductOptionSchema,
199
+ ProductSchema,
200
+ ProductStatusSchema,
201
+ ProductVariantSchema,
202
+ ShopCategoriesBlockSchema,
203
+ ShopCategorySchema,
204
+ formatCents,
205
+ productPriceCents,
206
+ shippingCentsFor,
207
+ variantKeyFor
208
+ };
209
+ //# sourceMappingURL=shop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/schema/shop.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { blockBase } from \"./blocks\";\n\n/**\n * Shop-schema's (5.5) — webwinkel + donaties. Pure Zod: gedeeld door core, admin en Functions.\n *\n * Datamodel per tenant:\n * sites/{siteId}/shopCategories/{id} — categorieën (editor-write, publiek leesbaar)\n * sites/{siteId}/products/{id} — producten (editor-write, publiek leesbaar als actief)\n * sites/{siteId}/orders/{id} — bestellingen (ALLEEN via Functions; editor leest,\n * en mag alleen status/verzendvelden muteren)\n * sites/{siteId}/private/shop — geheimen (Stripe-key, SMTP) — deny-all, alleen Functions\n *\n * Geld is ALTIJD een integer in centen (nooit floats). Publieke shopconfig (verzendkosten,\n * paden, donatie-grenzen) leeft in de module-settings van de `shop`-module\n * (settings/public.modules.shop.config) — zie modules/shop/module.ts.\n */\n\n/* ── Categorieën ─────────────────────────────────────────────────────────── */\n\nexport const ShopCategorySchema = z.object({\n name: z.string().min(1).max(120),\n slug: z\n .string()\n .min(1)\n .max(120)\n .regex(/^[a-z0-9-]+$/, \"slug: alleen kleine letters, cijfers en streepjes\"),\n description: z.string().max(2000).optional(),\n imageUrl: z.string().max(2000).optional(),\n order: z.number().int().min(0).max(9999).default(0),\n visible: z.boolean().default(true),\n});\nexport type ShopCategory = z.infer<typeof ShopCategorySchema>;\n\n/* ── Producten ───────────────────────────────────────────────────────────── */\n\n/** Optie-as, bv. { name: \"Maat\", values: [\"56\",\"62\",\"68\",\"74\"] }. Max 3 assen per product. */\nexport const ProductOptionSchema = z.object({\n name: z.string().min(1).max(40),\n values: z.array(z.string().min(1).max(60)).min(1).max(30),\n});\nexport type ProductOption = z.infer<typeof ProductOptionSchema>;\n\n/**\n * Variant = één combinatie van optiewaarden. `key` is de waarden gejoined met \"|\" in\n * optie-volgorde (bv. \"68|Roze\"). Alleen aanwezig waar hij afwijkt van de product-defaults.\n */\nexport const ProductVariantSchema = z.object({\n key: z.string().min(1).max(200),\n priceCents: z.number().int().min(0).max(1_000_000_00).optional(),\n stock: z.number().int().min(0).max(1_000_000).optional(),\n sku: z.string().max(60).optional(),\n active: z.boolean().default(true),\n});\nexport type ProductVariant = z.infer<typeof ProductVariantSchema>;\n\nexport const ProductStatusSchema = z.enum([\"draft\", \"active\", \"archived\"]);\nexport type ProductStatus = z.infer<typeof ProductStatusSchema>;\n\nexport const ProductSchema = z.object({\n name: z.string().min(1).max(200),\n slug: z\n .string()\n .min(1)\n .max(120)\n .regex(/^[a-z0-9-]+$/, \"slug: alleen kleine letters, cijfers en streepjes\"),\n /**\n * Beschrijving als platte tekst (regeleinden blijven behouden). Bewust géén HTML:\n * producten worden client-side door editors geschreven (rules-pad, niet via de\n * publish-sanitizer), dus HTML zou hier een XSS-route zijn.\n */\n description: z.string().max(20_000).default(\"\"),\n categoryIds: z.array(z.string().max(80)).max(20).default([]),\n /** Afbeeldings-URL's (via de mediabibliotheek). Eerste = hoofdbeeld. */\n images: z.array(z.string().max(2000)).max(12).default([]),\n /** Prijs incl. btw, in centen. */\n priceCents: z.number().int().min(0).max(1_000_000_00).default(0),\n /** Doorgestreepte \"van\"-prijs (optioneel). */\n compareAtCents: z.number().int().min(0).max(1_000_000_00).optional(),\n /** Btw-tarief (NL): 21, 9 of 0. Prijzen zijn inclusief. */\n vatRate: z.union([z.literal(21), z.literal(9), z.literal(0)]).default(21),\n options: z.array(ProductOptionSchema).max(3).default([]),\n variants: z.array(ProductVariantSchema).max(200).default([]),\n /** Voorraad bijhouden? false = altijd bestelbaar (op-maat/print-on-demand). */\n trackStock: z.boolean().default(false),\n /** Voorraad voor producten zónder varianten (met varianten telt de variant-stock). */\n stock: z.number().int().min(0).max(1_000_000).optional(),\n status: ProductStatusSchema.default(\"draft\"),\n /** Sorteervolgorde binnen een categorie (laag = eerst). */\n order: z.number().int().min(0).max(9999).default(0),\n featured: z.boolean().default(false),\n});\nexport type Product = z.infer<typeof ProductSchema>;\n\n/* ── Bestellingen ────────────────────────────────────────────────────────── */\n\nexport const OrderStatusSchema = z.enum([\"pending\", \"paid\", \"shipped\", \"cancelled\", \"refunded\"]);\nexport type OrderStatus = z.infer<typeof OrderStatusSchema>;\n\n/** Orderregel: snapshot van naam/prijs op besteldatum — blijft kloppen als het product wijzigt. */\nexport const OrderItemSchema = z.object({\n kind: z.enum([\"product\", \"donation\"]).default(\"product\"),\n productId: z.string().max(80).optional(),\n name: z.string().min(1).max(240),\n variantKey: z.string().max(200).optional(),\n qty: z.number().int().min(1).max(999),\n unitPriceCents: z.number().int().min(0).max(1_000_000_00),\n vatRate: z.union([z.literal(21), z.literal(9), z.literal(0)]).default(21),\n});\nexport type OrderItem = z.infer<typeof OrderItemSchema>;\n\nexport const OrderAddressSchema = z.object({\n address: z.string().min(1).max(200),\n zip: z.string().min(1).max(20),\n city: z.string().min(1).max(120),\n country: z.string().min(2).max(2).default(\"NL\"),\n});\nexport type OrderAddress = z.infer<typeof OrderAddressSchema>;\n\nexport const OrderCustomerSchema = z.object({\n email: z.string().email().max(200),\n name: z.string().min(1).max(200),\n phone: z.string().max(40).optional(),\n});\nexport type OrderCustomer = z.infer<typeof OrderCustomerSchema>;\n\n/** Volledige order zoals door Functions geschreven (timestamps komen daarbovenop). */\nexport const OrderSchema = z.object({\n number: z.string().min(1).max(40),\n status: OrderStatusSchema,\n items: z.array(OrderItemSchema).min(1).max(50),\n amount: z.object({\n itemsCents: z.number().int().min(0),\n shippingCents: z.number().int().min(0),\n totalCents: z.number().int().min(0),\n }),\n customer: OrderCustomerSchema,\n shipping: OrderAddressSchema.optional(),\n note: z.string().max(2000).optional(),\n stripe: z\n .object({\n sessionId: z.string().max(200).optional(),\n paymentIntentId: z.string().max(200).optional(),\n })\n .default({}),\n});\nexport type Order = z.infer<typeof OrderSchema>;\n\n/* ── Callable-input (checkout) ───────────────────────────────────────────── */\n\n/**\n * Input van de `createOrder`-callable. Client-prijzen worden NOOIT vertrouwd: de functie\n * herrekent alles tegen de opgeslagen product-docs. Alleen `donationCents` komt van de\n * client (binnen de per-site ingestelde min/max van het donatie-block).\n */\nexport const CreateOrderItemInputSchema = z.object({\n productId: z.string().min(1).max(80).optional(),\n variantKey: z.string().max(200).optional(),\n qty: z.number().int().min(1).max(999).default(1),\n /** Alleen voor donaties (zonder productId). */\n donationCents: z.number().int().min(50).max(1_000_000_00).optional(),\n});\n\nexport const CreateOrderInputSchema = z.object({\n siteId: z.string().min(1).max(80),\n items: z.array(CreateOrderItemInputSchema).min(1).max(50),\n customer: OrderCustomerSchema,\n shipping: OrderAddressSchema.optional(),\n note: z.string().max(2000).optional(),\n /** Paden op de klantsite waarheen Stripe terug redirect. */\n successPath: z.string().max(400).default(\"/bedankt\"),\n cancelPath: z.string().max(400).default(\"/winkelwagen\"),\n /** Honeypot — moet leeg zijn (bot-detectie). */\n _hp: z.string().max(0).optional(),\n});\nexport type CreateOrderInput = z.infer<typeof CreateOrderInputSchema>;\n\n/* ── Block-schema's ──────────────────────────────────────────────────────── */\n\nexport const ShopCategoriesBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"shopCategories\"),\n heading: z.string().max(200).optional(),\n columns: z.number().int().min(2).max(4).default(3),\n});\n\nexport const ProductGridBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"productGrid\"),\n heading: z.string().max(200).optional(),\n /** Vast categorie-filter (slug). Leeg + useQueryCategory → filter via `?cat=` in de URL. */\n categorySlug: z.string().max(120).optional(),\n useQueryCategory: z.boolean().default(true),\n featuredOnly: z.boolean().default(false),\n columns: z.number().int().min(2).max(4).default(3),\n limit: z.number().int().min(1).max(200).default(60),\n sort: z.enum([\"order\", \"name\", \"priceAsc\", \"priceDesc\"]).default(\"order\"),\n});\n\nexport const ProductDetailBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"productDetail\"),\n /** Vast product (slug); leeg → product uit `?p=` in de URL (één generieke detailpagina). */\n slug: z.string().max(120).optional(),\n});\n\nexport const CartBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"cart\"),\n heading: z.string().max(200).optional(),\n});\n\nexport const CheckoutBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"checkout\"),\n heading: z.string().max(200).optional(),\n introText: z.string().max(2000).optional(),\n});\n\nexport const DonationBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"donation\"),\n heading: z.string().max(200).optional(),\n introText: z.string().max(2000).optional(),\n /** Voorgestelde bedragen in centen. */\n amountsCents: z.array(z.number().int().min(50).max(1_000_000_00)).max(8).default([500, 1000, 2500]),\n allowCustom: z.boolean().default(true),\n customMinCents: z.number().int().min(50).default(100),\n customMaxCents: z.number().int().max(1_000_000_00).default(100_000),\n buttonLabel: z.string().max(80).default(\"Doneer\"),\n});\n\nexport const OrderStatusBlockSchema = z.object({\n ...blockBase,\n _type: z.literal(\"orderStatus\"),\n successTitle: z.string().max(200).default(\"Bedankt voor je bestelling!\"),\n successText: z.string().max(2000).default(\"Je ontvangt zo een bevestiging per e-mail.\"),\n cancelledTitle: z.string().max(200).default(\"Betaling niet afgerond\"),\n cancelledText: z.string().max(2000).default(\"Je bestelling is niet betaald. Probeer het gerust opnieuw.\"),\n});\n\n/* ── Helpers (gedeeld client/server) ─────────────────────────────────────── */\n\n/** Variant-key uit gekozen optiewaarden, in optie-volgorde: [\"68\",\"Roze\"] → \"68|Roze\". */\nexport function variantKeyFor(values: string[]): string {\n return values.join(\"|\");\n}\n\n/** Effectieve prijs van een product(-variant) in centen. */\nexport function productPriceCents(product: Pick<Product, \"priceCents\" | \"variants\">, variantKey?: string): number {\n if (variantKey) {\n const v = product.variants?.find((x) => x.key === variantKey);\n if (v?.priceCents !== undefined) return v.priceCents;\n }\n return product.priceCents;\n}\n\n/** Verzendkosten volgens de publieke shopconfig (flat + gratis-vanaf). Donatie-only = gratis. */\nexport function shippingCentsFor(\n itemsCents: number,\n cfg: { shippingFlatCents?: number; shippingFreeFromCents?: number },\n hasShippableItems: boolean,\n): number {\n if (!hasShippableItems) return 0;\n const flat = cfg.shippingFlatCents ?? 0;\n const freeFrom = cfg.shippingFreeFromCents;\n if (freeFrom !== undefined && freeFrom > 0 && itemsCents >= freeFrom) return 0;\n return flat;\n}\n\n/** Centen → \"€ 13,95\" (nl-NL). */\nexport function formatCents(cents: number, currency = \"EUR\"): string {\n return new Intl.NumberFormat(\"nl-NL\", { style: \"currency\", currency }).format(cents / 100);\n}\n"],"mappings":"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","names":[]}
@@ -45,17 +45,6 @@ export interface SiteConfig {
45
45
  seoDefaults?: Seo;
46
46
  /** Afzender-instelling voor formulier-e-mail (fallback voor settings/site). */
47
47
  forms?: FormsSender;
48
- /**
49
- * @deprecated Gebruik `modules` (XiCMS 3.0). Blijft werken als legacy-brug: `features[key] ===
50
- * false` zet de bijbehorende module uit. Aan/uit zetten van optionele beheer-panelen.
51
- */
52
- features?: {
53
- pages?: boolean;
54
- news?: boolean;
55
- media?: boolean;
56
- forms?: boolean;
57
- stats?: boolean;
58
- };
59
48
  /**
60
49
  * Modules per site aan/uit zetten en configureren (XiCMS 3.0). Een uitgezette module verbergt
61
50
  * zowel zijn blocks als zijn beheer-paneel. Per module kun je ook `config` meegeven (gevalideerd
@@ -80,7 +69,7 @@ export declare function defineSiteConfig(config: SiteConfig): SiteConfig;
80
69
  export declare const SiteConfigSchema: z.ZodObject<{
81
70
  siteId: z.ZodString;
82
71
  name: z.ZodString;
83
- domain: z.ZodOptional<z.ZodString>;
72
+ domain: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
84
73
  mode: z.ZodEnum<["demo", "live"]>;
85
74
  demoEditing: z.ZodOptional<z.ZodBoolean>;
86
75
  lang: z.ZodOptional<z.ZodString>;
@@ -262,25 +251,6 @@ export declare const SiteConfigSchema: z.ZodObject<{
262
251
  fromName?: string | undefined;
263
252
  notifyTo?: string | undefined;
264
253
  }>>;
265
- features: z.ZodOptional<z.ZodObject<{
266
- pages: z.ZodOptional<z.ZodBoolean>;
267
- news: z.ZodOptional<z.ZodBoolean>;
268
- media: z.ZodOptional<z.ZodBoolean>;
269
- forms: z.ZodOptional<z.ZodBoolean>;
270
- stats: z.ZodOptional<z.ZodBoolean>;
271
- }, "strip", z.ZodTypeAny, {
272
- forms?: boolean | undefined;
273
- pages?: boolean | undefined;
274
- news?: boolean | undefined;
275
- media?: boolean | undefined;
276
- stats?: boolean | undefined;
277
- }, {
278
- forms?: boolean | undefined;
279
- pages?: boolean | undefined;
280
- news?: boolean | undefined;
281
- media?: boolean | undefined;
282
- stats?: boolean | undefined;
283
- }>>;
284
254
  modules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodObject<{
285
255
  enabled: z.ZodOptional<z.ZodBoolean>;
286
256
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -294,7 +264,7 @@ export declare const SiteConfigSchema: z.ZodObject<{
294
264
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
295
265
  siteId: z.ZodString;
296
266
  name: z.ZodString;
297
- domain: z.ZodOptional<z.ZodString>;
267
+ domain: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
298
268
  mode: z.ZodEnum<["demo", "live"]>;
299
269
  demoEditing: z.ZodOptional<z.ZodBoolean>;
300
270
  lang: z.ZodOptional<z.ZodString>;
@@ -476,25 +446,6 @@ export declare const SiteConfigSchema: z.ZodObject<{
476
446
  fromName?: string | undefined;
477
447
  notifyTo?: string | undefined;
478
448
  }>>;
479
- features: z.ZodOptional<z.ZodObject<{
480
- pages: z.ZodOptional<z.ZodBoolean>;
481
- news: z.ZodOptional<z.ZodBoolean>;
482
- media: z.ZodOptional<z.ZodBoolean>;
483
- forms: z.ZodOptional<z.ZodBoolean>;
484
- stats: z.ZodOptional<z.ZodBoolean>;
485
- }, "strip", z.ZodTypeAny, {
486
- forms?: boolean | undefined;
487
- pages?: boolean | undefined;
488
- news?: boolean | undefined;
489
- media?: boolean | undefined;
490
- stats?: boolean | undefined;
491
- }, {
492
- forms?: boolean | undefined;
493
- pages?: boolean | undefined;
494
- news?: boolean | undefined;
495
- media?: boolean | undefined;
496
- stats?: boolean | undefined;
497
- }>>;
498
449
  modules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodObject<{
499
450
  enabled: z.ZodOptional<z.ZodBoolean>;
500
451
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -508,7 +459,7 @@ export declare const SiteConfigSchema: z.ZodObject<{
508
459
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
509
460
  siteId: z.ZodString;
510
461
  name: z.ZodString;
511
- domain: z.ZodOptional<z.ZodString>;
462
+ domain: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
512
463
  mode: z.ZodEnum<["demo", "live"]>;
513
464
  demoEditing: z.ZodOptional<z.ZodBoolean>;
514
465
  lang: z.ZodOptional<z.ZodString>;
@@ -690,25 +641,6 @@ export declare const SiteConfigSchema: z.ZodObject<{
690
641
  fromName?: string | undefined;
691
642
  notifyTo?: string | undefined;
692
643
  }>>;
693
- features: z.ZodOptional<z.ZodObject<{
694
- pages: z.ZodOptional<z.ZodBoolean>;
695
- news: z.ZodOptional<z.ZodBoolean>;
696
- media: z.ZodOptional<z.ZodBoolean>;
697
- forms: z.ZodOptional<z.ZodBoolean>;
698
- stats: z.ZodOptional<z.ZodBoolean>;
699
- }, "strip", z.ZodTypeAny, {
700
- forms?: boolean | undefined;
701
- pages?: boolean | undefined;
702
- news?: boolean | undefined;
703
- media?: boolean | undefined;
704
- stats?: boolean | undefined;
705
- }, {
706
- forms?: boolean | undefined;
707
- pages?: boolean | undefined;
708
- news?: boolean | undefined;
709
- media?: boolean | undefined;
710
- stats?: boolean | undefined;
711
- }>>;
712
644
  modules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodObject<{
713
645
  enabled: z.ZodOptional<z.ZodBoolean>;
714
646
  config: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -7,7 +7,9 @@ function defineSiteConfig(config) {
7
7
  const SiteConfigSchema = z.object({
8
8
  siteId: z.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "gebruik kleine letters/cijfers/koppeltekens (bv. 'bakkerij-jansen')"),
9
9
  name: z.string().min(1).max(160),
10
- domain: z.string().max(255).optional(),
10
+ // Alleen een hostname (www.klant.nl) — géén protocol/pad: "https://…/x" glipte er eerst
11
+ // doorheen en werd maar half genormaliseerd in siteOrigin/buildMetadata.
12
+ domain: z.string().max(255).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, "alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)").optional().or(z.literal("")),
11
13
  mode: z.enum(["demo", "live"]),
12
14
  demoEditing: z.boolean().optional(),
13
15
  lang: z.string().max(10).optional(),
@@ -18,13 +20,6 @@ const SiteConfigSchema = z.object({
18
20
  contactEmail: z.string().email().optional(),
19
21
  seoDefaults: SeoSchema.optional(),
20
22
  forms: FormsSenderSchema.optional(),
21
- features: z.object({
22
- pages: z.boolean().optional(),
23
- news: z.boolean().optional(),
24
- media: z.boolean().optional(),
25
- forms: z.boolean().optional(),
26
- stats: z.boolean().optional()
27
- }).optional(),
28
23
  modules: ModulesSettingsSchema.optional()
29
24
  }).passthrough();
30
25
  function validateSiteConfig(config) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schema/siteConfig.ts"],"sourcesContent":["/**\n * SiteConfig — de statische basisconfig in een klantrepo (`content/site.config.ts`).\n * Dit is de fallback/identiteit van de site; bewerkbare instellingen kunnen in Firebase\n * (`sites/{siteId}/settings/site`) staan en deze waarden overschrijven.\n *\n * `@xiwebdesign/xicms-core` bevat hier nooit klantgegevens — die staan in de klantrepo.\n */\nimport { z } from \"zod\";\nimport { NavItemSchema, ThemeSchema, FormsSenderSchema, ModulesSettingsSchema, type NavItem, type ThemeTokens, type FormsSender } from \"./settings\";\nimport { SeoSchema, type Seo } from \"./seo\";\nimport type { ModulesSiteConfig } from \"../modules/types\";\n\nexport type SiteMode = \"demo\" | \"live\";\n\nexport interface SiteConfig {\n /** Uniek tenant-id; bepaalt het pad `sites/{siteId}/...` in Firebase. */\n siteId: string;\n /** Weergavenaam van het bedrijf. */\n name: string;\n /** Productiedomein, bv. \"www.bakkerijjansen.nl\". */\n domain?: string;\n /** \"demo\" toont de lock/CTA; \"live\" is de productiestand. */\n mode: SiteMode;\n /**\n * Demo-bewerkmodus: toont een pseudo-login zodat bezoekers het beheer (teksten + blokken) kunnen\n * uitproberen. Alle \"bewerkingen\" blijven UITSLUITEND lokaal in de browser (localStorage) — er gaat\n * niets naar Firebase. Alleen bedoeld voor een demo-site; laat dit `false`/weg op echte klantsites.\n */\n demoEditing?: boolean;\n /** Taalcode voor `<html lang>` en a11y (default \"nl\"). */\n lang?: string;\n /**\n * Extra talen naast de default (5.0), bv. `[\"de\", \"en\"]`. Vertalingen leven als overlay op\n * pagina's/symbolen; niet-default talen krijgen een URL-prefix (`/de/over-ons`). Weglaten =\n * ééntalige site (geen overhead).\n */\n locales?: string[];\n /** Default-taal van de canonieke content (default: `lang` ?? \"nl\"). */\n defaultLocale?: string;\n /** Huisstijl. */\n theme: ThemeTokens;\n /** Hoofdnavigatie. */\n nav?: NavItem[];\n /** Contact-e-mailadres voor de demo-CTA en formulieren. */\n contactEmail?: string;\n /** Standaard-SEO als een pagina niets opgeeft. */\n seoDefaults?: Seo;\n /** Afzender-instelling voor formulier-e-mail (fallback voor settings/site). */\n forms?: FormsSender;\n /**\n * @deprecated Gebruik `modules` (XiCMS 3.0). Blijft werken als legacy-brug: `features[key] ===\n * false` zet de bijbehorende module uit. Aan/uit zetten van optionele beheer-panelen.\n */\n features?: {\n pages?: boolean;\n news?: boolean;\n media?: boolean;\n forms?: boolean;\n stats?: boolean;\n };\n /**\n * Modules per site aan/uit zetten en configureren (XiCMS 3.0). Een uitgezette module verbergt\n * zowel zijn blocks als zijn beheer-paneel. Per module kun je ook `config` meegeven (gevalideerd\n * tegen het `settingsSchema` van de module) zodat de agent gedrag instelt zonder core te forken:\n *\n * modules: {\n * gallery: { enabled: true, config: { lightbox: true, columns: 3 } },\n * stats: false,\n * }\n *\n * Afwezig = de standaard van de module (`defaultEnabled`, doorgaans aan).\n */\n modules?: ModulesSiteConfig;\n}\n\n/** Helper met type-inferentie voor `site.config.ts`. */\nexport function defineSiteConfig(config: SiteConfig): SiteConfig {\n return config;\n}\n\n/**\n * Runtime-spiegel van de `SiteConfig`-interface. TypeScript vangt fouten bij compileren, maar een\n * (agent-)geschreven `site.config.ts` kan alsnog ongeldige waarden bevatten (verkeerd siteId-formaat,\n * te lange strings, kapotte nav-items). `.passthrough()` zodat extra klant-velden nooit een fout zijn.\n */\nexport const SiteConfigSchema = z\n .object({\n siteId: z\n .string()\n .min(1)\n .max(64)\n .regex(/^[a-z0-9][a-z0-9-]*$/, \"gebruik kleine letters/cijfers/koppeltekens (bv. 'bakkerij-jansen')\"),\n name: z.string().min(1).max(160),\n domain: z.string().max(255).optional(),\n mode: z.enum([\"demo\", \"live\"]),\n demoEditing: z.boolean().optional(),\n lang: z.string().max(10).optional(),\n locales: z.array(z.string().min(2).max(10)).max(10).optional(),\n defaultLocale: z.string().min(2).max(10).optional(),\n theme: ThemeSchema,\n nav: z.array(NavItemSchema).max(50).optional(),\n contactEmail: z.string().email().optional(),\n seoDefaults: SeoSchema.optional(),\n forms: FormsSenderSchema.optional(),\n features: z\n .object({\n pages: z.boolean().optional(),\n news: z.boolean().optional(),\n media: z.boolean().optional(),\n forms: z.boolean().optional(),\n stats: z.boolean().optional(),\n })\n .optional(),\n modules: ModulesSettingsSchema.optional(),\n })\n .passthrough();\n\n/**\n * Valideert een SiteConfig en geeft leesbare issues terug (leeg = geldig). Wordt in development\n * door de provider aangeroepen zodat een agent config-fouten direct in de console ziet, in plaats\n * van een subtiel kapotte site. Gooit bewust niet — een issue mag een site nooit platleggen.\n */\nexport function validateSiteConfig(config: unknown): string[] {\n const res = SiteConfigSchema.safeParse(config);\n if (res.success) return [];\n return res.error.issues.map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`);\n}\n"],"mappings":"AAOA,SAAS,SAAS;AAClB,SAAS,eAAe,aAAa,mBAAmB,6BAA+E;AACvI,SAAS,iBAA2B;AAmE7B,SAAS,iBAAiB,QAAgC;AAC/D,SAAO;AACT;AAOO,MAAM,mBAAmB,EAC7B,OAAO;AAAA,EACN,QAAQ,EACL,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,wBAAwB,qEAAqE;AAAA,EACtG,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACrC,MAAM,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC;AAAA,EAC7B,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,EAClC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAClC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC7D,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAClD,OAAO;AAAA,EACP,KAAK,EAAE,MAAM,aAAa,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC7C,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,EAC1C,aAAa,UAAU,SAAS;AAAA,EAChC,OAAO,kBAAkB,SAAS;AAAA,EAClC,UAAU,EACP,OAAO;AAAA,IACN,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC5B,MAAM,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC3B,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC5B,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC5B,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC9B,CAAC,EACA,SAAS;AAAA,EACZ,SAAS,sBAAsB,SAAS;AAC1C,CAAC,EACA,YAAY;AAOR,SAAS,mBAAmB,QAA2B;AAC5D,QAAM,MAAM,iBAAiB,UAAU,MAAM;AAC7C,MAAI,IAAI,QAAS,QAAO,CAAC;AACzB,SAAO,IAAI,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,OAAO,EAAE;AACpF;","names":[]}
1
+ {"version":3,"sources":["../../src/schema/siteConfig.ts"],"sourcesContent":["/**\n * SiteConfig — de statische basisconfig in een klantrepo (`content/site.config.ts`).\n * Dit is de fallback/identiteit van de site; bewerkbare instellingen kunnen in Firebase\n * (`sites/{siteId}/settings/site`) staan en deze waarden overschrijven.\n *\n * `@xiwebdesign/xicms-core` bevat hier nooit klantgegevens — die staan in de klantrepo.\n */\nimport { z } from \"zod\";\nimport { NavItemSchema, ThemeSchema, FormsSenderSchema, ModulesSettingsSchema, type NavItem, type ThemeTokens, type FormsSender } from \"./settings\";\nimport { SeoSchema, type Seo } from \"./seo\";\nimport type { ModulesSiteConfig } from \"../modules/types\";\n\nexport type SiteMode = \"demo\" | \"live\";\n\nexport interface SiteConfig {\n /** Uniek tenant-id; bepaalt het pad `sites/{siteId}/...` in Firebase. */\n siteId: string;\n /** Weergavenaam van het bedrijf. */\n name: string;\n /** Productiedomein, bv. \"www.bakkerijjansen.nl\". */\n domain?: string;\n /** \"demo\" toont de lock/CTA; \"live\" is de productiestand. */\n mode: SiteMode;\n /**\n * Demo-bewerkmodus: toont een pseudo-login zodat bezoekers het beheer (teksten + blokken) kunnen\n * uitproberen. Alle \"bewerkingen\" blijven UITSLUITEND lokaal in de browser (localStorage) — er gaat\n * niets naar Firebase. Alleen bedoeld voor een demo-site; laat dit `false`/weg op echte klantsites.\n */\n demoEditing?: boolean;\n /** Taalcode voor `<html lang>` en a11y (default \"nl\"). */\n lang?: string;\n /**\n * Extra talen naast de default (5.0), bv. `[\"de\", \"en\"]`. Vertalingen leven als overlay op\n * pagina's/symbolen; niet-default talen krijgen een URL-prefix (`/de/over-ons`). Weglaten =\n * ééntalige site (geen overhead).\n */\n locales?: string[];\n /** Default-taal van de canonieke content (default: `lang` ?? \"nl\"). */\n defaultLocale?: string;\n /** Huisstijl. */\n theme: ThemeTokens;\n /** Hoofdnavigatie. */\n nav?: NavItem[];\n /** Contact-e-mailadres voor de demo-CTA en formulieren. */\n contactEmail?: string;\n /** Standaard-SEO als een pagina niets opgeeft. */\n seoDefaults?: Seo;\n /** Afzender-instelling voor formulier-e-mail (fallback voor settings/site). */\n forms?: FormsSender;\n /**\n * Modules per site aan/uit zetten en configureren (XiCMS 3.0). Een uitgezette module verbergt\n * zowel zijn blocks als zijn beheer-paneel. Per module kun je ook `config` meegeven (gevalideerd\n * tegen het `settingsSchema` van de module) zodat de agent gedrag instelt zonder core te forken:\n *\n * modules: {\n * gallery: { enabled: true, config: { lightbox: true, columns: 3 } },\n * stats: false,\n * }\n *\n * Afwezig = de standaard van de module (`defaultEnabled`, doorgaans aan).\n */\n modules?: ModulesSiteConfig;\n}\n\n/** Helper met type-inferentie voor `site.config.ts`. */\nexport function defineSiteConfig(config: SiteConfig): SiteConfig {\n return config;\n}\n\n/**\n * Runtime-spiegel van de `SiteConfig`-interface. TypeScript vangt fouten bij compileren, maar een\n * (agent-)geschreven `site.config.ts` kan alsnog ongeldige waarden bevatten (verkeerd siteId-formaat,\n * te lange strings, kapotte nav-items). `.passthrough()` zodat extra klant-velden nooit een fout zijn.\n */\nexport const SiteConfigSchema = z\n .object({\n siteId: z\n .string()\n .min(1)\n .max(64)\n .regex(/^[a-z0-9][a-z0-9-]*$/, \"gebruik kleine letters/cijfers/koppeltekens (bv. 'bakkerij-jansen')\"),\n name: z.string().min(1).max(160),\n // Alleen een hostname (www.klant.nl) — géén protocol/pad: \"https://…/x\" glipte er eerst\n // doorheen en werd maar half genormaliseerd in siteOrigin/buildMetadata.\n domain: z\n .string()\n .max(255)\n .regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\")\n .optional()\n .or(z.literal(\"\")),\n mode: z.enum([\"demo\", \"live\"]),\n demoEditing: z.boolean().optional(),\n lang: z.string().max(10).optional(),\n locales: z.array(z.string().min(2).max(10)).max(10).optional(),\n defaultLocale: z.string().min(2).max(10).optional(),\n theme: ThemeSchema,\n nav: z.array(NavItemSchema).max(50).optional(),\n contactEmail: z.string().email().optional(),\n seoDefaults: SeoSchema.optional(),\n forms: FormsSenderSchema.optional(),\n modules: ModulesSettingsSchema.optional(),\n })\n .passthrough();\n\n/**\n * Valideert een SiteConfig en geeft leesbare issues terug (leeg = geldig). Wordt in development\n * door de provider aangeroepen zodat een agent config-fouten direct in de console ziet, in plaats\n * van een subtiel kapotte site. Gooit bewust niet — een issue mag een site nooit platleggen.\n */\nexport function validateSiteConfig(config: unknown): string[] {\n const res = SiteConfigSchema.safeParse(config);\n if (res.success) return [];\n return res.error.issues.map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`);\n}\n"],"mappings":"AAOA,SAAS,SAAS;AAClB,SAAS,eAAe,aAAa,mBAAmB,6BAA+E;AACvI,SAAS,iBAA2B;AAwD7B,SAAS,iBAAiB,QAAgC;AAC/D,SAAO;AACT;AAOO,MAAM,mBAAmB,EAC7B,OAAO;AAAA,EACN,QAAQ,EACL,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,wBAAwB,qEAAqE;AAAA,EACtG,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA;AAAA;AAAA,EAG/B,QAAQ,EACL,OAAO,EACP,IAAI,GAAG,EACP,MAAM,sEAAsE,kEAAkE,EAC9I,SAAS,EACT,GAAG,EAAE,QAAQ,EAAE,CAAC;AAAA,EACnB,MAAM,EAAE,KAAK,CAAC,QAAQ,MAAM,CAAC;AAAA,EAC7B,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,EAClC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAClC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC7D,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAClD,OAAO;AAAA,EACP,KAAK,EAAE,MAAM,aAAa,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC7C,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS;AAAA,EAC1C,aAAa,UAAU,SAAS;AAAA,EAChC,OAAO,kBAAkB,SAAS;AAAA,EAClC,SAAS,sBAAsB,SAAS;AAC1C,CAAC,EACA,YAAY;AAOR,SAAS,mBAAmB,QAA2B;AAC5D,QAAM,MAAM,iBAAiB,UAAU,MAAM;AAC7C,MAAI,IAAI,QAAS,QAAO,CAAC;AACzB,SAAO,IAAI,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,EAAE,OAAO,EAAE;AACpF;","names":[]}
@@ -199,7 +199,7 @@ export declare const SymbolSchema: z.ZodObject<{
199
199
  }, z.ZodTypeAny, "passthrough">[] | undefined;
200
200
  translations?: Record<string, Record<string, string>> | undefined;
201
201
  }>;
202
- export type Symbol = z.infer<typeof SymbolSchema>;
203
- export type StoredSymbol = Symbol & {
202
+ export type SymbolDefinition = z.infer<typeof SymbolSchema>;
203
+ export type StoredSymbol = SymbolDefinition & {
204
204
  id: string;
205
205
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schema/symbols.ts"],"sourcesContent":["/**\n * Symbols (5.0, primo-concept): een symbol is een benoemde, gedeelde sectie — een compositie van\n * gewone blokken — die op meerdere pagina's als **referentie** wordt ingevoegd (`symbolRef`-blok).\n * Wijzig het symbol → alle pagina's die ernaar verwijzen bewegen mee (i.t.t. patterns, die bij\n * invoegen worden gekloond). \"Loskoppelen\" in de editor zet een referentie om in een kloon.\n *\n * Opslag: `sites/{siteId}/symbols/{symbolId}` — publiek leesbaar (de live site leest ze bij het\n * renderen), schrijven alleen via de `saveSymbol`-callable (sanitize + validatie + audit).\n * Een symbol mag GEEN symbolRef bevatten (geen recursie) — afgedwongen in schema én callable.\n */\nimport { z } from \"zod\";\nimport { BlockBaseSchema } from \"./blocks\";\nimport { TranslationsSchema } from \"../i18n/overlay\";\n\nexport const SymbolSchema = z.object({\n name: z.string().min(1).max(120).describe(\"Naam\"),\n /** De blok-compositie van het symbol (zelfde vorm als page.blocks). */\n blocks: z\n .array(BlockBaseSchema)\n .max(50)\n .default([])\n .superRefine((blocks, ctx) => {\n if (blocks.some((b) => b._type === \"symbolRef\")) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: \"Een symbool kan geen ander symbool bevatten.\" });\n }\n }),\n /** Taal-overlay (5.0): per locale gepunte veldpaden → vertaalde tekst (zie i18n/overlay.ts). */\n translations: TranslationsSchema.optional(),\n});\nexport type Symbol = z.infer<typeof SymbolSchema>;\nexport type StoredSymbol = Symbol & { id: string };\n"],"mappings":"AAUA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAE5B,MAAM,eAAe,EAAE,OAAO;AAAA,EACnC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,MAAM;AAAA;AAAA,EAEhD,QAAQ,EACL,MAAM,eAAe,EACrB,IAAI,EAAE,EACN,QAAQ,CAAC,CAAC,EACV,YAAY,CAAC,QAAQ,QAAQ;AAC5B,QAAI,OAAO,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,GAAG;AAC/C,UAAI,SAAS,EAAE,MAAM,EAAE,aAAa,QAAQ,SAAS,+CAA+C,CAAC;AAAA,IACvG;AAAA,EACF,CAAC;AAAA;AAAA,EAEH,cAAc,mBAAmB,SAAS;AAC5C,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/schema/symbols.ts"],"sourcesContent":["/**\n * Symbols (5.0, primo-concept): een symbol is een benoemde, gedeelde sectie — een compositie van\n * gewone blokken — die op meerdere pagina's als **referentie** wordt ingevoegd (`symbolRef`-blok).\n * Wijzig het symbol → alle pagina's die ernaar verwijzen bewegen mee (i.t.t. patterns, die bij\n * invoegen worden gekloond). \"Loskoppelen\" in de editor zet een referentie om in een kloon.\n *\n * Opslag: `sites/{siteId}/symbols/{symbolId}` — publiek leesbaar (de live site leest ze bij het\n * renderen), schrijven alleen via de `saveSymbol`-callable (sanitize + validatie + audit).\n * Een symbol mag GEEN symbolRef bevatten (geen recursie) — afgedwongen in schema én callable.\n */\nimport { z } from \"zod\";\nimport { BlockBaseSchema } from \"./blocks\";\nimport { TranslationsSchema } from \"../i18n/overlay\";\n\nexport const SymbolSchema = z.object({\n name: z.string().min(1).max(120).describe(\"Naam\"),\n /** De blok-compositie van het symbol (zelfde vorm als page.blocks). */\n blocks: z\n .array(BlockBaseSchema)\n .max(50)\n .default([])\n .superRefine((blocks, ctx) => {\n if (blocks.some((b) => b._type === \"symbolRef\")) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: \"Een symbool kan geen ander symbool bevatten.\" });\n }\n }),\n /** Taal-overlay (5.0): per locale gepunte veldpaden → vertaalde tekst (zie i18n/overlay.ts). */\n translations: TranslationsSchema.optional(),\n});\n// \"SymbolDefinition\", niet \"Symbol\": dat schaduwde de JS-global Symbol in elke consumer-import.\nexport type SymbolDefinition = z.infer<typeof SymbolSchema>;\nexport type StoredSymbol = SymbolDefinition & { id: string };\n"],"mappings":"AAUA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AAE5B,MAAM,eAAe,EAAE,OAAO;AAAA,EACnC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,MAAM;AAAA;AAAA,EAEhD,QAAQ,EACL,MAAM,eAAe,EACrB,IAAI,EAAE,EACN,QAAQ,CAAC,CAAC,EACV,YAAY,CAAC,QAAQ,QAAQ;AAC5B,QAAI,OAAO,KAAK,CAAC,MAAM,EAAE,UAAU,WAAW,GAAG;AAC/C,UAAI,SAAS,EAAE,MAAM,EAAE,aAAa,QAAQ,SAAS,+CAA+C,CAAC;AAAA,IACvG;AAAA,EACF,CAAC;AAAA;AAAA,EAEH,cAAc,mBAAmB,SAAS;AAC5C,CAAC;","names":[]}
@@ -8,5 +8,5 @@ export { sanitizeRichText } from "./sanitize";
8
8
  export { jsonLdHtml } from "../blocks/builtins/jsonld";
9
9
  export { buildMetadata, buildJsonLd, buildSitemap, buildRobots, siteOrigin, businessJsonLd, breadcrumbListJsonLd, articleJsonLd, productJsonLd, openingHoursJsonLd, galleryJsonLd, videoObjectJsonLd, type BusinessInfo, } from "./seo";
10
10
  export { validateEnv, assertEnv, type EnvReport } from "./env";
11
- export { pathFromSlugs, listPublishedPages, getPublishedPageByPath, getPublishedPageByPathExpanded, getSiteSymbols, expandSymbols, getSiteCustomBlocks, getDraftPageById, getDraftPageByPath, type StoredPage, } from "./pages";
11
+ export { pathFromSlugs, listPublishedPages, getPublishedPageByPath, getPublishedPageByPathExpanded, getSiteSymbols, expandSymbols, getSiteCustomBlocks, getPublicSiteSettings, getDraftPageById, getDraftPageByPath, type StoredPage, } from "./pages";
12
12
  export { siteLocales, parseLocaleSlugs, localizedPath, localizePage, localizeBlocks, pruneTranslations, TranslationsSchema, type Translations, type LocaleConfig, } from "../i18n/overlay";
@@ -25,6 +25,7 @@ import {
25
25
  getSiteSymbols,
26
26
  expandSymbols,
27
27
  getSiteCustomBlocks,
28
+ getPublicSiteSettings,
28
29
  getDraftPageById,
29
30
  getDraftPageByPath
30
31
  } from "./pages";
@@ -54,6 +55,7 @@ export {
54
55
  getAdminDb,
55
56
  getDraftPageById,
56
57
  getDraftPageByPath,
58
+ getPublicSiteSettings,
57
59
  getPublishedPageByPath,
58
60
  getPublishedPageByPathExpanded,
59
61
  getSiteCustomBlocks,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/server — server-only helpers (firebase-admin, sanitization, SEO,\n * pagina-reads). NOOIT importeren in client-componenten.\n */\nimport \"server-only\";\n\nexport { getAdminApp, getAdminAuth, getAdminDb } from \"../firebase/admin\";\nexport { sanitizeRichText } from \"./sanitize\";\n// Veilige JSON-LD-serialisatie voor <script type=\"application/ld+json\"> in consumer-routes\n// (escapet `<` zodat klant-tekst met </script> de pagina niet kan breken).\nexport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\nexport {\n buildMetadata,\n buildJsonLd,\n buildSitemap,\n buildRobots,\n siteOrigin,\n businessJsonLd,\n breadcrumbListJsonLd,\n articleJsonLd,\n productJsonLd,\n openingHoursJsonLd,\n galleryJsonLd,\n videoObjectJsonLd,\n type BusinessInfo,\n} from \"./seo\";\nexport { validateEnv, assertEnv, type EnvReport } from \"./env\";\nexport {\n pathFromSlugs,\n listPublishedPages,\n getPublishedPageByPath,\n getPublishedPageByPathExpanded,\n getSiteSymbols,\n expandSymbols,\n getSiteCustomBlocks,\n getDraftPageById,\n getDraftPageByPath,\n type StoredPage,\n} from \"./pages\";\n// Localisatie (5.0) — server-safe overlay-helpers, ook bruikbaar in route-handlers/layouts.\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n type Translations,\n type LocaleConfig,\n} from \"../i18n/overlay\";\n"],"mappings":"AAIA,OAAO;AAEP,SAAS,aAAa,cAAc,kBAAkB;AACtD,SAAS,wBAAwB;AAGjC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,aAAa,iBAAiC;AACvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * @xiwebdesign/xicms-core/server — server-only helpers (firebase-admin, sanitization, SEO,\n * pagina-reads). NOOIT importeren in client-componenten.\n */\nimport \"server-only\";\n\nexport { getAdminApp, getAdminAuth, getAdminDb } from \"../firebase/admin\";\nexport { sanitizeRichText } from \"./sanitize\";\n// Veilige JSON-LD-serialisatie voor <script type=\"application/ld+json\"> in consumer-routes\n// (escapet `<` zodat klant-tekst met </script> de pagina niet kan breken).\nexport { jsonLdHtml } from \"../blocks/builtins/jsonld\";\nexport {\n buildMetadata,\n buildJsonLd,\n buildSitemap,\n buildRobots,\n siteOrigin,\n businessJsonLd,\n breadcrumbListJsonLd,\n articleJsonLd,\n productJsonLd,\n openingHoursJsonLd,\n galleryJsonLd,\n videoObjectJsonLd,\n type BusinessInfo,\n} from \"./seo\";\nexport { validateEnv, assertEnv, type EnvReport } from \"./env\";\nexport {\n pathFromSlugs,\n listPublishedPages,\n getPublishedPageByPath,\n getPublishedPageByPathExpanded,\n getSiteSymbols,\n expandSymbols,\n getSiteCustomBlocks,\n getPublicSiteSettings,\n getDraftPageById,\n getDraftPageByPath,\n type StoredPage,\n} from \"./pages\";\n// Localisatie (5.0) — server-safe overlay-helpers, ook bruikbaar in route-handlers/layouts.\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n type Translations,\n type LocaleConfig,\n} from \"../i18n/overlay\";\n"],"mappings":"AAIA,OAAO;AAEP,SAAS,aAAa,cAAc,kBAAkB;AACtD,SAAS,wBAAwB;AAGjC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,aAAa,iBAAiC;AACvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
@@ -2,6 +2,7 @@ import "server-only";
2
2
  import { type Page } from "../schema/pages";
3
3
  import { type StoredSymbol } from "../schema/symbols";
4
4
  import { type CustomBlock } from "../schema/customBlocks";
5
+ import { type PublicSiteSettings } from "../schema/settings";
5
6
  export type StoredPage = Page & {
6
7
  id: string;
7
8
  };
@@ -9,14 +10,24 @@ export type StoredPage = Page & {
9
10
  export declare function pathFromSlugs(slugs: string[] | undefined): string;
10
11
  /** Alle gepubliceerde pagina's (sitemap / generateStaticParams). Keyless via REST als geen Admin. */
11
12
  export declare function listPublishedPages(siteId: string): Promise<StoredPage[]>;
13
+ /**
14
+ * Per-request-dedup via React cache(): metadata (generateMetadata) én de render lezen dezelfde
15
+ * pagina — zonder cache() waren dat twee identieke Firestore-reads per request. NB: de
16
+ * `next:{revalidate}`-hint op het REST-pad dekt dit niet (POST-fetches cachen niet); route-level
17
+ * ISR (revalidate=300 in de consumer) blijft de echte kosten-demper tussen requests.
18
+ */
19
+ export declare const getPublishedPageByPath: typeof getPublishedPageByPathUncached;
20
+ export declare const getSiteSymbols: typeof getSiteSymbolsUncached;
21
+ export declare const getSiteCustomBlocks: typeof getSiteCustomBlocksUncached;
22
+ export declare const getPublicSiteSettings: typeof getPublicSiteSettingsUncached;
12
23
  /** Eén gepubliceerde pagina op pad. Keyless via REST als geen Admin. */
13
- export declare function getPublishedPageByPath(siteId: string, path: string): Promise<StoredPage | null>;
24
+ declare function getPublishedPageByPathUncached(siteId: string, path: string): Promise<StoredPage | null>;
14
25
  /** Draft-versie (voor preview achter auth). */
15
26
  export declare function getDraftPageById(siteId: string, pageId: string): Promise<StoredPage | null>;
16
27
  /** Draft op pad (preview). */
17
28
  export declare function getDraftPageByPath(siteId: string, path: string): Promise<StoredPage | null>;
18
29
  /** Alle symbolen van een site (gevalideerd; ongeldige overgeslagen met waarschuwing). */
19
- export declare function getSiteSymbols(siteId: string): Promise<StoredSymbol[]>;
30
+ declare function getSiteSymbolsUncached(siteId: string): Promise<StoredSymbol[]>;
20
31
  /**
21
32
  * Vervangt symbolRef-blokken door de blokken van hun symbool (één niveau — symbolen kunnen zelf
22
33
  * geen symbolRef bevatten). `_key`s van de ingevoegde blokken worden geprefixt met de ref-key
@@ -32,5 +43,14 @@ export declare function expandSymbols<T extends {
32
43
  * én op de ingevoegde symbolen (elk symbool draagt zijn eigen `translations`).
33
44
  */
34
45
  export declare function getPublishedPageByPathExpanded(siteId: string, path: string, locale?: string): Promise<StoredPage | null>;
46
+ /**
47
+ * Publieke site-instellingen (`sites/{siteId}/settings/public`) server-side — voor de
48
+ * `settingsData`-prop van XiCMSProvider. Zo zit het klant-thema/nav/footer al in de server-HTML
49
+ * (geen theme-flits bij eerste paint) én vervalt de per-bezoeker `getDoc` in de browser: de
50
+ * layout-route is ISR-gecached, dus dit kost ~1 read per revalidate i.p.v. per bezoeker.
51
+ * Fouten leveren `{}` op — de site valt dan terug op de statische SiteConfig.
52
+ */
53
+ declare function getPublicSiteSettingsUncached(siteId: string): Promise<PublicSiteSettings>;
35
54
  /** Alle custom blocks van een site (gevalideerd) — voor SSR-registratie via XiCMSProvider. */
36
- export declare function getSiteCustomBlocks(siteId: string): Promise<CustomBlock[]>;
55
+ declare function getSiteCustomBlocksUncached(siteId: string): Promise<CustomBlock[]>;
56
+ export {};
@@ -1,8 +1,10 @@
1
1
  import "server-only";
2
+ import { cache } from "react";
2
3
  import { getAdminDb } from "../firebase/admin";
3
4
  import { PageSchema } from "../schema/pages";
4
5
  import { SymbolSchema } from "../schema/symbols";
5
6
  import { CustomBlockSchema } from "../schema/customBlocks";
7
+ import { PublicSiteSettingsSchema } from "../schema/settings";
6
8
  import { localizePage, localizeBlocks } from "../i18n/overlay";
7
9
  function toPath(slugs) {
8
10
  const clean = (slugs ?? []).filter(Boolean);
@@ -82,7 +84,11 @@ async function listPublishedPages(siteId) {
82
84
  }
83
85
  return restQuery(siteId, [eq("status", "published")]);
84
86
  }
85
- async function getPublishedPageByPath(siteId, path) {
87
+ const getPublishedPageByPath = cache(getPublishedPageByPathUncached);
88
+ const getSiteSymbols = cache(getSiteSymbolsUncached);
89
+ const getSiteCustomBlocks = cache(getSiteCustomBlocksUncached);
90
+ const getPublicSiteSettings = cache(getPublicSiteSettingsUncached);
91
+ async function getPublishedPageByPathUncached(siteId, path) {
86
92
  const adb = getAdminDb();
87
93
  if (adb) {
88
94
  return safeAdminRead(null, `getPublishedPageByPath ${siteId}${path}`, async () => {
@@ -138,7 +144,7 @@ async function readCollection(siteId, collectionId) {
138
144
  return [];
139
145
  }
140
146
  }
141
- async function getSiteSymbols(siteId) {
147
+ async function getSiteSymbolsUncached(siteId) {
142
148
  const rows = await readCollection(siteId, "symbols");
143
149
  const out = [];
144
150
  for (const r of rows) {
@@ -173,7 +179,34 @@ async function getPublishedPageByPathExpanded(siteId, path, locale) {
173
179
  }
174
180
  return expandSymbols(page, symbols);
175
181
  }
176
- async function getSiteCustomBlocks(siteId) {
182
+ async function getPublicSiteSettingsUncached(siteId) {
183
+ const adb = getAdminDb();
184
+ let data = {};
185
+ if (adb) {
186
+ data = await safeAdminRead({}, `getPublicSiteSettings ${siteId}`, async () => {
187
+ const d = await adb.doc(`sites/${siteId}/settings/public`).get();
188
+ return d.exists ? d.data() : {};
189
+ });
190
+ } else {
191
+ const apiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;
192
+ const projectId = process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID;
193
+ if (apiKey && projectId) {
194
+ const url = `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents/sites/${siteId}/settings/public?key=${apiKey}`;
195
+ try {
196
+ const res = await fetch(url, { next: { revalidate: 300 } });
197
+ if (res.ok) {
198
+ const doc = await res.json();
199
+ data = decodeFields(doc.fields ?? {});
200
+ }
201
+ } catch (err) {
202
+ console.error("[xicms] Firestore REST-read van settings/public mislukt:", err);
203
+ }
204
+ }
205
+ }
206
+ const parsed = PublicSiteSettingsSchema.safeParse(data ?? {});
207
+ return parsed.success ? parsed.data : {};
208
+ }
209
+ async function getSiteCustomBlocksUncached(siteId) {
177
210
  const rows = await readCollection(siteId, "customBlocks");
178
211
  const out = [];
179
212
  for (const r of rows) {
@@ -187,6 +220,7 @@ export {
187
220
  expandSymbols,
188
221
  getDraftPageById,
189
222
  getDraftPageByPath,
223
+ getPublicSiteSettings,
190
224
  getPublishedPageByPath,
191
225
  getPublishedPageByPathExpanded,
192
226
  getSiteCustomBlocks,