includio-cms 0.27.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/API.md +58 -14
  2. package/CHANGELOG.md +59 -0
  3. package/DOCS.md +1 -1
  4. package/ROADMAP.md +1 -0
  5. package/dist/admin/api/handler.js +4 -0
  6. package/dist/admin/api/integrations.d.ts +13 -0
  7. package/dist/admin/api/integrations.js +61 -0
  8. package/dist/admin/api/test-email.d.ts +9 -0
  9. package/dist/admin/api/test-email.js +39 -0
  10. package/dist/admin/auth-client.d.ts +543 -543
  11. package/dist/admin/client/index.d.ts +10 -0
  12. package/dist/admin/client/index.js +12 -0
  13. package/dist/admin/client/maintenance/maintenance-page.svelte +210 -0
  14. package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
  15. package/dist/admin/client/shop/restore-order-cell.svelte +29 -0
  16. package/dist/admin/client/shop/restore-order-cell.svelte.d.ts +8 -0
  17. package/dist/admin/client/shop/shop-order-detail-page.svelte +156 -1
  18. package/dist/admin/client/shop/shop-orders-list-page.svelte +113 -53
  19. package/dist/admin/components/layout/app-sidebar.svelte +2 -0
  20. package/dist/admin/components/layout/nav-custom.svelte +26 -0
  21. package/dist/admin/components/layout/nav-custom.svelte.d.ts +3 -0
  22. package/dist/admin/components/layout/page-header.svelte +13 -3
  23. package/dist/admin/components/layout/page-header.svelte.d.ts +13 -3
  24. package/dist/admin/remote/admin.remote.d.ts +7 -0
  25. package/dist/admin/remote/admin.remote.js +10 -0
  26. package/dist/admin/remote/entry.remote.d.ts +2 -2
  27. package/dist/admin/remote/index.d.ts +1 -0
  28. package/dist/admin/remote/index.js +1 -0
  29. package/dist/admin/remote/invite.d.ts +1 -1
  30. package/dist/admin/remote/shop.remote.d.ts +125 -40
  31. package/dist/admin/remote/shop.remote.js +59 -10
  32. package/dist/admin/types.d.ts +15 -0
  33. package/dist/admin/utils/csv-export.d.ts +45 -0
  34. package/dist/admin/utils/csv-export.js +61 -0
  35. package/dist/cli/scaffold/admin.js +1 -1
  36. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  37. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  38. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  39. package/dist/core/cms.d.ts +44 -2
  40. package/dist/core/cms.js +64 -0
  41. package/dist/core/index.d.ts +2 -4
  42. package/dist/core/index.js +1 -4
  43. package/dist/core/server/index.d.ts +4 -1
  44. package/dist/core/server/index.js +4 -1
  45. package/dist/db-postgres/schema/shop/index.d.ts +1 -0
  46. package/dist/db-postgres/schema/shop/index.js +1 -0
  47. package/dist/db-postgres/schema/shop/invoice.d.ts +254 -0
  48. package/dist/db-postgres/schema/shop/invoice.js +27 -0
  49. package/dist/db-postgres/schema/shop/order.d.ts +104 -0
  50. package/dist/db-postgres/schema/shop/order.js +8 -0
  51. package/dist/shop/adapters/fakturownia/client.d.ts +33 -0
  52. package/dist/shop/adapters/fakturownia/client.js +87 -0
  53. package/dist/shop/adapters/fakturownia/index.d.ts +27 -0
  54. package/dist/shop/adapters/fakturownia/index.js +47 -0
  55. package/dist/shop/adapters/fakturownia/payload.d.ts +35 -0
  56. package/dist/shop/adapters/fakturownia/payload.js +45 -0
  57. package/dist/shop/adapters/payu/index.js +11 -0
  58. package/dist/shop/client/index.d.ts +7 -0
  59. package/dist/shop/http/checkout-handler.js +11 -0
  60. package/dist/shop/index.d.ts +4 -1
  61. package/dist/shop/index.js +3 -0
  62. package/dist/shop/nip.d.ts +12 -0
  63. package/dist/shop/nip.js +23 -0
  64. package/dist/shop/server/coupons.d.ts +10 -0
  65. package/dist/shop/server/coupons.js +19 -0
  66. package/dist/shop/server/email.d.ts +7 -3
  67. package/dist/shop/server/email.js +86 -112
  68. package/dist/shop/server/emailTemplateRegistry.d.ts +47 -0
  69. package/dist/shop/server/emailTemplateRegistry.js +288 -0
  70. package/dist/shop/server/invoices.d.ts +64 -0
  71. package/dist/shop/server/invoices.js +237 -0
  72. package/dist/shop/server/orders.d.ts +64 -1
  73. package/dist/shop/server/orders.js +155 -15
  74. package/dist/shop/templates/_partials/footer.en.html +4 -0
  75. package/dist/shop/templates/_partials/footer.pl.html +4 -0
  76. package/dist/shop/templates/_partials/header.en.html +4 -0
  77. package/dist/shop/templates/_partials/header.pl.html +4 -0
  78. package/dist/shop/templates/_partials/items.en.html +14 -0
  79. package/dist/shop/templates/_partials/items.pl.html +14 -0
  80. package/dist/shop/templates/_partials/tracking.en.html +7 -0
  81. package/dist/shop/templates/_partials/tracking.pl.html +7 -0
  82. package/dist/shop/templates/awaiting-payment.en.html +6 -0
  83. package/dist/shop/templates/awaiting-payment.pl.html +6 -0
  84. package/dist/shop/templates/cancelled.en.html +6 -0
  85. package/dist/shop/templates/cancelled.pl.html +6 -0
  86. package/dist/shop/templates/low-stock.en.html +14 -0
  87. package/dist/shop/templates/low-stock.pl.html +14 -0
  88. package/dist/shop/templates/order-completed.en.html +6 -0
  89. package/dist/shop/templates/order-completed.pl.html +6 -0
  90. package/dist/shop/templates/order-received.en.html +7 -0
  91. package/dist/shop/templates/order-received.pl.html +7 -0
  92. package/dist/shop/templates/payment-received.en.html +7 -0
  93. package/dist/shop/templates/payment-received.pl.html +7 -0
  94. package/dist/shop/templates/payment-rejected.en.html +6 -0
  95. package/dist/shop/templates/payment-rejected.pl.html +6 -0
  96. package/dist/shop/templates/preparing.en.html +7 -0
  97. package/dist/shop/templates/preparing.pl.html +7 -0
  98. package/dist/shop/templates/refunded.en.html +6 -0
  99. package/dist/shop/templates/refunded.pl.html +6 -0
  100. package/dist/shop/templates/shipped.en.html +7 -0
  101. package/dist/shop/templates/shipped.pl.html +7 -0
  102. package/dist/shop/types.d.ts +130 -1
  103. package/dist/sveltekit/index.d.ts +0 -1
  104. package/dist/sveltekit/index.js +0 -1
  105. package/dist/sveltekit/server/index.d.ts +1 -0
  106. package/dist/sveltekit/server/index.js +1 -0
  107. package/dist/types/adapters/email.d.ts +13 -0
  108. package/dist/types/cms.d.ts +30 -0
  109. package/dist/types/index.d.ts +1 -1
  110. package/dist/updates/0.28.0/index.d.ts +2 -0
  111. package/dist/updates/0.28.0/index.js +38 -0
  112. package/dist/updates/0.34.0/index.d.ts +2 -0
  113. package/dist/updates/0.34.0/index.js +17 -0
  114. package/dist/updates/index.js +5 -1
  115. package/package.json +7 -2
