includio-cms 0.36.9 → 0.37.1

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 (247) hide show
  1. package/API.md +178 -3
  2. package/CHANGELOG.md +77 -0
  3. package/DOCS.md +28 -3
  4. package/ROADMAP.md +32 -0
  5. package/dist/admin/client/booking/booking-dashboard.svelte +40 -0
  6. package/dist/admin/client/booking/booking-dashboard.svelte.d.ts +3 -0
  7. package/dist/admin/client/booking/customer-panel.svelte +102 -0
  8. package/dist/admin/client/booking/customer-panel.svelte.d.ts +11 -0
  9. package/dist/admin/client/booking/customer-view.d.ts +52 -0
  10. package/dist/admin/client/booking/customer-view.js +134 -0
  11. package/dist/admin/client/booking/participant-data-panel.svelte +330 -0
  12. package/dist/admin/client/booking/participant-data-panel.svelte.d.ts +22 -0
  13. package/dist/admin/client/booking/reservation-detail-page.svelte +637 -0
  14. package/dist/admin/client/booking/reservation-detail-page.svelte.d.ts +3 -0
  15. package/dist/admin/client/booking/reservations-page.svelte +221 -0
  16. package/dist/admin/client/booking/reservations-page.svelte.d.ts +3 -0
  17. package/dist/admin/client/collection/collection-entries.svelte +1 -1
  18. package/dist/admin/client/collection/entry-link.svelte +6 -4
  19. package/dist/admin/client/collection/entry-link.svelte.d.ts +3 -1
  20. package/dist/admin/client/collection/status-badge.svelte +39 -3
  21. package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
  22. package/dist/admin/client/entry/entry.svelte +26 -9
  23. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
  24. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
  25. package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
  26. package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
  27. package/dist/admin/client/entry/preview-views.d.ts +27 -0
  28. package/dist/admin/client/entry/preview-views.js +33 -0
  29. package/dist/admin/client/index.d.ts +3 -0
  30. package/dist/admin/client/index.js +3 -0
  31. package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
  32. package/dist/admin/client/modules/dashboard-registry.js +11 -0
  33. package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
  34. package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
  35. package/dist/admin/client/modules/stat-card.svelte +22 -0
  36. package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
  37. package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
  38. package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
  39. package/dist/admin/components/fields/relation-field.svelte +11 -2
  40. package/dist/admin/components/icons/icon-map.js +8 -0
  41. package/dist/admin/components/layout/app-sidebar.svelte +45 -14
  42. package/dist/admin/components/layout/context-switcher.svelte +90 -0
  43. package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
  44. package/dist/admin/components/layout/lang.d.ts +4 -0
  45. package/dist/admin/components/layout/lang.js +8 -0
  46. package/dist/admin/components/layout/layout-renderer.svelte +2 -1
  47. package/dist/admin/components/layout/layout-tabs.svelte +14 -0
  48. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
  49. package/dist/admin/components/layout/nav-collections.svelte +16 -9
  50. package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
  51. package/dist/admin/components/layout/nav-global.svelte +45 -0
  52. package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
  53. package/dist/admin/components/layout/nav-main.svelte +8 -32
  54. package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
  55. package/dist/admin/components/layout/nav-module.svelte +57 -0
  56. package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
  57. package/dist/admin/components/layout/nav-section.svelte +1 -1
  58. package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
  59. package/dist/admin/components/layout/nav-singletons.svelte +14 -7
  60. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
  61. package/dist/admin/remote/booking.remote.d.ts +247 -0
  62. package/dist/admin/remote/booking.remote.js +286 -0
  63. package/dist/admin/remote/index.d.ts +2 -0
  64. package/dist/admin/remote/index.js +2 -0
  65. package/dist/admin/remote/modules.remote.d.ts +10 -0
  66. package/dist/admin/remote/modules.remote.js +14 -0
  67. package/dist/admin/state/active-context-resolve.d.ts +37 -0
  68. package/dist/admin/state/active-context-resolve.js +66 -0
  69. package/dist/admin/state/active-context.svelte.d.ts +8 -0
  70. package/dist/admin/state/active-context.svelte.js +27 -0
  71. package/dist/booking/adjustments.d.ts +33 -0
  72. package/dist/booking/adjustments.js +26 -0
  73. package/dist/booking/capacity.d.ts +12 -0
  74. package/dist/booking/capacity.js +10 -0
  75. package/dist/booking/client/index.d.ts +42 -0
  76. package/dist/booking/client/index.js +48 -0
  77. package/dist/booking/client/use-booking.svelte.d.ts +24 -0
  78. package/dist/booking/client/use-booking.svelte.js +56 -0
  79. package/dist/booking/config.d.ts +176 -0
  80. package/dist/booking/config.js +37 -0
  81. package/dist/booking/draft.d.ts +96 -0
  82. package/dist/booking/draft.js +67 -0
  83. package/dist/booking/expiry-logic.d.ts +30 -0
  84. package/dist/booking/expiry-logic.js +33 -0
  85. package/dist/booking/index.d.ts +18 -0
  86. package/dist/booking/index.js +52 -0
  87. package/dist/booking/ledger.d.ts +46 -0
  88. package/dist/booking/ledger.js +60 -0
  89. package/dist/booking/number.d.ts +2 -0
  90. package/dist/booking/number.js +11 -0
  91. package/dist/booking/participant-fields/access.d.ts +10 -0
  92. package/dist/booking/participant-fields/access.js +14 -0
  93. package/dist/booking/participant-fields/age.d.ts +12 -0
  94. package/dist/booking/participant-fields/age.js +28 -0
  95. package/dist/booking/participant-fields/completeness.d.ts +8 -0
  96. package/dist/booking/participant-fields/completeness.js +13 -0
  97. package/dist/booking/participant-fields/group.d.ts +6 -0
  98. package/dist/booking/participant-fields/group.js +15 -0
  99. package/dist/booking/participant-fields/index.d.ts +6 -0
  100. package/dist/booking/participant-fields/index.js +6 -0
  101. package/dist/booking/participant-fields/types.d.ts +50 -0
  102. package/dist/booking/participant-fields/types.js +5 -0
  103. package/dist/booking/participant-fields/validate.d.ts +7 -0
  104. package/dist/booking/participant-fields/validate.js +44 -0
  105. package/dist/booking/participant-pricing.d.ts +93 -0
  106. package/dist/booking/participant-pricing.js +91 -0
  107. package/dist/booking/participants-view.d.ts +32 -0
  108. package/dist/booking/participants-view.js +22 -0
  109. package/dist/booking/payment-context.d.ts +19 -0
  110. package/dist/booking/payment-context.js +29 -0
  111. package/dist/booking/pricing-inputs.d.ts +28 -0
  112. package/dist/booking/pricing-inputs.js +35 -0
  113. package/dist/booking/pricing.d.ts +86 -0
  114. package/dist/booking/pricing.js +125 -0
  115. package/dist/booking/recompute.d.ts +22 -0
  116. package/dist/booking/recompute.js +31 -0
  117. package/dist/booking/reminders-schedule.d.ts +11 -0
  118. package/dist/booking/reminders-schedule.js +28 -0
  119. package/dist/booking/server/adjustments.d.ts +27 -0
  120. package/dist/booking/server/adjustments.js +139 -0
  121. package/dist/booking/server/bookings.d.ts +51 -0
  122. package/dist/booking/server/bookings.js +336 -0
  123. package/dist/booking/server/catalog.d.ts +9 -0
  124. package/dist/booking/server/catalog.js +20 -0
  125. package/dist/booking/server/db.d.ts +7 -0
  126. package/dist/booking/server/db.js +18 -0
  127. package/dist/booking/server/email.d.ts +23 -0
  128. package/dist/booking/server/email.js +59 -0
  129. package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
  130. package/dist/booking/server/emailTemplateRegistry.js +125 -0
  131. package/dist/booking/server/http/create-handler.d.ts +5 -0
  132. package/dist/booking/server/http/create-handler.js +70 -0
  133. package/dist/booking/server/http/index.d.ts +4 -0
  134. package/dist/booking/server/http/index.js +4 -0
  135. package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
  136. package/dist/booking/server/http/maintenance-handler.js +36 -0
  137. package/dist/booking/server/http/portal-handler.d.ts +13 -0
  138. package/dist/booking/server/http/portal-handler.js +103 -0
  139. package/dist/booking/server/http/webhook-handler.d.ts +9 -0
  140. package/dist/booking/server/http/webhook-handler.js +39 -0
  141. package/dist/booking/server/index.d.ts +12 -0
  142. package/dist/booking/server/index.js +14 -0
  143. package/dist/booking/server/maintenance.d.ts +28 -0
  144. package/dist/booking/server/maintenance.js +134 -0
  145. package/dist/booking/server/participants.d.ts +26 -0
  146. package/dist/booking/server/participants.js +92 -0
  147. package/dist/booking/server/payments.d.ts +34 -0
  148. package/dist/booking/server/payments.js +207 -0
  149. package/dist/booking/server/portal-session.d.ts +16 -0
  150. package/dist/booking/server/portal-session.js +42 -0
  151. package/dist/booking/server/portal.d.ts +206 -0
  152. package/dist/booking/server/portal.js +236 -0
  153. package/dist/booking/server/price-source.d.ts +73 -0
  154. package/dist/booking/server/price-source.js +94 -0
  155. package/dist/booking/server/refunds.d.ts +7 -0
  156. package/dist/booking/server/refunds.js +58 -0
  157. package/dist/booking/server/reminders.d.ts +9 -0
  158. package/dist/booking/server/reminders.js +24 -0
  159. package/dist/booking/server/validation.d.ts +17 -0
  160. package/dist/booking/server/validation.js +55 -0
  161. package/dist/booking/state-machine.d.ts +41 -0
  162. package/dist/booking/state-machine.js +73 -0
  163. package/dist/booking/templates/_partials/footer.en.html +4 -0
  164. package/dist/booking/templates/_partials/footer.pl.html +4 -0
  165. package/dist/booking/templates/_partials/header.en.html +4 -0
  166. package/dist/booking/templates/_partials/header.pl.html +4 -0
  167. package/dist/booking/templates/balance-due.en.html +10 -0
  168. package/dist/booking/templates/balance-due.pl.html +10 -0
  169. package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
  170. package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
  171. package/dist/booking/templates/booking-received.en.html +9 -0
  172. package/dist/booking/templates/booking-received.pl.html +9 -0
  173. package/dist/booking/templates/deposit-paid.en.html +6 -0
  174. package/dist/booking/templates/deposit-paid.pl.html +6 -0
  175. package/dist/booking/templates/payment-link.en.html +5 -0
  176. package/dist/booking/templates/payment-link.pl.html +5 -0
  177. package/dist/booking/templates/payment-received.en.html +10 -0
  178. package/dist/booking/templates/payment-received.pl.html +10 -0
  179. package/dist/booking/totals.d.ts +38 -0
  180. package/dist/booking/totals.js +61 -0
  181. package/dist/booking/webhook-logic.d.ts +28 -0
  182. package/dist/booking/webhook-logic.js +50 -0
  183. package/dist/cli/create-user.d.ts +7 -1
  184. package/dist/cli/create-user.js +23 -15
  185. package/dist/cli/index.js +55 -7
  186. package/dist/cli/scaffold/admin.d.ts +6 -0
  187. package/dist/cli/scaffold/admin.js +5 -3
  188. package/dist/cli/scaffold/booking.d.ts +12 -0
  189. package/dist/cli/scaffold/booking.js +103 -0
  190. package/dist/core/cms.d.ts +24 -9
  191. package/dist/core/cms.js +72 -34
  192. package/dist/core/config/validatePreviews.d.ts +12 -0
  193. package/dist/core/config/validatePreviews.js +90 -0
  194. package/dist/core/fields/slugPath.d.ts +9 -1
  195. package/dist/core/fields/slugPath.js +16 -2
  196. package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
  197. package/dist/core/server/fields/resolveRelationFields.js +12 -3
  198. package/dist/core/server/generator/generator.js +7 -2
  199. package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
  200. package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
  201. package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
  202. package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
  203. package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
  204. package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
  205. package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
  206. package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
  207. package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
  208. package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
  209. package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
  210. package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
  211. package/dist/db-postgres/schema/booking/bookings.d.ts +324 -0
  212. package/dist/db-postgres/schema/booking/bookings.js +52 -0
  213. package/dist/db-postgres/schema/booking/index.d.ts +7 -0
  214. package/dist/db-postgres/schema/booking/index.js +7 -0
  215. package/dist/db-postgres/schema/index.d.ts +1 -0
  216. package/dist/db-postgres/schema/index.js +1 -0
  217. package/dist/db-postgres/schema-booking.d.ts +1 -0
  218. package/dist/db-postgres/schema-booking.js +1 -0
  219. package/dist/server/security/rate-limit-handler.d.ts +19 -0
  220. package/dist/server/security/rate-limit-handler.js +34 -0
  221. package/dist/shop/adapters/payu/client.js +3 -1
  222. package/dist/shop/adapters/payu/index.js +37 -17
  223. package/dist/shop/adapters/payu/payload.d.ts +6 -0
  224. package/dist/shop/adapters/payu/payload.js +1 -1
  225. package/dist/shop/adapters/stripe/index.js +36 -21
  226. package/dist/shop/index.d.ts +11 -2
  227. package/dist/shop/index.js +43 -1
  228. package/dist/shop/rate-limit.d.ts +1 -9
  229. package/dist/shop/rate-limit.js +3 -28
  230. package/dist/shop/types.d.ts +19 -0
  231. package/dist/sveltekit/config.d.ts +4 -0
  232. package/dist/sveltekit/config.js +6 -0
  233. package/dist/types/cms.d.ts +15 -1
  234. package/dist/types/cms.schema.d.ts +96 -2
  235. package/dist/types/cms.schema.js +84 -1
  236. package/dist/types/config.d.ts +33 -0
  237. package/dist/types/layout.d.ts +6 -0
  238. package/dist/types/modules.d.ts +133 -0
  239. package/dist/types/modules.js +27 -0
  240. package/dist/updates/0.37.0/index.d.ts +2 -0
  241. package/dist/updates/0.37.0/index.js +52 -0
  242. package/dist/updates/0.37.1/index.d.ts +2 -0
  243. package/dist/updates/0.37.1/index.js +24 -0
  244. package/dist/updates/index.js +5 -1
  245. package/package.json +19 -1
  246. package/dist/admin/components/layout/nav-shop.svelte +0 -33
  247. package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Booking state machine — pure, deterministic. Two concerns:
