@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.
- package/dist/XiCMSProvider.d.ts +11 -2
- package/dist/XiCMSProvider.js +10 -5
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/blockEditors/GenericEditor.js +18 -8
- package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
- package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
- package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
- package/dist/admin/lib/pagesClient.js +4 -1
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/lib/upload.js +14 -7
- package/dist/admin/lib/upload.js.map +1 -1
- package/dist/admin/media/MediaManager.js +60 -28
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/media/MediaPicker.js +25 -15
- package/dist/admin/media/MediaPicker.js.map +1 -1
- package/dist/admin/pages/PageEditor.d.ts +2 -1
- package/dist/admin/pages/PageEditor.js +16 -2
- package/dist/admin/pages/PageEditor.js.map +1 -1
- package/dist/admin/pages/PagesPanel.js +9 -5
- package/dist/admin/pages/PagesPanel.js.map +1 -1
- package/dist/admin/panelRegistry.js +3 -0
- package/dist/admin/panelRegistry.js.map +1 -1
- package/dist/admin/shop/CategoriesTab.d.ts +3 -0
- package/dist/admin/shop/CategoriesTab.js +140 -0
- package/dist/admin/shop/CategoriesTab.js.map +1 -0
- package/dist/admin/shop/ConnectionsTab.d.ts +9 -0
- package/dist/admin/shop/ConnectionsTab.js +158 -0
- package/dist/admin/shop/ConnectionsTab.js.map +1 -0
- package/dist/admin/shop/OrdersTab.d.ts +3 -0
- package/dist/admin/shop/OrdersTab.js +236 -0
- package/dist/admin/shop/OrdersTab.js.map +1 -0
- package/dist/admin/shop/ProductsTab.d.ts +3 -0
- package/dist/admin/shop/ProductsTab.js +397 -0
- package/dist/admin/shop/ProductsTab.js.map +1 -0
- package/dist/admin/shop/ShopPanel.d.ts +4 -0
- package/dist/admin/shop/ShopPanel.js +25 -0
- package/dist/admin/shop/ShopPanel.js.map +1 -0
- package/dist/admin/shop/lib.d.ts +16 -0
- package/dist/admin/shop/lib.js +46 -0
- package/dist/admin/shop/lib.js.map +1 -0
- package/dist/admin/visual/VisualEditor.js +11 -2
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/agentFiles/files.d.ts +15 -3
- package/dist/agentFiles/files.js +57 -0
- package/dist/agentFiles/files.js.map +1 -1
- package/dist/auth/AuthProvider.js +3 -3
- package/dist/auth/AuthProvider.js.map +1 -1
- package/dist/blocks/builtins/Columns.js +1 -1
- package/dist/blocks/builtins/Columns.js.map +1 -1
- package/dist/blocks/builtins/Gallery.js +2 -2
- package/dist/blocks/builtins/Gallery.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +1 -1
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/components/PageView.js +4 -3
- package/dist/components/PageView.js.map +1 -1
- package/dist/demo/index.d.ts +7 -0
- package/dist/demo/index.js +23 -0
- package/dist/demo/index.js.map +1 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.js +0 -20
- package/dist/index.js.map +1 -1
- package/dist/modules/builtins.d.ts +1 -0
- package/dist/modules/builtins.js +4 -0
- package/dist/modules/builtins.js.map +1 -1
- package/dist/modules/index.d.ts +4 -0
- package/dist/modules/index.js +12 -0
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/registry.d.ts +3 -4
- package/dist/modules/registry.js +2 -3
- package/dist/modules/registry.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.d.ts +2 -0
- package/dist/modules/shop/blocks/Cart.js +91 -0
- package/dist/modules/shop/blocks/Cart.js.map +1 -0
- package/dist/modules/shop/blocks/Checkout.d.ts +2 -0
- package/dist/modules/shop/blocks/Checkout.js +126 -0
- package/dist/modules/shop/blocks/Checkout.js.map +1 -0
- package/dist/modules/shop/blocks/Donation.d.ts +2 -0
- package/dist/modules/shop/blocks/Donation.js +145 -0
- package/dist/modules/shop/blocks/Donation.js.map +1 -0
- package/dist/modules/shop/blocks/OrderStatus.d.ts +2 -0
- package/dist/modules/shop/blocks/OrderStatus.js +40 -0
- package/dist/modules/shop/blocks/OrderStatus.js.map +1 -0
- package/dist/modules/shop/blocks/ProductDetail.d.ts +2 -0
- package/dist/modules/shop/blocks/ProductDetail.js +168 -0
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -0
- package/dist/modules/shop/blocks/ProductGrid.d.ts +8 -0
- package/dist/modules/shop/blocks/ProductGrid.js +136 -0
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -0
- package/dist/modules/shop/blocks/ShopCategories.d.ts +2 -0
- package/dist/modules/shop/blocks/ShopCategories.js +69 -0
- package/dist/modules/shop/blocks/ShopCategories.js.map +1 -0
- package/dist/modules/shop/cartStore.d.ts +22 -0
- package/dist/modules/shop/cartStore.js +85 -0
- package/dist/modules/shop/cartStore.js.map +1 -0
- package/dist/modules/shop/lib.d.ts +25 -0
- package/dist/modules/shop/lib.js +61 -0
- package/dist/modules/shop/lib.js.map +1 -0
- package/dist/modules/shop/module.d.ts +1 -0
- package/dist/modules/shop/module.js +39 -0
- package/dist/modules/shop/module.js.map +1 -0
- package/dist/modules/shop/settings.d.ts +41 -0
- package/dist/modules/shop/settings.js +18 -0
- package/dist/modules/shop/settings.js.map +1 -0
- package/dist/schema/blocks.d.ts +27 -12
- package/dist/schema/blocks.js +3 -0
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/settings.d.ts +1 -34
- package/dist/schema/settings.js +2 -8
- package/dist/schema/settings.js.map +1 -1
- package/dist/schema/shop.d.ts +939 -0
- package/dist/schema/shop.js +209 -0
- package/dist/schema/shop.js.map +1 -0
- package/dist/schema/siteConfig.d.ts +3 -71
- package/dist/schema/siteConfig.js +3 -8
- package/dist/schema/siteConfig.js.map +1 -1
- package/dist/schema/symbols.d.ts +2 -2
- package/dist/schema/symbols.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +23 -3
- package/dist/server/pages.js +37 -3
- package/dist/server/pages.js.map +1 -1
- package/dist/server/seo.js +5 -3
- package/dist/server/seo.js.map +1 -1
- package/dist/settings/useSiteSettings.d.ts +15 -3
- package/dist/settings/useSiteSettings.js +26 -5
- package/dist/settings/useSiteSettings.js.map +1 -1
- package/dist/styles.css +14 -0
- 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
|
-
|
|
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 *
|
|
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":[]}
|
package/dist/schema/symbols.d.ts
CHANGED
|
@@ -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
|
|
203
|
-
export type StoredSymbol =
|
|
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
|
|
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":[]}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/server/index.js
CHANGED
|
@@ -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,
|
package/dist/server/index.js.map
CHANGED
|
@@ -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":[]}
|
package/dist/server/pages.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
+
declare function getSiteCustomBlocksUncached(siteId: string): Promise<CustomBlock[]>;
|
|
56
|
+
export {};
|
package/dist/server/pages.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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,
|