@@ -93,28 +93,26 @@ export declare const reorderShippingMethodsCmd: import("@sveltejs/kit").RemoteCo
93
93
  }>>;
94
94
  export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunction<{
95
95
  status?: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | "refunded" | undefined;
96
- email?: string | undefined;
96
+ search?: string | undefined;
97
97
  limit?: number | undefined;
98
98
  offset?: number | undefined;
99
+ deleted?: "exclude" | "only" | "include" | undefined;
99
100
  } | undefined, {
100
101
  items: {
101
102
  number: string;
102
- currency: string;
103
- consents: {
104
- id: string;
105
- accepted: boolean;
106
- label: string;
107
- }[] | null;
108
103
  id: string;
109
- status: import("../../shop/types.js").OrderStatus;
110
- createdAt: Date;
111
- updatedAt: Date;
112
- language: string | null;
113
104
  carrierType: string | null;
105
+ createdAt: Date;
106
+ status: import("../../shop/types.js").OrderStatus;
107
+ currency: string;
114
108
  customerEmail: string;
115
109
  customerName: string | null;
116
110
  customerPhone: string | null;
111
+ customerNip: string | null;
112
+ customerCompanyName: string | null;
117
113
  shippingAddress: Record<string, string> | null;
114
+ billingAddress: Record<string, string> | null;
115
+ invoiceRequested: boolean;
118
116
  totalNet: number;
119
117
  totalGross: number;
120
118
  vatAmount: number;
@@ -128,10 +126,19 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
128
126
  shipmentCreatedAt: Date | null;
129
127
  paymentMethod: string | null;
130
128
  paymentProviderRef: string | null;
129
+ consents: {
130
+ id: string;
131
+ accepted: boolean;
132
+ label: string;
133
+ }[] | null;
131
134
  notes: string | null;
135
+ language: string | null;
132
136
  accessToken: string;
133
137
  partialPayment: import("../../shop/types.js").PartialPayment | null;
134
138
  balanceOwed: boolean;
139
+ deletedAt: Date | null;
140
+ deletedBy: string | null;
141
+ updatedAt: Date;
135
142
  }[];
136
143
  total: number;
137
144
  limit: number;
@@ -140,22 +147,19 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
140
147
  export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
141
148
  order: {
142
149
  number: string;
143
- currency: string;
144
- consents: {
145
- id: string;
146
- accepted: boolean;
147
- label: string;
148
- }[] | null;
149
150
  id: string;
150
- status: import("../../shop/types.js").OrderStatus;
151
- createdAt: Date;
152
- updatedAt: Date;
153
- language: string | null;
154
151
  carrierType: string | null;
152
+ createdAt: Date;
153
+ status: import("../../shop/types.js").OrderStatus;
154
+ currency: string;
155
155
  customerEmail: string;
156
156
  customerName: string | null;
157
157
  customerPhone: string | null;
158
+ customerNip: string | null;
159
+ customerCompanyName: string | null;
158
160
  shippingAddress: Record<string, string> | null;
161
+ billingAddress: Record<string, string> | null;
162
+ invoiceRequested: boolean;
159
163
  totalNet: number;
160
164
  totalGross: number;
161
165
  vatAmount: number;
@@ -169,10 +173,19 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
169
173
  shipmentCreatedAt: Date | null;
170
174
  paymentMethod: string | null;
171
175
  paymentProviderRef: string | null;
176
+ consents: {
177
+ id: string;
178
+ accepted: boolean;
179
+ label: string;
180
+ }[] | null;
172
181
  notes: string | null;
182
+ language: string | null;
173
183
  accessToken: string;
174
184
  partialPayment: import("../../shop/types.js").PartialPayment | null;
175
185
  balanceOwed: boolean;
186
+ deletedAt: Date | null;
187
+ deletedBy: string | null;
188
+ updatedAt: Date;
176
189
  };
177
190
  items: {
178
191
  id: string;
@@ -188,12 +201,16 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
188
201
  }[];
189
202
  history: {
190
203
  id: string;
191
- note: string | null;
192
204
  status: import("../../shop/types.js").OrderStatus;
205
+ note: string | null;
193
206
  orderId: string;
194
207
  changedBy: string | null;
195
208
  changedAt: Date;
196
209
  }[];
210
+ coupon: {
211
+ code: string;
212
+ discountAmount: number;
213
+ } | null;
197
214
  } | null>;