3
+ *
4
+ * 1. {@link deriveStatus} computes the payment-progress status from money
5
+ * (gross, paid, deposit) — the source of truth is the ledger balance.
6
+ * 2. {@link assertTransition} / {@link canTransition} validate **manual**
7
+ * status moves (notably `→ completed` and `→ cancelled`), rejecting illegal
8
+ * transitions with a {@link BookingStateError}.
9
+ */
10
+ export const BOOKING_STATUSES = [
11
+ 'draft',
12
+ 'reserved',
13
+ 'partially_paid',
14
+ 'paid',
15
+ 'completed',
16
+ 'cancelled'
17
+ ];
18
+ /** Thrown when an illegal status transition is attempted. */
19
+ export class BookingStateError extends Error {
20
+ from;
21
+ to;
22
+ constructor(from, to) {
23
+ super(`Illegal booking status transition: ${from} → ${to}`);
24
+ this.from = from;
25
+ this.to = to;
26
+ this.name = 'BookingStateError';
27
+ }
28
+ }
29
+ /**
30
+ * Allowed manual transitions. Backward edges (e.g. `paid → partially_paid`)
31
+ * support refunds/edits that raise the balance. `completed` and `cancelled`
32
+ * are terminal.
33
+ */
34
+ const TRANSITIONS = {
35
+ draft: ['reserved', 'partially_paid', 'paid', 'cancelled'],
36
+ reserved: ['partially_paid', 'paid', 'cancelled'],
37
+ partially_paid: ['reserved', 'paid', 'cancelled'],
38
+ paid: ['partially_paid', 'completed', 'cancelled'],
39
+ completed: [],
40
+ cancelled: []
41
+ };
42
+ /** Whether `from → to` is a legal transition (self-transitions are allowed). */
43
+ export function canTransition(from, to) {
44
+ if (from === to)
45
+ return true;
46
+ return TRANSITIONS[from]?.includes(to) ?? false;
47
+ }
48
+ /** Asserts a legal transition, throwing {@link BookingStateError} otherwise. */
49
+ export function assertTransition(from, to) {
50
+ if (!canTransition(from, to))
51
+ throw new BookingStateError(from, to);
52
+ }
53
+ /**
54
+ * Derives the payment-progress status from the ledger.
55
+ *
56
+ * Money bands (after preserving terminal states):
57
+ * - `paid == 0` → `draft`
58
+ * - `paid >= gross` (gross > 0) → `paid`
59
+ * - `paid > depositRequired` → `partially_paid` (installments beyond deposit)
60
+ * - `0 < paid <= depositRequired` → `reserved` (deposit secured)
61
+ */
62
+ export function deriveStatus(input) {
63
+ const { gross, paid, depositRequired, current } = input;
64
+ if (current === 'completed' || current === 'cancelled')
65
+ return current;
66
+ if (paid <= 0)
67
+ return 'draft';
68
+ if (gross > 0 && paid >= gross)
69
+ return 'paid';
70
+ if (paid > depositRequired)
71
+ return 'partially_paid';
72
+ return 'reserved';
73
+ }
@@ -0,0 +1,4 @@
1
+ </div>
2
+ <p style="text-align:center;color:#8888A0;font-size:12px;margin-top:16px;">{{#if brand}}{{brand}} · {{/if}}{{booking.customer.email}}</p>
3
+ </div>
4
+ </body></html>
@@ -0,0 +1,4 @@
1
+ </div>
2
+ <p style="text-align:center;color:#8888A0;font-size:12px;margin-top:16px;">{{#if brand}}{{brand}} · {{/if}}{{booking.customer.email}}</p>
3
+ </div>
4
+ </body></html>
@@ -0,0 +1,4 @@
1
+ <!doctype html>
2
+ <html lang="en"><body style="margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;background:#FAFAFE;color:#1A1A2E;">
3
+ <div style="max-width:560px;margin:0 auto;padding:24px;">
4
+ <div style="background:#fff;border-radius:12px;padding:28px;">
@@ -0,0 +1,4 @@
1
+ <!doctype html>
2
+ <html lang="pl"><body style="margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;background:#FAFAFE;color:#1A1A2E;">
3
+ <div style="max-width:560px;margin:0 auto;padding:24px;">
4
+ <div style="background:#fff;border-radius:12px;padding:28px;">
@@ -0,0 +1,10 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Time to pay your balance — booking {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">A reminder about the remaining amount: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
4
+ {{#if booking.balanceDueAt}}
5
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">You have until <strong>{{date booking.balanceDueAt "en-GB"}}</strong>{{#if now}} (about {{daysUntil booking.balanceDueAt now}} days left){{/if}}. By then, pay the remaining amount or change your departure date in your portal. Otherwise your booking will be cancelled and the deposit forfeited.</p>
6
+ {{else}}
7
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Please pay before your departure date — you can also change the date in your portal.</p>
8
+ {{/if}}
9
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Pay or change the date</a></div>{{/if}}
10
+ {{> footer}}
@@ -0,0 +1,10 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Czas na dopłatę — rezerwacja {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Przypominamy o pozostałej kwocie: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
4
+ {{#if booking.balanceDueAt}}
5
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Masz czas do <strong>{{date booking.balanceDueAt}}</strong>{{#if now}} (zostało około {{daysUntil booking.balanceDueAt now}} dni){{/if}}. Do tego dnia dopłać pozostałą kwotę albo zmień termin wyjazdu w swoim panelu. Inaczej rezerwacja zostanie anulowana, a wpłacony zadatek przepada.</p>
6
+ {{else}}
7
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Prosimy o dopłatę przed terminem wyjazdu — możesz też zmienić termin w swoim panelu.</p>
8
+ {{/if}}
9
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Dopłać lub zmień termin</a></div>{{/if}}
10
+ {{> footer}}
@@ -0,0 +1,5 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Booking {{booking.number}} has been cancelled</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">The payment deadline has passed, so your booking was cancelled and the deposit forfeited.</p>
4
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">If this is a mistake or you'd like to rebook — just reply to this email. We'll help you find a new date.</p>
5
+ {{> footer}}
@@ -0,0 +1,5 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Rezerwacja {{booking.number}} została anulowana</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Termin dopłaty minął, więc Twoja rezerwacja została anulowana, a wpłacony zadatek przepadł.</p>
4
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Jeśli to nieporozumienie albo chcesz wrócić do tematu — po prostu odpowiedz na tę wiadomość. Pomożemy znaleźć nowy termin.</p>
5
+ {{> footer}}
@@ -0,0 +1,9 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">We've got your booking {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Thank you! We've saved your booking. To confirm it, pay the deposit — you can do it all from your booking portal.</p>
4
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Amount still due: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
5
+ {{#if booking.totals.perks.length}}
6
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Your booking includes: {{#each booking.totals.perks}}<strong>{{this.label}}</strong>{{#unless @last}}, {{/unless}}{{/each}}.</p>
7
+ {{/if}}
8
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Pay the deposit</a></div>{{/if}}
9
+ {{> footer}}
@@ -0,0 +1,9 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Mamy Twoją rezerwację {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Dziękujemy! Zapisaliśmy Twoją rezerwację. Żeby ją potwierdzić, opłać zadatek — wszystko zrobisz w swoim panelu rezerwacji.</p>
4
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Kwota do dopłaty: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
5
+ {{#if booking.totals.perks.length}}
6
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">W ramach rezerwacji otrzymujesz: {{#each booking.totals.perks}}<strong>{{this.label}}</strong>{{#unless @last}}, {{/unless}}{{/each}}.</p>
7
+ {{/if}}
8
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Opłać zadatek w panelu</a></div>{{/if}}
9
+ {{> footer}}
@@ -0,0 +1,6 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Deposit received — booking {{booking.number}} confirmed</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Thank you! We've received your deposit, so your booking is confirmed.</p>
4
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Amount still due: <strong>{{currency booking.totals.balance currency}}</strong>. You can pay it anytime from your portal.</p>
5
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Pay the balance</a></div>{{/if}}
6
+ {{> footer}}
@@ -0,0 +1,6 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Zadatek zaksięgowany — rezerwacja {{booking.number}} potwierdzona</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Dziękujemy! Zaksięgowaliśmy Twój zadatek, więc rezerwacja jest potwierdzona.</p>
4
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Pozostała kwota do dopłaty: <strong>{{currency booking.totals.balance currency}}</strong>. Możesz ją uregulować w dowolnej chwili w swoim panelu.</p>
5
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Dopłać w panelu</a></div>{{/if}}
6
+ {{> footer}}
@@ -0,0 +1,5 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Payment link — booking {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">We've prepared a payment of <strong>{{currency amount currency}}</strong> for you.</p>
4
+ {{#if paymentLink}}<div style="margin-top:24px;text-align:center;"><a href="{{paymentLink}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Pay now</a></div>{{/if}}
5
+ {{> footer}}
@@ -0,0 +1,5 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Link do płatności — rezerwacja {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Przygotowaliśmy dla Ciebie płatność na kwotę <strong>{{currency amount currency}}</strong>.</p>
4
+ {{#if paymentLink}}<div style="margin-top:24px;text-align:center;"><a href="{{paymentLink}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Zapłać teraz</a></div>{{/if}}
5
+ {{> footer}}
@@ -0,0 +1,10 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Payment received — booking {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Thank you! We've recorded your payment.</p>
4
+ {{#if booking.totals.balance}}
5
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Amount still due: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
6
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Pay the balance</a></div>{{/if}}
7
+ {{else}}
8
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Your booking is paid in full — see you soon!</p>
9
+ {{/if}}
10
+ {{> footer}}
@@ -0,0 +1,10 @@
1
+ {{> header}}
2
+ <h1 style="font-size:20px;font-weight:800;margin:0 0 8px;">Wpłata zaksięgowana — rezerwacja {{booking.number}}</h1>
3
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Dziękujemy! Zaksięgowaliśmy Twoją wpłatę.</p>
4
+ {{#if booking.totals.balance}}
5
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Pozostała kwota do dopłaty: <strong>{{currency booking.totals.balance currency}}</strong>.</p>
6
+ {{#if portalUrl}}<div style="margin-top:24px;text-align:center;"><a href="{{portalUrl}}" style="display:inline-block;background:#5B4A9E;color:#fff;text-decoration:none;padding:10px 18px;border-radius:8px;font-weight:600;">Dopłać w panelu</a></div>{{/if}}
7
+ {{else}}
8
+ <p style="margin:0 0 16px;color:#555566;line-height:1.5;">Rezerwacja jest opłacona w całości — do zobaczenia!</p>
9
+ {{/if}}
10
+ {{> footer}}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Map a per-participant {@link ParticipantPriceResult} into the persisted
3
+ * booking totals + a synthesized per-rule audit list. Pure.
4
+ */
5
+ import type { AppliedRule, Perk } from './pricing.js';
6
+ import type { ParticipantPriceResult } from './participant-pricing.js';
7
+ import type { AdjustmentSums } from './adjustments.js';
8
+ export interface BookingTotals {
9
+ gross: number;
10
+ discount: number;
11
+ /** Σ active manual surcharges (minor units). */
12
+ surcharge: number;
13
+ /** Σ active manual discounts, magnitude (minor units). */
14
+ manualDiscount: number;
15
+ paid: number;
16
+ balance: number;
17
+ perks: Perk[];
18
+ appliedRules: AppliedRule[];
19
+ }
20
+ /** Amount owed = gross − rule discounts − manual discounts + surcharges (≥ 0). Legacy-safe. */
21
+ export declare function netOf(t: Pick<BookingTotals, 'gross' | 'discount' | 'surcharge' | 'manualDiscount'>): number;
22
+ /**
23
+ * Flatten per-participant discounts into audit rules. Package discounts carry a
24
+ * `targetRef` (the person's cheapest trip) so they attach to that item line;
25
+ * early-bird is booking-level (no target).
26
+ */
27
+ export declare function synthesizeAppliedRules(result: ParticipantPriceResult): AppliedRule[];
28
+ /**
29
+ * Build the persisted totals snapshot from a pricing result, manual-adjustment
30
+ * sums, and amount paid. Balance = net − paid, where net folds in surcharges and
31
+ * manual discounts on top of rule discounts.
32
+ */
33
+ export declare function resultToTotals(result: ParticipantPriceResult, sums: AdjustmentSums, paid: number): BookingTotals;
34
+ /**
35
+ * Age-adjusted gross per trip ref = Σ participant line bases on that trip.
36
+ * Drives `booking_items.subtotalSnapshot` so the line gross reflects age pricing.
37
+ */
38
+ export declare function grossByTripRef(result: ParticipantPriceResult): Map<string, number>;
@@ -0,0 +1,61 @@
1
+ /** Amount owed = gross − rule discounts − manual discounts + surcharges (≥ 0). Legacy-safe. */
2
+ export function netOf(t) {
3
+ return Math.max(0, t.gross - t.discount - (t.manualDiscount ?? 0) + (t.surcharge ?? 0));
4
+ }
5
+ /**
6
+ * Flatten per-participant discounts into audit rules. Package discounts carry a
7
+ * `targetRef` (the person's cheapest trip) so they attach to that item line;
8
+ * early-bird is booking-level (no target).
9
+ */
10
+ export function synthesizeAppliedRules(result) {
11
+ const rules = [];
12
+ for (const p of result.participants) {
13
+ if (p.packageDiscount > 0) {
14
+ const rule = {
15
+ ruleId: 'package',
16
+ label: 'Rabat pakietowy',
17
+ amount: p.packageDiscount
18
+ };
19
+ if (p.packageTargetRef)
20
+ rule.targetRef = p.packageTargetRef;
21
+ rules.push(rule);
22
+ }
23
+ if (p.earlyBirdDiscount > 0) {
24
+ rules.push({
25
+ ruleId: 'early-bird',
26
+ label: 'Wcześniejsza rezerwacja',
27
+ amount: p.earlyBirdDiscount
28
+ });
29
+ }
30
+ }
31
+ return rules;
32
+ }
33
+ /**
34
+ * Build the persisted totals snapshot from a pricing result, manual-adjustment
35
+ * sums, and amount paid. Balance = net − paid, where net folds in surcharges and
36
+ * manual discounts on top of rule discounts.
37
+ */
38
+ export function resultToTotals(result, sums, paid) {
39
+ const base = {
40
+ gross: result.gross,
41
+ discount: result.discountTotal,
42
+ surcharge: sums.surcharge,
43
+ manualDiscount: sums.manualDiscount,
44
+ perks: result.perks,
45
+ appliedRules: synthesizeAppliedRules(result)
46
+ };
47
+ return { ...base, paid, balance: Math.max(0, netOf(base) - paid) };
48
+ }
49
+ /**
50
+ * Age-adjusted gross per trip ref = Σ participant line bases on that trip.
51
+ * Drives `booking_items.subtotalSnapshot` so the line gross reflects age pricing.
52
+ */
53
+ export function grossByTripRef(result) {
54
+ const byRef = new Map();
55
+ for (const p of result.participants) {
56
+ for (const line of p.lines) {
57
+ byRef.set(line.tripRef, (byRef.get(line.tripRef) ?? 0) + line.base);
58
+ }
59
+ }
60
+ return byRef;
61
+ }
@@ -0,0 +1,28 @@
1
+ import { type BookingStatus } from './state-machine.js';
2
+ import { type PaymentEntry, type PaymentStatus } from './ledger.js';
3
+ import type { PaymentEvent } from '../shop/types.js';
4
+ /**
5
+ * Pure decision for a payment webhook. Given the event, the targeted payment
6
+ * row, and the booking's other payments, decides the new payment status, the
7
+ * new total paid, the derived booking status, and whether to clear the draft
8
+ * hold. `alreadyApplied` short-circuits idempotently (duplicate webhook for a
9
+ * settled/refunded payment) — the caller returns early without mutating, so the
10
+ * other fields are intentionally a benign snapshot of the current state.
11
+ */
12
+ export declare function applyPaymentEvent(input: {
13
+ event: PaymentEvent;
14
+ payment: {
15
+ status: PaymentStatus;
16
+ amount: number;
17
+ } | null;
18
+ otherPayments: PaymentEntry[];
19
+ gross: number;
20
+ depositRequired: number;
21
+ currentStatus: BookingStatus;
22
+ }): {
23
+ alreadyApplied: boolean;
24
+ newPaymentStatus: PaymentStatus;
25
+ newTotalsPaid: number;
26
+ newStatus: BookingStatus;
27
+ clearHold: boolean;
28
+ };
@@ -0,0 +1,50 @@
1
+ import { deriveStatus } from './state-machine.js';
2
+ import { totalPaid } from './ledger.js';
3
+ /**
4
+ * Pure decision for a payment webhook. Given the event, the targeted payment
5
+ * row, and the booking's other payments, decides the new payment status, the
6
+ * new total paid, the derived booking status, and whether to clear the draft
7
+ * hold. `alreadyApplied` short-circuits idempotently (duplicate webhook for a
8
+ * settled/refunded payment) — the caller returns early without mutating, so the
9
+ * other fields are intentionally a benign snapshot of the current state.
10
+ */
11
+ export function applyPaymentEvent(input) {
12
+ const { event, payment, otherPayments, gross, depositRequired, currentStatus } = input;
13
+ const paidBefore = totalPaid(otherPayments);
14
+ if (!payment || payment.status === 'paid' || payment.status === 'refunded') {
15
+ return {
16
+ alreadyApplied: true,
17
+ newPaymentStatus: payment?.status ?? 'pending',
18
+ newTotalsPaid: paidBefore,
19
+ newStatus: currentStatus,
20
+ clearHold: false
21
+ };
22
+ }
23
+ if (event.status === 'paid') {
24
+ const paid = paidBefore + payment.amount;
25
+ return {
26
+ alreadyApplied: false,
27
+ newPaymentStatus: 'paid',
28
+ newTotalsPaid: paid,
29
+ newStatus: deriveStatus({ gross, paid, depositRequired, current: currentStatus }),
30
+ clearHold: true
31
+ };
32
+ }
33
+ if (event.status === 'paymentRejected') {
34
+ return {
35
+ alreadyApplied: false,
36
+ newPaymentStatus: 'failed',
37
+ newTotalsPaid: paidBefore,
38
+ newStatus: deriveStatus({ gross, paid: paidBefore, depositRequired, current: currentStatus }),
39
+ clearHold: false
40
+ };
41
+ }
42
+ // pending / unknown: no-op
43
+ return {
44
+ alreadyApplied: false,
45
+ newPaymentStatus: 'pending',
46
+ newTotalsPaid: paidBefore,
47
+ newStatus: currentStatus,
48
+ clearHold: false
49
+ };
50
+ }
@@ -1,2 +1,8 @@
1
1
  import 'dotenv/config';
2
- export declare function createUser(): Promise<void>;
2
+ export interface CreateUserOptions {
3
+ name?: string;
4
+ email?: string;
5
+ password?: string;
6
+ role?: string;
7
+ }
8
+ export declare function createUser(opts?: CreateUserOptions): Promise<void>;
@@ -15,37 +15,38 @@ const roleSchema = z.enum(['admin', 'user']);
15
15
  function generatePassword(length = 24) {
16
16
  return crypto.randomBytes(length).toString('base64url').slice(0, length);
17
17
  }
18
- export async function createUser() {
18
+ export async function createUser(opts = {}) {
19
19
  const DATABASE_URL = process.env.DATABASE_URL;
20
20
  if (!DATABASE_URL) {
21
21
  console.error('DATABASE_URL is not set. Check your .env file.');
22
22
  process.exit(1);
23
23
  }
24
- const client = postgres(DATABASE_URL);
25
- const db = drizzle(client);
26
- const auth = betterAuth({
27
- database: drizzleAdapter(db, { provider: 'pg', schema: authSchema }),
28
- emailAndPassword: { enabled: true },
29
- plugins: [admin()]
30
- });
31
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+ // Non-interactive when both name and email are supplied via flags; otherwise
25
+ // prompt only for the fields that weren't provided.
26
+ const nonInteractive = Boolean(opts.name && opts.email);
27
+ let rl;
32
28
  function ask(q) {
29
+ if (!rl)
30
+ rl = readline.createInterface({ input: process.stdin, output: process.stdout });
33
31
  return new Promise((resolve) => rl.question(q, resolve));
34
32
  }
35
- console.log('Please provide the following information to create a new user:');
36
- const name = await ask('Name: ');
33
+ if (!nonInteractive) {
34
+ console.log('Please provide the following information to create a new user:');
35
+ }
36
+ const name = opts.name ?? (await ask('Name: '));
37
37
  const parsedName = nameSchema.safeParse(name);
38
38
  if (!parsedName.success) {
39
39
  console.error(parsedName.error.flatten());
40
40
  process.exit(1);
41
41
  }
42
- const email = await ask('E-mail: ');
42
+ const email = opts.email ?? (await ask('E-mail: '));
43
43
  const parsedEmail = emailSchema.safeParse(email);
44
44
  if (!parsedEmail.success) {
45
45
  console.error(parsedEmail.error.flatten());
46
46
  process.exit(1);
47
47
  }
48
- const passwordInput = await ask('Password (leave empty to generate): ');
48
+ // Flag wins; interactive prompts (empty generate); non-interactive w/o flag → generate.
49
+ const passwordInput = opts.password ?? (nonInteractive ? '' : await ask('Password (leave empty to generate): '));
49
50
  const password = passwordInput.trim() || generatePassword();
50
51
  const wasGenerated = !passwordInput.trim();
51
52
  const parsedPassword = passwordSchema.safeParse(password);
@@ -53,14 +54,21 @@ export async function createUser() {
53
54
  console.error(parsedPassword.error.flatten());
54
55
  process.exit(1);
55
56
  }
56
- const roleInput = await ask('Role (admin/user) [admin]: ');
57
+ const roleInput = opts.role ?? (nonInteractive ? 'admin' : await ask('Role (admin/user) [admin]: '));
57
58
  const role = roleInput.trim() || 'admin';
58
59
  const parsedRole = roleSchema.safeParse(role);
59
60
  if (!parsedRole.success) {
60
61
  console.error('Invalid role. Must be "admin" or "user".');
61
62
  process.exit(1);
62
63
  }
63
- rl.close();
64
+ rl?.close();
65
+ const client = postgres(DATABASE_URL);
66
+ const db = drizzle(client);
67
+ const auth = betterAuth({
68
+ database: drizzleAdapter(db, { provider: 'pg', schema: authSchema }),
69
+ emailAndPassword: { enabled: true },
70
+ plugins: [admin()]
71
+ });
64
72
  const response = await auth.api.createUser({
65
73
  body: {
66
74
  email: parsedEmail.data,
package/dist/cli/index.js CHANGED
@@ -61,6 +61,8 @@ Options:
61
61
  --cms-config <FILE> Path to CMS config (default: src/lib/cms/cms.config.ts)
62
62
  --shop Force shop routes ON (overrides auto-detect)
63
63
  --no-shop Force shop routes OFF
64
+ --booking Force booking routes ON (overrides auto-detect)
65
+ --no-booking Force booking routes OFF
64
66
  --help, -h Show this help
65
67
 
66
68
  Examples:
@@ -90,16 +92,24 @@ Examples:
90
92
  function printCreateUserHelp() {
91
93
  console.log(`includio create-user — create an admin/user account
92
94
 
93
- Interactive prompt — asks for email, password, name, and role. Requires
94
- \`DATABASE_URL\` in the environment (or .env loaded by the project).
95
+ Interactive by default — asks for name, email, password, and role. Pass
96
+ \`--name\` and \`--email\` to run non-interactively (e.g. from another tool);
97
+ any field you omit gets a sensible default. Requires \`DATABASE_URL\` in the
98
+ environment (or .env loaded by the project).
95
99
 
96
100
  Usage: includio create-user [options]
97
101
 
98
102
  Options:
99
- --help, -h Show this help
103
+ --name <NAME> User's display name
104
+ --email <EMAIL> User's e-mail
105
+ --password <PASS> Password (omit → securely generated and printed)
106
+ --role <ROLE> Role: admin | user (default: admin)
107
+ --help, -h Show this help
100
108
 
101
- Example:
109
+ Examples:
102
110
  includio create-user
111
+ includio create-user --name "Jan Kowalski" --email jan@example.com
112
+ includio create-user --name Admin --email admin@example.com --role admin
103
113
  `);
104
114
  }
105
115
  function flagValue(name) {
@@ -124,6 +134,18 @@ function detectShopUsage(cmsConfigPath) {
124
134
  .replace(/\/\/[^\n]*/g, '');
125
135
  return /\bshop\s*:\s*\S/.test(stripped);
126
136
  }
137
+ /**
138
+ * Best-effort detection: scan cms config text for an active `defineBooking(`
139
+ * call (the booking module is registered via `modules: [defineBooking({...})]`).
140
+ * Returns null when the config can't be located.
141
+ */
142
+ function detectBookingUsage(cmsConfigPath) {
143
+ if (!fs.existsSync(cmsConfigPath))
144
+ return null;
145
+ const raw = fs.readFileSync(cmsConfigPath, 'utf-8');
146
+ const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
147
+ return /\bdefineBooking\s*\(/.test(stripped);
148
+ }
127
149
  // Top-level: no command, --help, or --version
128
150
  if (!command || HELP_FLAGS.has(command)) {
129
151
  printTopLevelHelp();
@@ -163,8 +185,29 @@ if (command === 'scaffold' && subcommand === 'admin') {
163
185
  shopSource = `${cmsConfigPath} → ${detected ? 'shop: detected' : 'no shop config'}`;
164
186
  }
165
187
  }
166
- console.log(`Scaffolding admin routes (shop ${shop ? 'enabled' : 'disabled'} — ${shopSource})...\n`);
167
- scaffoldAdmin({ routesDir, force, shop });
188
+ let booking;
189
+ let bookingSource;
190
+ if (args.includes('--booking')) {
191
+ booking = true;
192
+ bookingSource = 'flag --booking';
193
+ }
194
+ else if (args.includes('--no-booking')) {
195
+ booking = false;
196
+ bookingSource = 'flag --no-booking';
197
+ }
198
+ else {
199
+ const detected = detectBookingUsage(cmsConfigPath);
200
+ if (detected === null) {
201
+ booking = false;
202
+ bookingSource = `${cmsConfigPath} not found → off`;
203
+ }
204
+ else {
205
+ booking = detected;
206
+ bookingSource = `${cmsConfigPath} → ${detected ? 'defineBooking detected' : 'no booking'}`;
207
+ }
208
+ }
209
+ console.log(`Scaffolding admin routes (shop ${shop ? 'enabled' : 'disabled'} — ${shopSource}; booking ${booking ? 'enabled' : 'disabled'} — ${bookingSource})...\n`);
210
+ scaffoldAdmin({ routesDir, force, shop, booking });
168
211
  }
169
212
  else if (command === 'install-peers') {
170
213
  if (hasHelpFlag()) {
@@ -179,7 +222,12 @@ else if (command === 'create-user') {
179
222
  printCreateUserHelp();
180
223
  process.exit(0);
181
224
  }
182
- createUser();
225
+ createUser({
226
+ name: flagValue('--name'),
227
+ email: flagValue('--email'),
228
+ password: flagValue('--password'),
229
+ role: flagValue('--role')
230
+ });
183
231
  }
184
232
  else {
185
233
  printTopLevelHelp();
@@ -7,5 +7,11 @@ export interface ScaffoldAdminOptions {
7
7
  * module — keeps the route tree clean and avoids 404 stubs in your app.
8
8
  */
9
9
  shop?: boolean;
10
+ /**
11
+ * Emit booking module routes (api/booking/*, admin booking pages,
12
+ * rezerwacja/[token] portal). Default `true`. Set to `false` for projects
13
+ * without the booking module.
14
+ */
15
+ booking?: boolean;
10
16
  }
11
17
  export declare function scaffoldAdmin(options: ScaffoldAdminOptions): void;
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { getBookingFiles } from './booking.js';
3
4
  const GENERATED_COMMENT = '<!-- Generated by includio-cms -->';
4
5
  const GENERATED_COMMENT_TS = '// Generated by includio-cms';
5
6
  function isShopFile(p) {
@@ -404,9 +405,10 @@ export const POST: RequestHandler = async (event) => {
404
405
  ];
405
406
  }
406
407
  export function scaffoldAdmin(options) {
407
- const { routesDir, force = false, shop = true } = options;
408
- const allFiles = getAdminFiles();
409
- const files = shop ? allFiles : allFiles.filter((f) => !isShopFile(f.path));
408
+ const { routesDir, force = false, shop = true, booking = true } = options;
409
+ const adminFiles = getAdminFiles();
410
+ const withShop = shop ? adminFiles : adminFiles.filter((f) => !isShopFile(f.path));
411
+ const files = booking ? [...withShop, ...getBookingFiles()] : withShop;
410
412
  let created = 0;
411
413
  let skipped = 0;
412
414
  for (const file of files) {
@@ -0,0 +1,12 @@
1
+ export type FileEntry = {
2
+ path: string;
3
+ content: string;
4
+ };
5
+ /** Route prefixes owned by the booking module (for gating in scaffoldAdmin). */
6
+ export declare function isBookingFile(p: string): boolean;
7
+ /**
8
+ * Route files the booking module needs in a client project: public + portal +
9
+ * maintenance API endpoints (server handlers), the admin staff zone pages, and
10
+ * a portal page skeleton on the client SDK (brand it in the client repo).
11
+ */
12
+ export declare function getBookingFiles(): FileEntry[];