includio-cms 0.26.0 → 0.27.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/API.md +42 -2
- package/CHANGELOG.md +65 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +8 -0
- package/dist/admin/auth-client.d.ts +42 -42
- package/dist/admin/client/admin/admin-layout.svelte +12 -2
- package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
- package/dist/admin/client/collection/data-table.svelte +0 -39
- package/dist/admin/client/collection/data-table.svelte.d.ts +0 -2
- package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
- package/dist/admin/client/shop/refund-dialog.svelte +37 -1
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shop-order-detail-page.svelte +107 -0
- package/dist/admin/components/fields/field-renderer.svelte +6 -1
- package/dist/admin/components/fields/icon-field.svelte +86 -0
- package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
- package/dist/admin/components/fields/object-field.svelte +27 -7
- package/dist/admin/components/fields/shop-field.svelte +210 -20
- package/dist/admin/components/layout/layout-tabs.svelte +1 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
- package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
- package/dist/admin/helpers/build-icon-set-map.js +16 -0
- package/dist/admin/helpers/index.d.ts +2 -0
- package/dist/admin/helpers/index.js +2 -0
- package/dist/admin/remote/shop.remote.d.ts +58 -24
- package/dist/admin/remote/shop.remote.js +61 -6
- package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
- package/dist/admin/state/icon-sets.svelte.js +20 -0
- package/dist/cli/scaffold/admin.js +2 -2
- package/dist/components/ui/checkbox/checkbox.svelte +3 -3
- package/dist/core/cms.d.ts +11 -2
- package/dist/core/cms.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.js +7 -0
- package/dist/core/server/generator/fields.d.ts +2 -0
- package/dist/core/server/generator/fields.js +34 -1
- package/dist/core/server/generator/generator.js +2 -1
- package/dist/db-postgres/schema/shop/order.d.ts +37 -1
- package/dist/db-postgres/schema/shop/order.js +3 -1
- package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
- package/dist/db-postgres/schema/shop/payment.js +4 -1
- package/dist/db-postgres/schema/shop/product.d.ts +20 -0
- package/dist/db-postgres/schema/shop/product.js +3 -1
- package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
- package/dist/db-postgres/schema/shop/productVariant.js +22 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/shop/cart/types.d.ts +1 -0
- package/dist/shop/client/index.d.ts +54 -0
- package/dist/shop/client/index.js +5 -1
- package/dist/shop/expiry.d.ts +35 -0
- package/dist/shop/expiry.js +68 -0
- package/dist/shop/http/balance-handler.d.ts +20 -0
- package/dist/shop/http/balance-handler.js +91 -0
- package/dist/shop/http/cart-handler.js +19 -0
- package/dist/shop/http/checkout-handler.js +19 -1
- package/dist/shop/http/index.d.ts +2 -0
- package/dist/shop/http/index.js +2 -0
- package/dist/shop/http/upcoming-handler.d.ts +16 -0
- package/dist/shop/http/upcoming-handler.js +65 -0
- package/dist/shop/http/webhook-handler.js +46 -9
- package/dist/shop/index.d.ts +4 -1
- package/dist/shop/index.js +7 -1
- package/dist/shop/server/balance-payment.d.ts +40 -0
- package/dist/shop/server/balance-payment.js +140 -0
- package/dist/shop/server/cart-hydrate.js +2 -0
- package/dist/shop/server/init.d.ts +14 -0
- package/dist/shop/server/init.js +35 -0
- package/dist/shop/server/orders.d.ts +34 -0
- package/dist/shop/server/orders.js +141 -2
- package/dist/shop/server/payment-policy.d.ts +35 -0
- package/dist/shop/server/payment-policy.js +55 -0
- package/dist/shop/server/payments.d.ts +29 -0
- package/dist/shop/server/payments.js +64 -0
- package/dist/shop/server/populate.d.ts +1 -1
- package/dist/shop/server/refund.d.ts +17 -12
- package/dist/shop/server/refund.js +96 -13
- package/dist/shop/server/shop-data.d.ts +4 -1
- package/dist/shop/server/shop-data.js +24 -2
- package/dist/shop/template.d.ts +13 -0
- package/dist/shop/template.js +98 -0
- package/dist/shop/types.d.ts +142 -1
- package/dist/shop/variant-attributes.d.ts +28 -0
- package/dist/shop/variant-attributes.js +69 -0
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/types/cms.d.ts +4 -3
- package/dist/types/cms.schema.d.ts +1 -1
- package/dist/types/cms.schema.js +9 -0
- package/dist/types/fields.d.ts +21 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/plugins.d.ts +40 -0
- package/dist/types/plugins.js +4 -1
- package/dist/updates/0.26.1/index.d.ts +2 -0
- package/dist/updates/0.26.1/index.js +19 -0
- package/dist/updates/0.27.0/index.d.ts +2 -0
- package/dist/updates/0.27.0/index.js +50 -0
- package/dist/updates/index.js +5 -1
- package/package.json +1 -1
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -8,6 +8,9 @@ export declare const getShopConfig: import("@sveltejs/kit").RemoteQueryFunction<
|
|
|
8
8
|
id: string;
|
|
9
9
|
label: import("../../shop/types.js").I18nText;
|
|
10
10
|
}[];
|
|
11
|
+
variantAttributes: Record<string, import("../../shop/types.js").VariantAttribute>;
|
|
12
|
+
variantLabel: import("../../shop/types.js").VariantLabelConfig | null;
|
|
13
|
+
variantExpiry: import("../../shop/types.js").VariantExpiryConfig | null;
|
|
11
14
|
} | null>;
|
|
12
15
|
export declare const listShopProductEntries: import("@sveltejs/kit").RemoteQueryFunction<void, import("../../shop/server/shop-data.js").ShopEntryListItem[]>;
|
|
13
16
|
export declare const getShopDataForEntry: import("@sveltejs/kit").RemoteQueryFunction<string, import("../../shop/server/shop-data.js").ShopDataWithVariants | null>;
|
|
@@ -18,6 +21,18 @@ export declare const upsertShopDataForEntry: import("@sveltejs/kit").RemoteComma
|
|
|
18
21
|
vatRate: number;
|
|
19
22
|
isActive?: boolean | undefined;
|
|
20
23
|
sortOrder?: number | null | undefined;
|
|
24
|
+
paymentPolicy?: {
|
|
25
|
+
type: "full";
|
|
26
|
+
} | {
|
|
27
|
+
type: "deposit";
|
|
28
|
+
depositAmount: {
|
|
29
|
+
type: "percent";
|
|
30
|
+
value: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: "amount";
|
|
33
|
+
value: number;
|
|
34
|
+
};
|
|
35
|
+
} | null | undefined;
|
|
21
36
|
};
|
|
22
37
|
variants?: {
|
|
23
38
|
id?: string | undefined;
|
|
@@ -25,7 +40,7 @@ export declare const upsertShopDataForEntry: import("@sveltejs/kit").RemoteComma
|
|
|
25
40
|
name?: Record<string, string> | null | undefined;
|
|
26
41
|
priceDelta?: number | undefined;
|
|
27
42
|
stock?: number | null | undefined;
|
|
28
|
-
attributes?: Record<string,
|
|
43
|
+
attributes?: Record<string, unknown> | null | undefined;
|
|
29
44
|
}[] | undefined;
|
|
30
45
|
}, Promise<import("../../shop/server/shop-data.js").ShopDataWithVariants>>;
|
|
31
46
|
export declare const deleteShopDataForEntry: import("@sveltejs/kit").RemoteCommand<string, Promise<{
|
|
@@ -84,13 +99,18 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
|
|
|
84
99
|
} | undefined, {
|
|
85
100
|
items: {
|
|
86
101
|
number: string;
|
|
102
|
+
currency: string;
|
|
103
|
+
consents: {
|
|
104
|
+
id: string;
|
|
105
|
+
accepted: boolean;
|
|
106
|
+
label: string;
|
|
107
|
+
}[] | null;
|
|
87
108
|
id: string;
|
|
88
109
|
status: import("../../shop/types.js").OrderStatus;
|
|
89
110
|
createdAt: Date;
|
|
90
111
|
updatedAt: Date;
|
|
91
112
|
language: string | null;
|
|
92
113
|
carrierType: string | null;
|
|
93
|
-
currency: string;
|
|
94
114
|
customerEmail: string;
|
|
95
115
|
customerName: string | null;
|
|
96
116
|
customerPhone: string | null;
|
|
@@ -108,13 +128,10 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
|
|
|
108
128
|
shipmentCreatedAt: Date | null;
|
|
109
129
|
paymentMethod: string | null;
|
|
110
130
|
paymentProviderRef: string | null;
|
|
111
|
-
consents: {
|
|
112
|
-
id: string;
|
|
113
|
-
accepted: boolean;
|
|
114
|
-
label: string;
|
|
115
|
-
}[] | null;
|
|
116
131
|
notes: string | null;
|
|
117
132
|
accessToken: string;
|
|
133
|
+
partialPayment: import("../../shop/types.js").PartialPayment | null;
|
|
134
|
+
balanceOwed: boolean;
|
|
118
135
|
}[];
|
|
119
136
|
total: number;
|
|
120
137
|
limit: number;
|
|
@@ -123,13 +140,18 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
|
|
|
123
140
|
export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
|
|
124
141
|
order: {
|
|
125
142
|
number: string;
|
|
143
|
+
currency: string;
|
|
144
|
+
consents: {
|
|
145
|
+
id: string;
|
|
146
|
+
accepted: boolean;
|
|
147
|
+
label: string;
|
|
148
|
+
}[] | null;
|
|
126
149
|
id: string;
|
|
127
150
|
status: import("../../shop/types.js").OrderStatus;
|
|
128
151
|
createdAt: Date;
|
|
129
152
|
updatedAt: Date;
|
|
130
153
|
language: string | null;
|
|
131
154
|
carrierType: string | null;
|
|
132
|
-
currency: string;
|
|
133
155
|
customerEmail: string;
|
|
134
156
|
customerName: string | null;
|
|
135
157
|
customerPhone: string | null;
|
|
@@ -147,13 +169,10 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
|
|
|
147
169
|
shipmentCreatedAt: Date | null;
|
|
148
170
|
paymentMethod: string | null;
|
|
149
171
|
paymentProviderRef: string | null;
|
|
150
|
-
consents: {
|
|
151
|
-
id: string;
|
|
152
|
-
accepted: boolean;
|
|
153
|
-
label: string;
|
|
154
|
-
}[] | null;
|
|
155
172
|
notes: string | null;
|
|
156
173
|
accessToken: string;
|
|
174
|
+
partialPayment: import("../../shop/types.js").PartialPayment | null;
|
|
175
|
+
balanceOwed: boolean;
|
|
157
176
|
};
|
|
158
177
|
items: {
|
|
159
178
|
id: string;
|
|
@@ -182,13 +201,18 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
|
|
|
182
201
|
note?: string | undefined;
|
|
183
202
|
}, Promise<{
|
|
184
203
|
number: string;
|
|
204
|
+
currency: string;
|
|
205
|
+
consents: {
|
|
206
|
+
id: string;
|
|
207
|
+
accepted: boolean;
|
|
208
|
+
label: string;
|
|
209
|
+
}[] | null;
|
|
185
210
|
id: string;
|
|
186
211
|
status: import("../../shop/types.js").OrderStatus;
|
|
187
212
|
createdAt: Date;
|
|
188
213
|
updatedAt: Date;
|
|
189
214
|
language: string | null;
|
|
190
215
|
carrierType: string | null;
|
|
191
|
-
currency: string;
|
|
192
216
|
customerEmail: string;
|
|
193
217
|
customerName: string | null;
|
|
194
218
|
customerPhone: string | null;
|
|
@@ -206,13 +230,10 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
|
|
|
206
230
|
shipmentCreatedAt: Date | null;
|
|
207
231
|
paymentMethod: string | null;
|
|
208
232
|
paymentProviderRef: string | null;
|
|
209
|
-
consents: {
|
|
210
|
-
id: string;
|
|
211
|
-
accepted: boolean;
|
|
212
|
-
label: string;
|
|
213
|
-
}[] | null;
|
|
214
233
|
notes: string | null;
|
|
215
234
|
accessToken: string;
|
|
235
|
+
partialPayment: import("../../shop/types.js").PartialPayment | null;
|
|
236
|
+
balanceOwed: boolean;
|
|
216
237
|
}>>;
|
|
217
238
|
export declare const resendOrderEmailCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
218
239
|
orderId: string;
|
|
@@ -253,15 +274,15 @@ export declare const listShopableCollections: import("@sveltejs/kit").RemoteQuer
|
|
|
253
274
|
}[]>;
|
|
254
275
|
export declare const getOrderRefundsAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
|
|
255
276
|
refunds: {
|
|
277
|
+
amount: number;
|
|
278
|
+
currency: string;
|
|
256
279
|
id: string;
|
|
257
280
|
status: import("../../db-postgres/schema/shop/index.js").ShopRefundStatus;
|
|
258
281
|
createdAt: Date;
|
|
259
282
|
updatedAt: Date;
|
|
260
|
-
currency: string;
|
|
261
283
|
orderId: string;
|
|
262
284
|
provider: string;
|
|
263
285
|
providerRef: string | null;
|
|
264
|
-
amount: number;
|
|
265
286
|
paymentId: string | null;
|
|
266
287
|
reason: string | null;
|
|
267
288
|
createdBy: string | null;
|
|
@@ -277,6 +298,8 @@ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
277
298
|
orderId: string;
|
|
278
299
|
amount?: number | undefined;
|
|
279
300
|
reason?: string | undefined;
|
|
301
|
+
kind?: "full" | "deposit" | "balance" | undefined;
|
|
302
|
+
releaseStock?: boolean | undefined;
|
|
280
303
|
}, Promise<{
|
|
281
304
|
refund: import("../../shop/server/refund.js").ShopRefundRow;
|
|
282
305
|
remainingRefundable: number;
|
|
@@ -286,9 +309,20 @@ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
286
309
|
error?: undefined;
|
|
287
310
|
} | {
|
|
288
311
|
success: false;
|
|
289
|
-
code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error";
|
|
312
|
+
code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error" | "no_payment_kind";
|
|
290
313
|
error: string;
|
|
291
314
|
}>>;
|
|
315
|
+
export declare const generateBalanceLinkForOrder: import("@sveltejs/kit").RemoteCommand<string, Promise<{
|
|
316
|
+
success: false;
|
|
317
|
+
error: string;
|
|
318
|
+
url?: undefined;
|
|
319
|
+
balanceAmount?: undefined;
|
|
320
|
+
} | {
|
|
321
|
+
success: true;
|
|
322
|
+
url: string;
|
|
323
|
+
balanceAmount: number;
|
|
324
|
+
error?: undefined;
|
|
325
|
+
}>>;
|
|
292
326
|
export declare const listCouponsAdmin: import("@sveltejs/kit").RemoteQueryFunction<void, {
|
|
293
327
|
id: string;
|
|
294
328
|
code: string;
|
|
@@ -317,7 +351,7 @@ export declare const getCouponAdmin: import("@sveltejs/kit").RemoteQueryFunction
|
|
|
317
351
|
}>;
|
|
318
352
|
export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
319
353
|
code: string;
|
|
320
|
-
type: "
|
|
354
|
+
type: "percent" | "fixed";
|
|
321
355
|
value: number;
|
|
322
356
|
minOrderAmount?: number | null | undefined;
|
|
323
357
|
maxUses?: number | null | undefined;
|
|
@@ -340,7 +374,7 @@ export declare const updateCouponCmd: import("@sveltejs/kit").RemoteCommand<{
|
|
|
340
374
|
id: string;
|
|
341
375
|
input: {
|
|
342
376
|
code?: string | undefined;
|
|
343
|
-
type?: "
|
|
377
|
+
type?: "percent" | "fixed" | undefined;
|
|
344
378
|
value?: number | undefined;
|
|
345
379
|
minOrderAmount?: number | null | undefined;
|
|
346
380
|
maxUses?: number | null | undefined;
|
|
@@ -24,7 +24,10 @@ export const getShopConfig = query(async () => {
|
|
|
24
24
|
vatRates: shop.vatRates,
|
|
25
25
|
features: shop.features,
|
|
26
26
|
languages: getCMS().languages,
|
|
27
|
-
paymentMethods: shop.payment.map((p) => ({ id: p.id, label: p.label }))
|
|
27
|
+
paymentMethods: shop.payment.map((p) => ({ id: p.id, label: p.label })),
|
|
28
|
+
variantAttributes: shop.variantAttributes,
|
|
29
|
+
variantLabel: shop.variantLabel,
|
|
30
|
+
variantExpiry: shop.variantExpiry
|
|
28
31
|
};
|
|
29
32
|
});
|
|
30
33
|
export const listShopProductEntries = query(async () => {
|
|
@@ -35,11 +38,20 @@ export const getShopDataForEntry = query(z.string(), async (entryId) => {
|
|
|
35
38
|
requireAuth();
|
|
36
39
|
return getShopDataByEntry(entryId);
|
|
37
40
|
});
|
|
41
|
+
const depositAmountSchema = z.discriminatedUnion('type', [
|
|
42
|
+
z.object({ type: z.literal('percent'), value: z.number().positive().max(100) }),
|
|
43
|
+
z.object({ type: z.literal('amount'), value: z.number().int().positive() })
|
|
44
|
+
]);
|
|
45
|
+
const paymentPolicySchema = z.discriminatedUnion('type', [
|
|
46
|
+
z.object({ type: z.literal('full') }),
|
|
47
|
+
z.object({ type: z.literal('deposit'), depositAmount: depositAmountSchema })
|
|
48
|
+
]);
|
|
38
49
|
const shopDataInputSchema = z.object({
|
|
39
50
|
basePrice: z.number().nonnegative().max(1e9), // PLN (≤6dp)
|
|
40
51
|
vatRate: z.number().int().min(0).max(100),
|
|
41
52
|
isActive: z.boolean().optional(),
|
|
42
|
-
sortOrder: z.number().int().nullable().optional()
|
|
53
|
+
sortOrder: z.number().int().nullable().optional(),
|
|
54
|
+
paymentPolicy: paymentPolicySchema.nullable().optional()
|
|
43
55
|
});
|
|
44
56
|
const variantInputSchema = z.object({
|
|
45
57
|
id: z.string().optional(),
|
|
@@ -47,7 +59,9 @@ const variantInputSchema = z.object({
|
|
|
47
59
|
name: z.record(z.string(), z.string()).nullable().optional(),
|
|
48
60
|
priceDelta: z.number().optional(), // PLN
|
|
49
61
|
stock: z.number().int().nullable().optional(),
|
|
50
|
-
|
|
62
|
+
// unknown — variantAttributes can be string|number|boolean|datetime;
|
|
63
|
+
// validateVariantAttributes (server) enforces the typed shape from shop config.
|
|
64
|
+
attributes: z.record(z.string(), z.unknown()).nullable().optional()
|
|
51
65
|
});
|
|
52
66
|
export const upsertShopDataForEntry = command(z.object({
|
|
53
67
|
entryId: z.string(),
|
|
@@ -223,11 +237,20 @@ export const getOrderRefundsAdmin = query(z.string(), async (orderId) => {
|
|
|
223
237
|
export const refundOrderCmd = command(z.object({
|
|
224
238
|
orderId: z.string(),
|
|
225
239
|
amount: z.number().int().positive().optional(),
|
|
226
|
-
reason: z.string().max(500).optional()
|
|
227
|
-
|
|
240
|
+
reason: z.string().max(500).optional(),
|
|
241
|
+
kind: z.enum(['full', 'deposit', 'balance']).optional(),
|
|
242
|
+
releaseStock: z.boolean().optional()
|
|
243
|
+
}), async ({ orderId, amount, reason, kind, releaseStock }) => {
|
|
228
244
|
requireAuth();
|
|
229
245
|
try {
|
|
230
|
-
const result = await refundOrder({
|
|
246
|
+
const result = await refundOrder({
|
|
247
|
+
orderId,
|
|
248
|
+
amount,
|
|
249
|
+
reason,
|
|
250
|
+
kind,
|
|
251
|
+
releaseStock,
|
|
252
|
+
createdBy: 'admin'
|
|
253
|
+
});
|
|
231
254
|
return { success: true, ...result };
|
|
232
255
|
}
|
|
233
256
|
catch (err) {
|
|
@@ -238,6 +261,38 @@ export const refundOrderCmd = command(z.object({
|
|
|
238
261
|
return { success: false, code: 'provider_error', error: message };
|
|
239
262
|
}
|
|
240
263
|
});
|
|
264
|
+
export const generateBalanceLinkForOrder = command(z.string(), async (orderId) => {
|
|
265
|
+
requireAuth();
|
|
266
|
+
const { generateBalanceToken, requireBalanceTokenSecret } = await import('../../shop/server/balance-payment.js');
|
|
267
|
+
const { getOrderById } = await import('../../shop/server/orders.js');
|
|
268
|
+
const order = await getOrderById(orderId);
|
|
269
|
+
if (!order)
|
|
270
|
+
return { success: false, error: 'Order not found' };
|
|
271
|
+
if (!order.balanceOwed) {
|
|
272
|
+
return { success: false, error: 'Order has no outstanding balance' };
|
|
273
|
+
}
|
|
274
|
+
let secret;
|
|
275
|
+
try {
|
|
276
|
+
secret = requireBalanceTokenSecret();
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
const message = err instanceof Error ? err.message : 'Token secret not configured';
|
|
280
|
+
return { success: false, error: message };
|
|
281
|
+
}
|
|
282
|
+
const token = generateBalanceToken(order.id, secret);
|
|
283
|
+
// Mirror orderViewUrl template — build the customer-facing URL by
|
|
284
|
+
// extending the configured base with the balance path + token.
|
|
285
|
+
const cms = (await import('../../core/cms.js')).getCMS();
|
|
286
|
+
const orderViewUrl = cms.shopConfig?.orderViewUrl ?? '/shop/order/{orderNumber}?token={accessToken}';
|
|
287
|
+
const base = orderViewUrl
|
|
288
|
+
.replace('{orderNumber}', encodeURIComponent(order.number))
|
|
289
|
+
.replace('{orderId}', encodeURIComponent(order.id))
|
|
290
|
+
.replace('{accessToken}', encodeURIComponent(order.accessToken))
|
|
291
|
+
.replace('{language}', encodeURIComponent(order.language ?? ''));
|
|
292
|
+
const sep = base.includes('?') ? '&' : '?';
|
|
293
|
+
const url = `${base}${sep}balance=1&balanceToken=${encodeURIComponent(token)}`;
|
|
294
|
+
return { success: true, url, balanceAmount: order.partialPayment?.balanceAmount ?? 0 };
|
|
295
|
+
});
|
|
241
296
|
// Coupons ────────────────────────────────────────────────────────────────────
|
|
242
297
|
const couponInputSchema = z.object({
|
|
243
298
|
code: z.string().min(1).max(64),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IconSetPlugin } from '../../types/plugins.js';
|
|
2
|
+
export declare function setIconSets(sets: Map<string, IconSetPlugin>): void;
|
|
3
|
+
export declare function getIconSets(): Map<string, IconSetPlugin>;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a single icon set: explicit `slug` (from {@link IconField.set}) takes
|
|
6
|
+
* precedence; otherwise returns the first registered set (predictable order
|
|
7
|
+
* matches Map insertion). Returns `null` when none are registered.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveIconSet(slug?: string): IconSetPlugin | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const contextKey = Symbol('iconSets');
|
|
3
|
+
export function setIconSets(sets) {
|
|
4
|
+
setContext(contextKey, sets);
|
|
5
|
+
}
|
|
6
|
+
export function getIconSets() {
|
|
7
|
+
return getContext(contextKey) ?? new Map();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a single icon set: explicit `slug` (from {@link IconField.set}) takes
|
|
11
|
+
* precedence; otherwise returns the first registered set (predictable order
|
|
12
|
+
* matches Map insertion). Returns `null` when none are registered.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveIconSet(slug) {
|
|
15
|
+
const sets = getIconSets();
|
|
16
|
+
if (slug)
|
|
17
|
+
return sets.get(slug) ?? null;
|
|
18
|
+
const first = sets.values().next();
|
|
19
|
+
return first.done ? null : first.value;
|
|
20
|
+
}
|
|
@@ -329,7 +329,7 @@ export const { POST } = createRetryPaymentHandler();
|
|
|
329
329
|
{
|
|
330
330
|
path: 'admin/api/[...path]/+server.ts',
|
|
331
331
|
content: `${GENERATED_COMMENT_TS}
|
|
332
|
-
import { createAdminApiHandler } from 'includio-cms/
|
|
332
|
+
import { createAdminApiHandler } from 'includio-cms/sveltekit/server';
|
|
333
333
|
|
|
334
334
|
export const { GET, POST, PATCH, PUT, DELETE } = createAdminApiHandler();
|
|
335
335
|
`
|
|
@@ -337,7 +337,7 @@ export const { GET, POST, PATCH, PUT, DELETE } = createAdminApiHandler();
|
|
|
337
337
|
{
|
|
338
338
|
path: 'admin/api/rest/[...restPath]/+server.ts',
|
|
339
339
|
content: `${GENERATED_COMMENT_TS}
|
|
340
|
-
import { createRestApiHandler } from 'includio-cms/
|
|
340
|
+
import { createRestApiHandler } from 'includio-cms/sveltekit/server';
|
|
341
341
|
|
|
342
342
|
export const { GET, POST, PUT, DELETE } = createRestApiHandler();
|
|
343
343
|
`
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
bind:ref
|
|
18
18
|
data-slot="checkbox"
|
|
19
19
|
class={cn(
|
|
20
|
-
"border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer flex size-
|
|
20
|
+
"border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] after:absolute after:-inset-1 after:content-[''] disabled:cursor-not-allowed disabled:opacity-50",
|
|
21
21
|
className
|
|
22
22
|
)}
|
|
23
23
|
bind:checked
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
{#snippet children({ checked, indeterminate })}
|
|
28
28
|
<div data-slot="checkbox-indicator" class="text-current transition-none">
|
|
29
29
|
{#if checked}
|
|
30
|
-
<CheckIcon class="size-
|
|
30
|
+
<CheckIcon class="size-3.5" />
|
|
31
31
|
{:else if indeterminate}
|
|
32
|
-
<MinusIcon class="size-
|
|
32
|
+
<MinusIcon class="size-3.5" />
|
|
33
33
|
{/if}
|
|
34
34
|
</div>
|
|
35
35
|
{/snippet}
|
package/dist/core/cms.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ApiKeyConfig, AuthConfig, CMSConfig, ICMS, MediaConfig, Typography
|
|
|
4
4
|
import type { CollectionConfigWithType } from '../types/collections.js';
|
|
5
5
|
import type { Language } from '../types/languages.js';
|
|
6
6
|
import type { SingleConfigWithType } from '../types/singles.js';
|
|
7
|
-
import type { CustomFieldDefinition,
|
|
7
|
+
import type { CustomFieldDefinition, IconSetPlugin, Plugin } from '../types/plugins.js';
|
|
8
8
|
import type { FormConfig } from '../types/forms.js';
|
|
9
9
|
import type { AIAdapter } from '../types/adapters/ai.js';
|
|
10
10
|
import type { EmailAdapter } from '../types/adapters/email.js';
|
|
@@ -28,8 +28,17 @@ export declare class CMS implements ICMS {
|
|
|
28
28
|
sidebarHelp: boolean;
|
|
29
29
|
shopConfig: ResolvedShopConfig | null;
|
|
30
30
|
cmpConfig: ResolvedCmpConfig | null;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Resolves once the shop's variant-attribute GIN indexes have been applied
|
|
33
|
+
* by `initCMS()`. `null` when the CMS is configured without a shop. Tests
|
|
34
|
+
* can `await` this to gate on init completion; production callers don't
|
|
35
|
+
* need to — index application is idempotent and non-blocking.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
shopInitPromise: Promise<void> | null;
|
|
39
|
+
plugins: Plugin[];
|
|
32
40
|
customFields: Map<string, CustomFieldDefinition>;
|
|
41
|
+
iconSets: Map<string, IconSetPlugin>;
|
|
33
42
|
apiKeys: ApiKeyConfig[];
|
|
34
43
|
constructor(config: CMSConfig);
|
|
35
44
|
private validateFieldSlugs;
|
package/dist/core/cms.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { isIconSetPlugin } from '../types/plugins.js';
|
|
1
2
|
import { setSchemaGetCMS } from './fields/fieldSchemaToTs.js';
|
|
2
3
|
import { betterAuth } from 'better-auth';
|
|
3
4
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
|
4
5
|
import { admin } from 'better-auth/plugins';
|
|
5
6
|
import { resetPasswordEmailTemplate } from '../admin/email/reset-password-template.js';
|
|
6
7
|
import * as authSchema from '../server/db/schema/auth-schema.js';
|
|
8
|
+
import { applyVariantAttributeIndexes } from '../shop/server/init.js';
|
|
7
9
|
export class CMS {
|
|
8
10
|
config;
|
|
9
11
|
databaseAdapter;
|
|
@@ -21,8 +23,17 @@ export class CMS {
|
|
|
21
23
|
sidebarHelp;
|
|
22
24
|
shopConfig;
|
|
23
25
|
cmpConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves once the shop's variant-attribute GIN indexes have been applied
|
|
28
|
+
* by `initCMS()`. `null` when the CMS is configured without a shop. Tests
|
|
29
|
+
* can `await` this to gate on init completion; production callers don't
|
|
30
|
+
* need to — index application is idempotent and non-blocking.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
shopInitPromise = null;
|
|
24
34
|
plugins = [];
|
|
25
35
|
customFields = new Map();
|
|
36
|
+
iconSets = new Map();
|
|
26
37
|
apiKeys = [];
|
|
27
38
|
constructor(config) {
|
|
28
39
|
this.config = config;
|
|
@@ -64,6 +75,13 @@ export class CMS {
|
|
|
64
75
|
if (config.plugins) {
|
|
65
76
|
this.plugins = config.plugins;
|
|
66
77
|
for (const plugin of this.plugins) {
|
|
78
|
+
if (isIconSetPlugin(plugin)) {
|
|
79
|
+
if (this.iconSets.has(plugin.slug)) {
|
|
80
|
+
throw new Error(`Duplicate icon-set plugin slug: "${plugin.slug}"`);
|
|
81
|
+
}
|
|
82
|
+
this.iconSets.set(plugin.slug, plugin);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
67
85
|
for (const def of plugin.fields ?? []) {
|
|
68
86
|
if (this.customFields.has(def.fieldType)) {
|
|
69
87
|
throw new Error(`Duplicate custom field type: "${def.fieldType}" (plugin: "${plugin.slug}")`);
|
|
@@ -157,6 +175,17 @@ export function initCMS(config) {
|
|
|
157
175
|
import('./server/media/operations/backgroundMaintenance.js')
|
|
158
176
|
.then((m) => m.startBackgroundMaintenance())
|
|
159
177
|
.catch((e) => console.warn('[cms] Failed to start background maintenance:', e));
|
|
178
|
+
// Apply shop variantAttribute GIN indexes (idempotent CREATE INDEX IF NOT EXISTS).
|
|
179
|
+
// Pass shop + drizzle explicitly so the dynamic import doesn't depend on a
|
|
180
|
+
// shared CMS singleton (vitest can give the dynamic module a fresh instance).
|
|
181
|
+
if (cms.shopConfig) {
|
|
182
|
+
const drizzle = cms.databaseAdapter._drizzle;
|
|
183
|
+
if (drizzle) {
|
|
184
|
+
cms.shopInitPromise = applyVariantAttributeIndexes(cms.shopConfig, drizzle).catch((e) => {
|
|
185
|
+
console.warn('[shop] Failed to apply variant attribute indexes:', e);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
160
189
|
return cms;
|
|
161
190
|
}
|
|
162
191
|
/**
|
|
@@ -444,6 +444,13 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
444
444
|
localizedDefault.text = emptyLangMap;
|
|
445
445
|
return localizedSchema.optional().default(localizedDefault);
|
|
446
446
|
}
|
|
447
|
+
case 'icon': {
|
|
448
|
+
// Value is a plain string key referencing an icon registered by an
|
|
449
|
+
// IconSetPlugin. Validation matches `slug` field semantics.
|
|
450
|
+
if (field.required)
|
|
451
|
+
return z.string().min(1, { message: msg.required });
|
|
452
|
+
return z.string().optional().default('');
|
|
453
|
+
}
|
|
447
454
|
case 'custom': {
|
|
448
455
|
const customDef = getCustomFieldDef(field.fieldType);
|
|
449
456
|
if (!customDef)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Field } from '../../../types/fields.js';
|
|
2
2
|
import type { CustomFieldDefinition } from '../../../types/plugins.js';
|
|
3
|
+
import type { VariantAttribute } from '../../../shop/types.js';
|
|
3
4
|
export declare function setGeneratorCustomFields(customFields: Map<string, CustomFieldDefinition>): void;
|
|
5
|
+
export declare function setGeneratorShopVariantAttributes(attrs: Record<string, VariantAttribute>): void;
|
|
4
6
|
export declare function generateTsTypeFromFields(fields: Field[]): string;
|
|
5
7
|
export declare function generateFlatTsTypeFromFields(fields: Field[]): string;
|
|
6
8
|
export interface InlineBlockTypeDef {
|
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
import { toPascalCase, quoteKey } from './utils.js';
|
|
2
2
|
import { buildSeoTsType } from '../../fields/seoFieldDescriptor.js';
|
|
3
3
|
let _customFields = new Map();
|
|
4
|
+
let _shopVariantAttributes = {};
|
|
4
5
|
function isGuaranteed(f) {
|
|
5
6
|
return !!(f.required || f.type === 'seo' || f.defaultValue !== undefined);
|
|
6
7
|
}
|
|
7
8
|
export function setGeneratorCustomFields(customFields) {
|
|
8
9
|
_customFields = customFields;
|
|
9
10
|
}
|
|
11
|
+
export function setGeneratorShopVariantAttributes(attrs) {
|
|
12
|
+
_shopVariantAttributes = attrs;
|
|
13
|
+
}
|
|
14
|
+
function variantAttributeTsType(attr) {
|
|
15
|
+
switch (attr.type) {
|
|
16
|
+
case 'text':
|
|
17
|
+
case 'datetime':
|
|
18
|
+
case 'image':
|
|
19
|
+
case 'entry':
|
|
20
|
+
case 'slug':
|
|
21
|
+
return 'string';
|
|
22
|
+
case 'number':
|
|
23
|
+
return 'number';
|
|
24
|
+
case 'boolean':
|
|
25
|
+
return 'boolean';
|
|
26
|
+
case 'select':
|
|
27
|
+
return attr.options.map((o) => `'${o.value}'`).join(' | ');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function buildShopAttributesType() {
|
|
31
|
+
const entries = Object.entries(_shopVariantAttributes);
|
|
32
|
+
if (entries.length === 0)
|
|
33
|
+
return 'Record<string, string> | null';
|
|
34
|
+
const fields = entries
|
|
35
|
+
.map(([key, attr]) => {
|
|
36
|
+
const opt = attr.required ? '' : '?';
|
|
37
|
+
return `${quoteKey(key)}${opt}: ${variantAttributeTsType(attr)}`;
|
|
38
|
+
})
|
|
39
|
+
.join('; ');
|
|
40
|
+
return `{ ${fields} }`;
|
|
41
|
+
}
|
|
10
42
|
function getFieldTypeAsString(field) {
|
|
11
43
|
switch (field.type) {
|
|
12
44
|
case 'text':
|
|
@@ -68,6 +100,7 @@ function getFieldTypeAsString(field) {
|
|
|
68
100
|
return 'UrlFieldData[]';
|
|
69
101
|
}
|
|
70
102
|
case 'slug':
|
|
103
|
+
case 'icon':
|
|
71
104
|
return 'string';
|
|
72
105
|
case 'seo':
|
|
73
106
|
return buildSeoTsType();
|
|
@@ -82,7 +115,7 @@ function getFieldTypeAsString(field) {
|
|
|
82
115
|
name: Record<string, string> | null;
|
|
83
116
|
priceDelta: number;
|
|
84
117
|
stock: number | null;
|
|
85
|
-
attributes:
|
|
118
|
+
attributes: ${buildShopAttributesType()};
|
|
86
119
|
}>;
|
|
87
120
|
} | null`;
|
|
88
121
|
case 'url': {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { generateTsTypeFromFields, generateFlatTsTypeFromFields, generateInlineBlockTypeString, setGeneratorCustomFields } from './fields.js';
|
|
3
|
+
import { generateTsTypeFromFields, generateFlatTsTypeFromFields, generateInlineBlockTypeString, setGeneratorCustomFields, setGeneratorShopVariantAttributes } from './fields.js';
|
|
4
4
|
import { generateTsTypeFromFormFields } from './formFields.js';
|
|
5
5
|
import { generateZodSchemaStringFromFormFieldsAsString } from './formFieldSchemaToString.js';
|
|
6
6
|
import { toPascalCase, quoteKey } from './utils.js';
|
|
@@ -312,6 +312,7 @@ export function generateRuntime(config) {
|
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
setGeneratorCustomFields(customFields);
|
|
315
|
+
setGeneratorShopVariantAttributes(config.shop?.variantAttributes ?? {});
|
|
315
316
|
createCmsRuntimeDir();
|
|
316
317
|
generateTypes(config);
|
|
317
318
|
generateAPI(config);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OrderStatus } from '../../../shop/types.js';
|
|
1
|
+
import type { OrderStatus, PartialPayment } from '../../../shop/types.js';
|
|
2
2
|
export declare const shopOrdersTable: import("drizzle-orm/pg-core/table", { with: { "resolution-mode": "require" } }).PgTableWithColumns<{
|
|
3
3
|
name: "shop_orders";
|
|
4
4
|
schema: undefined;
|
|
@@ -459,6 +459,42 @@ export declare const shopOrdersTable: import("drizzle-orm/pg-core/table", { with
|
|
|
459
459
|
identity: undefined;
|
|
460
460
|
generated: undefined;
|
|
461
461
|
}, {}, {}>;
|
|
462
|
+
partialPayment: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
463
|
+
name: "partial_payment";
|
|
464
|
+
tableName: "shop_orders";
|
|
465
|
+
dataType: "json";
|
|
466
|
+
columnType: "PgJsonb";
|
|
467
|
+
data: PartialPayment | null;
|
|
468
|
+
driverParam: unknown;
|
|
469
|
+
notNull: false;
|
|
470
|
+
hasDefault: false;
|
|
471
|
+
isPrimaryKey: false;
|
|
472
|
+
isAutoincrement: false;
|
|
473
|
+
hasRuntimeDefault: false;
|
|
474
|
+
enumValues: undefined;
|
|
475
|
+
baseColumn: never;
|
|
476
|
+
identity: undefined;
|
|
477
|
+
generated: undefined;
|
|
478
|
+
}, {}, {
|
|
479
|
+
$type: PartialPayment | null;
|
|
480
|
+
}>;
|
|
481
|
+
balanceOwed: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
482
|
+
name: "balance_owed";
|
|
483
|
+
tableName: "shop_orders";
|
|
484
|
+
dataType: "boolean";
|
|
485
|
+
columnType: "PgBoolean";
|
|
486
|
+
data: boolean;
|
|
487
|
+
driverParam: boolean;
|
|
488
|
+
notNull: true;
|
|
489
|
+
hasDefault: true;
|
|
490
|
+
isPrimaryKey: false;
|
|
491
|
+
isAutoincrement: false;
|
|
492
|
+
hasRuntimeDefault: false;
|
|
493
|
+
enumValues: undefined;
|
|
494
|
+
baseColumn: never;
|
|
495
|
+
identity: undefined;
|
|
496
|
+
generated: undefined;
|
|
497
|
+
}, {}, {}>;
|
|
462
498
|
createdAt: import("drizzle-orm/pg-core", { with: { "resolution-mode": "require" } }).PgColumn<{
|
|
463
499
|
name: "created_at";
|
|
464
500
|
tableName: "shop_orders";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { integer, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
1
|
+
import { boolean, integer, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
2
2
|
import { shopShippingMethodsTable } from './shippingMethod.js';
|
|
3
3
|
export const shopOrdersTable = pgTable('shop_orders', {
|
|
4
4
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
@@ -29,6 +29,8 @@ export const shopOrdersTable = pgTable('shop_orders', {
|
|
|
29
29
|
notes: text('notes'),
|
|
30
30
|
language: text('language'),
|
|
31
31
|
accessToken: uuid('access_token').defaultRandom().notNull(),
|
|
32
|
+
partialPayment: jsonb('partial_payment').$type(),
|
|
33
|
+
balanceOwed: boolean('balance_owed').default(false).notNull(),
|
|
32
34
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
33
35
|
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
|
|
34
36
|
});
|