198
215
  export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand<{
199
216
  orderId: string;
@@ -201,22 +218,19 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
201
218
  note?: string | undefined;
202
219
  }, Promise<{
203
220
  number: string;
204
- currency: string;
205
- consents: {
206
- id: string;
207
- accepted: boolean;
208
- label: string;
209
- }[] | null;
210
221
  id: string;
211
- status: import("../../shop/types.js").OrderStatus;
212
- createdAt: Date;
213
- updatedAt: Date;
214
- language: string | null;
215
222
  carrierType: string | null;
223
+ createdAt: Date;
224
+ status: import("../../shop/types.js").OrderStatus;
225
+ currency: string;
216
226
  customerEmail: string;
217
227
  customerName: string | null;
218
228
  customerPhone: string | null;
229
+ customerNip: string | null;
230
+ customerCompanyName: string | null;
219
231
  shippingAddress: Record<string, string> | null;
232
+ billingAddress: Record<string, string> | null;
233
+ invoiceRequested: boolean;
220
234
  totalNet: number;
221
235
  totalGross: number;
222
236
  vatAmount: number;
@@ -230,10 +244,35 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
230
244
  shipmentCreatedAt: Date | null;
231
245
  paymentMethod: string | null;
232
246
  paymentProviderRef: string | null;
247
+ consents: {
248
+ id: string;
249
+ accepted: boolean;
250
+ label: string;
251
+ }[] | null;
233
252
  notes: string | null;
253
+ language: string | null;
234
254
  accessToken: string;
235
255
  partialPayment: import("../../shop/types.js").PartialPayment | null;
236
256
  balanceOwed: boolean;
257
+ deletedAt: Date | null;
258
+ deletedBy: string | null;
259
+ updatedAt: Date;
260
+ }>>;
261
+ export declare const deleteOrderCmd: import("@sveltejs/kit").RemoteCommand<{
262
+ orderId: string;
263
+ }, Promise<{
264
+ success: true;
265
+ reason?: undefined;
266
+ error?: undefined;
267
+ } | {
268
+ success: false;
269
+ reason: "status" | "invoice";
270
+ error: string;
271
+ }>>;
272
+ export declare const restoreOrderCmd: import("@sveltejs/kit").RemoteCommand<{
273
+ orderId: string;
274
+ }, Promise<{
275
+ success: true;
237
276
  }>>;
238
277
  export declare const resendOrderEmailCmd: import("@sveltejs/kit").RemoteCommand<{
239
278
  orderId: string;
@@ -274,12 +313,12 @@ export declare const listShopableCollections: import("@sveltejs/kit").RemoteQuer
274
313
  }[]>;
275
314
  export declare const getOrderRefundsAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
276
315
  refunds: {
277
- amount: number;
278
- currency: string;
279
316
  id: string;
280
- status: import("../../db-postgres/schema/shop/index.js").ShopRefundStatus;
281
317
  createdAt: Date;
318
+ status: import("../../db-postgres/schema/shop/index.js").ShopRefundStatus;
319
+ currency: string;
282
320
  updatedAt: Date;
321
+ amount: number;
283
322
  orderId: string;
284
323
  provider: string;
285
324
  providerRef: string | null;
@@ -312,6 +351,52 @@ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
312
351
  code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error" | "no_payment_kind";
313
352
  error: string;
314
353
  }>>;
354
+ export declare const getOrderInvoiceAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
355
+ invoice: {
356
+ number: string | null;
357
+ id: string;
358
+ createdAt: Date;
359
+ status: import("../../db-postgres/schema/shop/index.js").ShopInvoiceStatus;
360
+ updatedAt: Date;
361
+ raw: unknown;
362
+ orderId: string;
363
+ provider: string;
364
+ kind: string;
365
+ externalId: string | null;
366
+ pdfUrl: string | null;
367
+ attempts: number;
368
+ nextRetryAt: Date | null;
369
+ lastError: string | null;
370
+ } | null;
371
+ invoicingEnabled: boolean;
372
+ }>;
373
+ export declare const issueInvoiceCmd: import("@sveltejs/kit").RemoteCommand<{
374
+ orderId: string;
375
+ force?: boolean | undefined;
376
+ }, Promise<{
377
+ success: true;
378
+ invoice: {
379
+ number: string | null;
380
+ id: string;
381
+ createdAt: Date;
382
+ status: import("../../db-postgres/schema/shop/index.js").ShopInvoiceStatus;
383
+ updatedAt: Date;
384
+ raw: unknown;
385
+ orderId: string;
386
+ provider: string;
387
+ kind: string;
388
+ externalId: string | null;
389
+ pdfUrl: string | null;
390
+ attempts: number;
391
+ nextRetryAt: Date | null;
392
+ lastError: string | null;
393
+ } | null;
394
+ error?: undefined;
395
+ } | {
396
+ success: false;
397
+ error: string;
398
+ invoice?: undefined;
399
+ }>>;
315
400
  export declare const generateBalanceLinkForOrder: import("@sveltejs/kit").RemoteCommand<string, Promise<{
316
401
  success: false;
317
402
  error: string;
@@ -351,7 +436,7 @@ export declare const getCouponAdmin: import("@sveltejs/kit").RemoteQueryFunction
351
436
  }>;
352
437
  export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
353
438
  code: string;
354
- type: "percent" | "fixed";
439
+ type: "fixed" | "percent";
355
440
  value: number;
356
441
  minOrderAmount?: number | null | undefined;
357
442
  maxUses?: number | null | undefined;
@@ -360,10 +445,10 @@ export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
360
445
  }, Promise<{
361
446
  code: string;
362
447
  id: string;
363
- type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
448
+ isActive: boolean;
364
449
  createdAt: Date;
365
450
  updatedAt: Date;
366
- isActive: boolean;
451
+ type: import("../../db-postgres/schema/shop/index.js").ShopCouponType;
367
452
  expiresAt: Date | null;
368
453
  value: string;
369
454
  minOrderAmount: number | null;
@@ -374,7 +459,7 @@ export declare const updateCouponCmd: import("@sveltejs/kit").RemoteCommand<{
374
459
  id: string;
375
460
  input: {
376
461
  code?: string | undefined;
377
- type?: "percent" | "fixed" | undefined;
462
+ type?: "fixed" | "percent" | undefined;
378
463
  value?: number | undefined;
379
464
  minOrderAmount?: number | null | undefined;
380
465
  maxUses?: number | null | undefined;
@@ -399,7 +484,7 @@ export declare const deleteCouponCmd: import("@sveltejs/kit").RemoteCommand<stri
399
484
  }>>;
400
485
  export declare const exportOrdersCsv: import("@sveltejs/kit").RemoteQueryFunction<{
401
486
  status?: "done" | "new" | "awaitingPayment" | "paid" | "preparing" | "sent" | "cancelled" | "paymentRejected" | "refunded" | undefined;
402
- email?: string | undefined;
487
+ search?: string | undefined;
403
488
  } | undefined, {
404
489
  csv: string;
405
490
  count: number;
@@ -3,14 +3,16 @@ import z from 'zod';
3
3
  import { getCMS } from '../../core/cms.js';
4
4
  import { deleteShopData, getShopDataByEntry, listShopEntries, upsertShopData } from '../../shop/server/shop-data.js';
5
5
  import { createShippingMethod, deleteShippingMethod, getShippingMethod, listShippingMethods, reorderShippingMethods, updateShippingMethod } from '../../shop/server/shipping.js';
6
- import { countOrders, getOrderById, getOrderItems, getOrderStatusHistory, listOrders, updateOrderStatus } from '../../shop/server/orders.js';
6
+ import { countOrders, getOrderById, getOrderItems, getOrderStatusHistory, listOrders, OrderNotDeletableError, restoreOrder, softDeleteOrder, updateOrderStatus } from '../../shop/server/orders.js';
7
7
  import { cancelShipmentForOrder, createShipmentForOrder } from '../../shop/server/shipments.js';
8
8
  import { sendOrderStatusEmail } from '../../shop/server/email.js';
9
+ import { getOrderCoupon } from '../../shop/server/coupons.js';
10
+ import { getInvoiceByOrderId, issueInvoiceForOrder } from '../../shop/server/invoices.js';
9
11
  import { getRefundedAmount, listRefunds, refundOrder, RefundError } from '../../shop/server/refund.js';
10
12
  import { getShopDb } from '../../shop/server/db.js';
11
13
  import { shopCouponsTable } from '../../db-postgres/schema/shop/index.js';
12
14
  import { eq } from 'drizzle-orm';
13
- import { requireAuth } from './middleware/auth.js';
15
+ import { requireAuth, requireRole } from './middleware/auth.js';
14
16
  export const getShopEnabled = query(async () => {
15
17
  return getCMS().shopConfig !== null;
16
18
  });
@@ -137,18 +139,25 @@ const orderStatusSchema = z.enum([
137
139
  export const listOrdersAdmin = query(z
138
140
  .object({
139
141
  status: orderStatusSchema.optional(),
140
- email: z.string().optional(),
142
+ search: z.string().trim().min(1).optional(),
141
143
  limit: z.number().int().optional(),
142
- offset: z.number().int().optional()
144
+ offset: z.number().int().optional(),
145
+ deleted: z.enum(['exclude', 'only', 'include']).optional()
143
146
  })
144
147
  .optional(), async (opts) => {
145
- requireAuth();
146
148
  const params = opts ?? {};
149
+ const deleted = params.deleted ?? 'exclude';
150
+ // The trash (soft-deleted orders) is admin-only; the normal list is for
151
+ // any authenticated admin-panel user.
152
+ if (deleted === 'exclude')
153
+ requireAuth();
154
+ else
155
+ requireRole('admin');
147
156
  const limit = params.limit ?? 50;
148
157
  const offset = params.offset ?? 0;
149
158
  const [items, total] = await Promise.all([
150
- listOrders({ ...params, limit, offset }),
151
- countOrders({ status: params.status, email: params.email })
159
+ listOrders({ ...params, deleted, limit, offset }),
160
+ countOrders({ status: params.status, search: params.search, deleted })
152
161
  ]);
153
162
  return { items, total, limit, offset };
154
163
  });
@@ -157,8 +166,12 @@ export const getOrderForAdmin = query(z.string(), async (id) => {
157
166
  const order = await getOrderById(id);
158
167
  if (!order)
159
168
  return null;
160
- const [items, history] = await Promise.all([getOrderItems(id), getOrderStatusHistory(id)]);
161
- return { order, items, history };
169
+ const [items, history, coupon] = await Promise.all([
170
+ getOrderItems(id),
171
+ getOrderStatusHistory(id),
172
+ getOrderCoupon(id).catch(() => null)
173
+ ]);
174
+ return { order, items, history, coupon };
162
175
  });
163
176
  export const updateOrderStatusCmd = command(z.object({
164
177
  orderId: z.string(),
@@ -169,6 +182,25 @@ export const updateOrderStatusCmd = command(z.object({
169
182
  const updated = await updateOrderStatus(orderId, status, { note, changedBy: 'admin' });
170
183
  return updated;
171
184
  });
185
+ export const deleteOrderCmd = command(z.object({ orderId: z.string() }), async ({ orderId }) => {
186
+ const { user } = requireRole('admin');
187
+ try {
188
+ await softDeleteOrder(orderId, user.id);
189
+ return { success: true };
190
+ }
191
+ catch (err) {
192
+ if (err instanceof OrderNotDeletableError) {
193
+ return { success: false, reason: err.reason, error: err.message };
194
+ }
195
+ const message = err instanceof Error ? err.message : 'Nie udało się usunąć zamówienia';
196
+ return { success: false, reason: 'status', error: message };
197
+ }
198
+ });
199
+ export const restoreOrderCmd = command(z.object({ orderId: z.string() }), async ({ orderId }) => {
200
+ requireRole('admin');
201
+ await restoreOrder(orderId);
202
+ return { success: true };
203
+ });
172
204
  export const resendOrderEmailCmd = command(z.object({ orderId: z.string(), status: orderStatusSchema }), async ({ orderId, status }) => {
173
205
  requireAuth();
174
206
  await sendOrderStatusEmail(orderId, status);
@@ -261,6 +293,23 @@ export const refundOrderCmd = command(z.object({
261
293
  return { success: false, code: 'provider_error', error: message };
262
294
  }
263
295
  });
296
+ export const getOrderInvoiceAdmin = query(z.string(), async (orderId) => {
297
+ requireAuth();
298
+ const invoice = await getInvoiceByOrderId(orderId);
299
+ const invoicingEnabled = !!getCMS().shopConfig?.invoicing;
300
+ return { invoice, invoicingEnabled };
301
+ });
302
+ export const issueInvoiceCmd = command(z.object({ orderId: z.string(), force: z.boolean().optional() }), async ({ orderId, force }) => {
303
+ requireAuth();
304
+ try {
305
+ const invoice = await issueInvoiceForOrder(orderId, { force });
306
+ return { success: true, invoice };
307
+ }
308
+ catch (err) {
309
+ const message = err instanceof Error ? err.message : 'Invoice issuance failed';
310
+ return { success: false, error: message };
311
+ }
312
+ });
264
313
  export const generateBalanceLinkForOrder = command(z.string(), async (orderId) => {
265
314
  requireAuth();
266
315
  const { generateBalanceToken, requireBalanceTokenSecret } = await import('../../shop/server/balance-payment.js');
@@ -375,7 +424,7 @@ function csvEscape(value) {
375
424
  const exportFiltersSchema = z
376
425
  .object({
377
426
  status: orderStatusSchema.optional(),
378
- email: z.string().optional()
427
+ search: z.string().trim().min(1).optional()
379
428
  })
380
429
  .optional();
381
430
  export const exportOrdersCsv = query(exportFiltersSchema, async (opts) => {
@@ -8,3 +8,18 @@ export interface Breadcrumb {
8
8
  label: string;
9
9
  href?: string;
10
10
  }
11
+ /**
12
+ * One additional sidebar entry rendered below built-in nav sections
13
+ * (Dashboard, Collections, Singles, Forms, Shop). Configure via
14
+ * `defineConfig({ admin: { extraNavItems: [...] } })` — see CMSConfig.
15
+ *
16
+ * Icons are intentionally omitted from the public surface to keep userland
17
+ * free of `@tabler/icons-svelte` (or any specific icon library) as a
18
+ * dependency. The sidebar renders the title alone when no icon is provided.
19
+ *
20
+ * @public
21
+ */
22
+ export interface AdminNavItem {
23
+ title: string;
24
+ url: string;
25
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Build a CSV string from `headers` and `rows`. Values are escaped per
3
+ * RFC 4180: any cell containing `"`, `,`, `;`, `\n`, or `\r` is wrapped in
4
+ * double quotes and embedded `"` are doubled. `null` / `undefined` become
5
+ * empty cells. Lines are joined with CRLF.
6
+ *
7
+ * Pure (no DOM access) — safe on the server. Use together with
8
+ * `downloadCsv()` on the client, or return as a `Response` from a server
9
+ * action.
10
+ *
11
+ * @public
12
+ */
13
+ export declare function buildCsv(opts: {
14
+ headers: string[];
15
+ rows: (string | number | null | undefined)[][];
16
+ }): string;
17
+ /**
18
+ * Trigger a client-side CSV download. Wraps the given `csv` string in a
19
+ * `Blob` with `text/csv;charset=utf-8` and clicks a synthetic
20
+ * `<a download>`. Prepends a UTF-8 BOM (``) unless one is already
21
+ * present, so Excel honors UTF-8 (PL diacritics).
22
+ *
23
+ * Must be called from a browser context — relies on `document`, `Blob`,
24
+ * and `URL.createObjectURL`. Throws when called server-side.
25
+ *
26
+ * Typical wiring: server returns a precomposed CSV (e.g. via a
27
+ * `query()` remote like `exportOrdersCsv`) → client calls
28
+ * `downloadCsv({ filename, csv: result.csv })`.
29
+ *
30
+ * @public
31
+ * @example
32
+ * ```ts
33
+ * import { buildCsv, downloadCsv } from 'includio-cms/admin/client';
34
+ *
35
+ * const csv = buildCsv({
36
+ * headers: ['email', 'name', 'consentedAt'],
37
+ * rows: items.map((i) => [i.email, i.name, i.consentedAt])
38
+ * });
39
+ * downloadCsv({ filename: 'newsletter-2026-06-02.csv', csv });
40
+ * ```
41
+ */
42
+ export declare function downloadCsv(opts: {
43
+ filename: string;
44
+ csv: string;
45
+ }): void;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Build a CSV string from `headers` and `rows`. Values are escaped per
3
+ * RFC 4180: any cell containing `"`, `,`, `;`, `\n`, or `\r` is wrapped in
4
+ * double quotes and embedded `"` are doubled. `null` / `undefined` become
5
+ * empty cells. Lines are joined with CRLF.
6
+ *
7
+ * Pure (no DOM access) — safe on the server. Use together with
8
+ * `downloadCsv()` on the client, or return as a `Response` from a server
9
+ * action.
10
+ *
11
+ * @public
12
+ */
13
+ export function buildCsv(opts) {
14
+ const escape = (v) => {
15
+ const s = v == null ? '' : String(v);
16
+ return /["\n\r,;]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
17
+ };
18
+ const lines = [opts.headers, ...opts.rows].map((row) => row.map(escape).join(','));
19
+ return lines.join('\r\n');
20
+ }
21
+ /**
22
+ * Trigger a client-side CSV download. Wraps the given `csv` string in a
23
+ * `Blob` with `text/csv;charset=utf-8` and clicks a synthetic
24
+ * `<a download>`. Prepends a UTF-8 BOM (``) unless one is already
25
+ * present, so Excel honors UTF-8 (PL diacritics).
26
+ *
27
+ * Must be called from a browser context — relies on `document`, `Blob`,
28
+ * and `URL.createObjectURL`. Throws when called server-side.
29
+ *
30
+ * Typical wiring: server returns a precomposed CSV (e.g. via a
31
+ * `query()` remote like `exportOrdersCsv`) → client calls
32
+ * `downloadCsv({ filename, csv: result.csv })`.
33
+ *
34
+ * @public
35
+ * @example
36
+ * ```ts
37
+ * import { buildCsv, downloadCsv } from 'includio-cms/admin/client';
38
+ *
39
+ * const csv = buildCsv({
40
+ * headers: ['email', 'name', 'consentedAt'],
41
+ * rows: items.map((i) => [i.email, i.name, i.consentedAt])
42
+ * });
43
+ * downloadCsv({ filename: 'newsletter-2026-06-02.csv', csv });
44
+ * ```
45
+ */
46
+ export function downloadCsv(opts) {
47
+ if (typeof document === 'undefined') {
48
+ throw new Error('[includio-cms] downloadCsv() must be called from a browser context.');
49
+ }
50
+ const csv = opts.csv.startsWith('') ? opts.csv : '' + opts.csv;
51
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
52
+ const url = URL.createObjectURL(blob);
53
+ const a = document.createElement('a');
54
+ a.href = url;
55
+ a.download = opts.filename;
56
+ a.style.display = 'none';
57
+ document.body.appendChild(a);
58
+ a.click();
59
+ document.body.removeChild(a);
60
+ URL.revokeObjectURL(url);
61
+ }
@@ -348,7 +348,7 @@ export const { GET, POST, PUT, DELETE } = createRestApiHandler();
348
348
  import { json } from '@sveltejs/kit';
349
349
  import type { RequestHandler } from './$types';
350
350
  import { createFormSubmission, parseFormDataForSubmission } from 'includio-cms/sveltekit/server';
351
- import { getCMS } from 'includio-cms/core';
351
+ import { getCMS } from 'includio-cms/core/server';
352
352
 
353
353
  const counts = new Map<string, { count: number; resetAt: number }>();
354
354
  const LIMIT = 5;
@@ -8,6 +8,6 @@ type Props = WithElementRef<Omit<HTMLInputAttributes, "type"> & ({
8
8
  type?: InputType;
9
9
  files?: undefined;
10
10
  })>;
11
- declare const Input: import("svelte").Component<Props, {}, "files" | "ref" | "value">;
11
+ declare const Input: import("svelte").Component<Props, {}, "ref" | "files" | "value">;
12
12
  type Input = ReturnType<typeof Input>;
13
13
  export default Input;
@@ -2,7 +2,7 @@ declare const InputGroupInput: import("svelte").Component<(Omit<import("svelte/e
2
2
  type: "file";
3
3
  files?: FileList;
4
4
  } | {
5
- type?: "number" | "image" | "url" | "text" | "date" | "radio" | "color" | "button" | "checkbox" | "search" | (string & {}) | "email" | "tel" | "password" | "time" | "hidden" | "reset" | "submit" | "datetime-local" | "month" | "range" | "week";
5
+ type?: "number" | "image" | "url" | "text" | "date" | "search" | "radio" | "color" | "button" | "checkbox" | (string & {}) | "email" | "tel" | "password" | "time" | "month" | "hidden" | "reset" | "submit" | "datetime-local" | "range" | "week";
6
6
  files?: undefined;
7
7
  })) & {
8
8
  ref?: HTMLElement | null | undefined;
@@ -2,7 +2,7 @@ declare const SidebarInput: import("svelte").Component<(Omit<import("svelte/elem
2
2
  type: "file";
3
3
  files?: FileList;
4
4
  } | {
5
- type?: "number" | "image" | "url" | "text" | "date" | "radio" | "color" | "button" | "checkbox" | "search" | (string & {}) | "email" | "tel" | "password" | "time" | "hidden" | "reset" | "submit" | "datetime-local" | "month" | "range" | "week";
5
+ type?: "number" | "image" | "url" | "text" | "date" | "search" | "radio" | "color" | "button" | "checkbox" | (string & {}) | "email" | "tel" | "password" | "time" | "month" | "hidden" | "reset" | "submit" | "datetime-local" | "range" | "week";
6
6
  files?: undefined;
7
7
  })) & {
8
8
  ref?: HTMLElement | null | undefined;
@@ -1,13 +1,13 @@
1
1
  import type { DatabaseAdapter } from '../types/adapters/db.js';
2
2
  import type { FilesAdapter } from '../types/adapters/files.js';
3
- import type { ApiKeyConfig, AuthConfig, CMSConfig, ICMS, MediaConfig, TypographyConfig } from '../types/cms.js';
3
+ import type { AdminConfig, ApiKeyConfig, AuthConfig, CMSConfig, ICMS, MediaConfig, TypographyConfig } from '../types/cms.js';
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
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
- import type { EmailAdapter } from '../types/adapters/email.js';
10
+ import type { EmailAdapter, SendMailOptions } from '../types/adapters/email.js';
11
11
  import { betterAuth } from 'better-auth';
12
12
  import type { ResolvedShopConfig } from '../shop/types.js';
13
13
  import type { ResolvedCmpConfig } from '../cmp/types.js';
@@ -28,6 +28,7 @@ export declare class CMS implements ICMS {
28
28
  sidebarHelp: boolean;
29
29
  shopConfig: ResolvedShopConfig | null;
30
30
  cmpConfig: ResolvedCmpConfig | null;
31
+ adminConfig: AdminConfig | null;
31
32
  /**
32
33
  * Resolves once the shop's variant-attribute GIN indexes have been applied
33
34
  * by `initCMS()`. `null` when the CMS is configured without a shop. Tests
@@ -64,3 +65,44 @@ export declare function initCMS(config: CMSConfig): CMS;
64
65
  * ```
65
66
  */
66
67
  export declare function getCMS(): CMS;
68
+ /**
69
+ * Returns the configured email adapter from the active CMS singleton, or
70
+ * `null` when no `email` was passed to `defineConfig`. Use this in userland
71
+ * hooks (e.g. `ShopConfig.onOrderPaid`) to reuse the same SMTP transport the
72
+ * CMS already opened — no second nodemailer instance, no duplicated ENV.
73
+ *
74
+ * @returns The current `EmailAdapter` or `null`.
75
+ * @throws {Error} when called before `initCMS()` has run.
76
+ * @public
77
+ * @example
78
+ * ```ts
79
+ * import { getMailer } from 'includio-cms/core';
80
+ *
81
+ * const mailer = getMailer();
82
+ * if (mailer) await mailer.sendMail({ to: 'admin@…', subject: '…', html: '…' });
83
+ * ```
84
+ */
85
+ export declare function getMailer(): EmailAdapter | null;
86
+ /**
87
+ * Send an email through the configured CMS email adapter. Thin convenience
88
+ * wrapper around `getCMS().emailAdapter.sendMail` — same options shape, same
89
+ * From address, same SMTP transport as the built-in flows (reset password,
90
+ * shop order status, form notifications).
91
+ *
92
+ * @throws {Error} when no email adapter is configured (matches
93
+ * `defineConfig({ email: ... })` not being set) or when the CMS has not
94
+ * been initialized yet.
95
+ * @public
96
+ * @example
97
+ * ```ts
98
+ * import { sendMail } from 'includio-cms/core';
99
+ *
100
+ * await sendMail({
101
+ * to: process.env.ADMIN_EMAIL!,
102
+ * subject: '[shop] New consent',
103
+ * text: 'Plain fallback for clients that prefer text.',
104
+ * html: '<p>Body…</p>'
105
+ * });
106
+ * ```
107
+ */
108
+ export declare function sendMail(options: SendMailOptions): Promise<void>;