includio-cms 0.36.8 → 0.37.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 (259) hide show
  1. package/API.md +179 -3
  2. package/CHANGELOG.md +64 -0
  3. package/DOCS.md +28 -3
  4. package/ROADMAP.md +18 -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 +21 -2
  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/row-actions.svelte +12 -4
  21. package/dist/admin/client/collection/row-actions.svelte.d.ts +1 -0
  22. package/dist/admin/client/collection/status-badge.svelte +39 -3
  23. package/dist/admin/client/collection/status-badge.svelte.d.ts +5 -3
  24. package/dist/admin/client/entry/entry.svelte +26 -9
  25. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +73 -10
  26. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte.d.ts +6 -5
  27. package/dist/admin/client/entry/preview-views-context.svelte.d.ts +22 -0
  28. package/dist/admin/client/entry/preview-views-context.svelte.js +58 -0
  29. package/dist/admin/client/entry/preview-views.d.ts +27 -0
  30. package/dist/admin/client/entry/preview-views.js +33 -0
  31. package/dist/admin/client/index.d.ts +3 -0
  32. package/dist/admin/client/index.js +3 -0
  33. package/dist/admin/client/modules/dashboard-registry.d.ts +7 -0
  34. package/dist/admin/client/modules/dashboard-registry.js +11 -0
  35. package/dist/admin/client/modules/module-dashboard-page.svelte +26 -0
  36. package/dist/admin/client/modules/module-dashboard-page.svelte.d.ts +6 -0
  37. package/dist/admin/client/modules/stat-card.svelte +22 -0
  38. package/dist/admin/client/modules/stat-card.svelte.d.ts +10 -0
  39. package/dist/admin/client/shop/shop-dashboard.svelte +29 -0
  40. package/dist/admin/client/shop/shop-dashboard.svelte.d.ts +3 -0
  41. package/dist/admin/components/fields/relation-field.svelte +11 -2
  42. package/dist/admin/components/icons/icon-map.js +8 -0
  43. package/dist/admin/components/layout/app-sidebar.svelte +45 -14
  44. package/dist/admin/components/layout/context-switcher.svelte +90 -0
  45. package/dist/admin/components/layout/context-switcher.svelte.d.ts +3 -0
  46. package/dist/admin/components/layout/lang.d.ts +4 -0
  47. package/dist/admin/components/layout/lang.js +8 -0
  48. package/dist/admin/components/layout/layout-renderer.svelte +2 -1
  49. package/dist/admin/components/layout/layout-tabs.svelte +14 -0
  50. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +2 -0
  51. package/dist/admin/components/layout/nav-collections.svelte +16 -9
  52. package/dist/admin/components/layout/nav-collections.svelte.d.ts +5 -1
  53. package/dist/admin/components/layout/nav-global.svelte +45 -0
  54. package/dist/admin/components/layout/nav-global.svelte.d.ts +3 -0
  55. package/dist/admin/components/layout/nav-main.svelte +8 -32
  56. package/dist/admin/components/layout/nav-main.svelte.d.ts +6 -1
  57. package/dist/admin/components/layout/nav-module.svelte +57 -0
  58. package/dist/admin/components/layout/nav-module.svelte.d.ts +7 -0
  59. package/dist/admin/components/layout/nav-section.svelte +1 -1
  60. package/dist/admin/components/layout/nav-section.svelte.d.ts +1 -1
  61. package/dist/admin/components/layout/nav-singletons.svelte +14 -7
  62. package/dist/admin/components/layout/nav-singletons.svelte.d.ts +5 -1
  63. package/dist/admin/remote/booking.remote.d.ts +244 -0
  64. package/dist/admin/remote/booking.remote.js +283 -0
  65. package/dist/admin/remote/entry.remote.d.ts +3 -0
  66. package/dist/admin/remote/entry.remote.js +14 -3
  67. package/dist/admin/remote/index.d.ts +2 -0
  68. package/dist/admin/remote/index.js +2 -0
  69. package/dist/admin/remote/modules.remote.d.ts +10 -0
  70. package/dist/admin/remote/modules.remote.js +14 -0
  71. package/dist/admin/state/active-context-resolve.d.ts +37 -0
  72. package/dist/admin/state/active-context-resolve.js +66 -0
  73. package/dist/admin/state/active-context.svelte.d.ts +8 -0
  74. package/dist/admin/state/active-context.svelte.js +27 -0
  75. package/dist/booking/adjustments.d.ts +33 -0
  76. package/dist/booking/adjustments.js +26 -0
  77. package/dist/booking/capacity.d.ts +12 -0
  78. package/dist/booking/capacity.js +10 -0
  79. package/dist/booking/client/index.d.ts +39 -0
  80. package/dist/booking/client/index.js +44 -0
  81. package/dist/booking/client/use-booking.svelte.d.ts +25 -0
  82. package/dist/booking/client/use-booking.svelte.js +56 -0
  83. package/dist/booking/config.d.ts +156 -0
  84. package/dist/booking/config.js +36 -0
  85. package/dist/booking/draft.d.ts +77 -0
  86. package/dist/booking/draft.js +67 -0
  87. package/dist/booking/expiry-logic.d.ts +30 -0
  88. package/dist/booking/expiry-logic.js +33 -0
  89. package/dist/booking/index.d.ts +18 -0
  90. package/dist/booking/index.js +52 -0
  91. package/dist/booking/ledger.d.ts +46 -0
  92. package/dist/booking/ledger.js +60 -0
  93. package/dist/booking/number.d.ts +2 -0
  94. package/dist/booking/number.js +11 -0
  95. package/dist/booking/participant-fields/access.d.ts +10 -0
  96. package/dist/booking/participant-fields/access.js +14 -0
  97. package/dist/booking/participant-fields/age.d.ts +12 -0
  98. package/dist/booking/participant-fields/age.js +28 -0
  99. package/dist/booking/participant-fields/completeness.d.ts +8 -0
  100. package/dist/booking/participant-fields/completeness.js +13 -0
  101. package/dist/booking/participant-fields/group.d.ts +6 -0
  102. package/dist/booking/participant-fields/group.js +15 -0
  103. package/dist/booking/participant-fields/index.d.ts +6 -0
  104. package/dist/booking/participant-fields/index.js +6 -0
  105. package/dist/booking/participant-fields/types.d.ts +50 -0
  106. package/dist/booking/participant-fields/types.js +5 -0
  107. package/dist/booking/participant-fields/validate.d.ts +7 -0
  108. package/dist/booking/participant-fields/validate.js +44 -0
  109. package/dist/booking/participant-pricing.d.ts +93 -0
  110. package/dist/booking/participant-pricing.js +91 -0
  111. package/dist/booking/participants-view.d.ts +32 -0
  112. package/dist/booking/participants-view.js +22 -0
  113. package/dist/booking/payment-context.d.ts +19 -0
  114. package/dist/booking/payment-context.js +29 -0
  115. package/dist/booking/pricing-inputs.d.ts +28 -0
  116. package/dist/booking/pricing-inputs.js +35 -0
  117. package/dist/booking/pricing.d.ts +86 -0
  118. package/dist/booking/pricing.js +125 -0
  119. package/dist/booking/recompute.d.ts +22 -0
  120. package/dist/booking/recompute.js +31 -0
  121. package/dist/booking/reminders-schedule.d.ts +11 -0
  122. package/dist/booking/reminders-schedule.js +28 -0
  123. package/dist/booking/server/adjustments.d.ts +27 -0
  124. package/dist/booking/server/adjustments.js +136 -0
  125. package/dist/booking/server/bookings.d.ts +45 -0
  126. package/dist/booking/server/bookings.js +288 -0
  127. package/dist/booking/server/catalog.d.ts +9 -0
  128. package/dist/booking/server/catalog.js +20 -0
  129. package/dist/booking/server/db.d.ts +7 -0
  130. package/dist/booking/server/db.js +18 -0
  131. package/dist/booking/server/email.d.ts +23 -0
  132. package/dist/booking/server/email.js +59 -0
  133. package/dist/booking/server/emailTemplateRegistry.d.ts +29 -0
  134. package/dist/booking/server/emailTemplateRegistry.js +125 -0
  135. package/dist/booking/server/http/create-handler.d.ts +5 -0
  136. package/dist/booking/server/http/create-handler.js +68 -0
  137. package/dist/booking/server/http/index.d.ts +4 -0
  138. package/dist/booking/server/http/index.js +4 -0
  139. package/dist/booking/server/http/maintenance-handler.d.ts +9 -0
  140. package/dist/booking/server/http/maintenance-handler.js +36 -0
  141. package/dist/booking/server/http/portal-handler.d.ts +13 -0
  142. package/dist/booking/server/http/portal-handler.js +99 -0
  143. package/dist/booking/server/http/webhook-handler.d.ts +9 -0
  144. package/dist/booking/server/http/webhook-handler.js +39 -0
  145. package/dist/booking/server/index.d.ts +12 -0
  146. package/dist/booking/server/index.js +14 -0
  147. package/dist/booking/server/maintenance.d.ts +28 -0
  148. package/dist/booking/server/maintenance.js +134 -0
  149. package/dist/booking/server/participants.d.ts +26 -0
  150. package/dist/booking/server/participants.js +92 -0
  151. package/dist/booking/server/payments.d.ts +34 -0
  152. package/dist/booking/server/payments.js +207 -0
  153. package/dist/booking/server/portal-session.d.ts +16 -0
  154. package/dist/booking/server/portal-session.js +42 -0
  155. package/dist/booking/server/portal.d.ts +207 -0
  156. package/dist/booking/server/portal.js +234 -0
  157. package/dist/booking/server/refunds.d.ts +7 -0
  158. package/dist/booking/server/refunds.js +58 -0
  159. package/dist/booking/server/reminders.d.ts +9 -0
  160. package/dist/booking/server/reminders.js +24 -0
  161. package/dist/booking/server/validation.d.ts +17 -0
  162. package/dist/booking/server/validation.js +55 -0
  163. package/dist/booking/state-machine.d.ts +41 -0
  164. package/dist/booking/state-machine.js +73 -0
  165. package/dist/booking/templates/_partials/footer.en.html +4 -0
  166. package/dist/booking/templates/_partials/footer.pl.html +4 -0
  167. package/dist/booking/templates/_partials/header.en.html +4 -0
  168. package/dist/booking/templates/_partials/header.pl.html +4 -0
  169. package/dist/booking/templates/balance-due.en.html +10 -0
  170. package/dist/booking/templates/balance-due.pl.html +10 -0
  171. package/dist/booking/templates/balance-overdue-cancelled.en.html +5 -0
  172. package/dist/booking/templates/balance-overdue-cancelled.pl.html +5 -0
  173. package/dist/booking/templates/booking-received.en.html +9 -0
  174. package/dist/booking/templates/booking-received.pl.html +9 -0
  175. package/dist/booking/templates/deposit-paid.en.html +6 -0
  176. package/dist/booking/templates/deposit-paid.pl.html +6 -0
  177. package/dist/booking/templates/payment-link.en.html +5 -0
  178. package/dist/booking/templates/payment-link.pl.html +5 -0
  179. package/dist/booking/templates/payment-received.en.html +10 -0
  180. package/dist/booking/templates/payment-received.pl.html +10 -0
  181. package/dist/booking/totals.d.ts +38 -0
  182. package/dist/booking/totals.js +61 -0
  183. package/dist/booking/webhook-logic.d.ts +28 -0
  184. package/dist/booking/webhook-logic.js +50 -0
  185. package/dist/cli/create-user.d.ts +7 -1
  186. package/dist/cli/create-user.js +23 -15
  187. package/dist/cli/index.js +55 -7
  188. package/dist/cli/scaffold/admin.d.ts +6 -0
  189. package/dist/cli/scaffold/admin.js +5 -3
  190. package/dist/cli/scaffold/booking.d.ts +12 -0
  191. package/dist/cli/scaffold/booking.js +103 -0
  192. package/dist/core/cms.d.ts +24 -9
  193. package/dist/core/cms.js +72 -34
  194. package/dist/core/config/validatePreviews.d.ts +12 -0
  195. package/dist/core/config/validatePreviews.js +90 -0
  196. package/dist/core/fields/dotPath.d.ts +4 -0
  197. package/dist/core/fields/dotPath.js +18 -0
  198. package/dist/core/fields/slugPath.d.ts +9 -1
  199. package/dist/core/fields/slugPath.js +16 -2
  200. package/dist/core/fields/slugify.d.ts +3 -0
  201. package/dist/core/fields/slugify.js +4 -0
  202. package/dist/core/server/entries/operations/duplicate.d.ts +10 -0
  203. package/dist/core/server/entries/operations/duplicate.js +73 -0
  204. package/dist/core/server/entries/operations/slugNormalize.d.ts +8 -0
  205. package/dist/core/server/entries/operations/slugNormalize.js +20 -0
  206. package/dist/core/server/entries/operations/slugUniqueness.d.ts +17 -0
  207. package/dist/core/server/entries/operations/slugUniqueness.js +39 -0
  208. package/dist/core/server/fields/resolveRelationFields.d.ts +11 -2
  209. package/dist/core/server/fields/resolveRelationFields.js +12 -3
  210. package/dist/core/server/generator/generator.js +7 -2
  211. package/dist/db-postgres/schema/booking/bookingAdjustments.d.ts +186 -0
  212. package/dist/db-postgres/schema/booking/bookingAdjustments.js +22 -0
  213. package/dist/db-postgres/schema/booking/bookingItemPersons.d.ts +117 -0
  214. package/dist/db-postgres/schema/booking/bookingItemPersons.js +22 -0
  215. package/dist/db-postgres/schema/booking/bookingItems.d.ts +181 -0
  216. package/dist/db-postgres/schema/booking/bookingItems.js +22 -0
  217. package/dist/db-postgres/schema/booking/bookingPayments.d.ts +204 -0
  218. package/dist/db-postgres/schema/booking/bookingPayments.js +25 -0
  219. package/dist/db-postgres/schema/booking/bookingPersons.d.ts +99 -0
  220. package/dist/db-postgres/schema/booking/bookingPersons.js +17 -0
  221. package/dist/db-postgres/schema/booking/bookingReminders.d.ts +131 -0
  222. package/dist/db-postgres/schema/booking/bookingReminders.js +18 -0
  223. package/dist/db-postgres/schema/booking/bookings.d.ts +307 -0
  224. package/dist/db-postgres/schema/booking/bookings.js +40 -0
  225. package/dist/db-postgres/schema/booking/index.d.ts +7 -0
  226. package/dist/db-postgres/schema/booking/index.js +7 -0
  227. package/dist/db-postgres/schema/index.d.ts +1 -0
  228. package/dist/db-postgres/schema/index.js +1 -0
  229. package/dist/db-postgres/schema-booking.d.ts +1 -0
  230. package/dist/db-postgres/schema-booking.js +1 -0
  231. package/dist/server/security/rate-limit-handler.d.ts +19 -0
  232. package/dist/server/security/rate-limit-handler.js +34 -0
  233. package/dist/shop/adapters/payu/client.js +3 -1
  234. package/dist/shop/adapters/payu/index.js +37 -17
  235. package/dist/shop/adapters/payu/payload.d.ts +6 -0
  236. package/dist/shop/adapters/payu/payload.js +1 -1
  237. package/dist/shop/adapters/stripe/index.js +36 -21
  238. package/dist/shop/index.d.ts +11 -2
  239. package/dist/shop/index.js +43 -1
  240. package/dist/shop/rate-limit.d.ts +1 -9
  241. package/dist/shop/rate-limit.js +3 -28
  242. package/dist/shop/types.d.ts +19 -0
  243. package/dist/sveltekit/config.d.ts +4 -0
  244. package/dist/sveltekit/config.js +6 -0
  245. package/dist/types/cms.d.ts +15 -1
  246. package/dist/types/cms.schema.d.ts +96 -2
  247. package/dist/types/cms.schema.js +84 -1
  248. package/dist/types/config.d.ts +33 -0
  249. package/dist/types/layout.d.ts +6 -0
  250. package/dist/types/modules.d.ts +133 -0
  251. package/dist/types/modules.js +27 -0
  252. package/dist/updates/0.36.9/index.d.ts +2 -0
  253. package/dist/updates/0.36.9/index.js +12 -0
  254. package/dist/updates/0.37.0/index.d.ts +2 -0
  255. package/dist/updates/0.37.0/index.js +52 -0
  256. package/dist/updates/index.js +5 -1
  257. package/package.json +19 -1
  258. package/dist/admin/components/layout/nav-shop.svelte +0 -33
  259. package/dist/admin/components/layout/nav-shop.svelte.d.ts +0 -3
@@ -1,26 +1,59 @@
1
1
  <script lang="ts">
2
2
  import * as ToggleGroup from '../../../../components/ui/toggle-group/index.js';
3
+ import * as Select from '../../../../components/ui/select/index.js';
3
4
  import Button from '../../../../components/ui/button/button.svelte';
4
5
  import DeviceTablet from '@tabler/icons-svelte/icons/device-tablet';
5
6
  import DeviceMobile from '@tabler/icons-svelte/icons/device-mobile';
6
7
  import DeviceDesktop from '@tabler/icons-svelte/icons/device-desktop';
7
8
  import ArrowsMaximize from '@tabler/icons-svelte/icons/arrows-maximize';
8
9
  import Link from '@tabler/icons-svelte/icons/link';
9
- import type { DbEntryVersion } from '../../../../types/entries.js';
10
+ import type { PreviewView } from '../../../../types/config.js';
11
+ import { getLocalizedLabel } from '../../../utils/collectionLabel.js';
12
+ import { useInterfaceLanguage } from '../../../state/interface-language.svelte.js';
10
13
  import type { ElementSize } from 'runed';
11
14
 
12
15
  type SizePreset = 'responsive' | 'desktop' | 'tablet' | 'mobile';
13
16
 
14
17
  type Props = {
15
- collection: { previewUrl?: string };
16
- editingEntry: DbEntryVersion;
18
+ /** Fully built preview URL of the active view. */
19
+ previewSrc: string;
20
+ views: PreviewView[];
21
+ activeName: string;
22
+ onSelectView: (name: string) => void;
17
23
  previewIframe?: HTMLIFrameElement;
18
24
  sizePreset: SizePreset;
19
25
  size: ElementSize;
20
26
  el?: HTMLElement;
21
27
  };
22
28
 
23
- let { collection, editingEntry, previewIframe = $bindable(), sizePreset = $bindable('responsive'), size, el = $bindable() }: Props = $props();
29
+ let {
30
+ previewSrc,
31
+ views,
32
+ activeName,
33
+ onSelectView,
34
+ previewIframe = $bindable(),
35
+ sizePreset = $bindable('responsive'),
36
+ size,
37
+ el = $bindable()
38
+ }: Props = $props();
39
+
40
+ const interfaceLanguage = useInterfaceLanguage();
41
+
42
+ const lang = {
43
+ pl: { view: 'Widok', openInNewTab: 'Otwórz podgląd w nowej karcie' },
44
+ en: { view: 'View', openInNewTab: 'Open preview in a new tab' }
45
+ } as const;
46
+
47
+ const t = $derived(lang[interfaceLanguage.current as 'pl' | 'en'] ?? lang.en);
48
+
49
+ function viewLabel(view: PreviewView): string {
50
+ return getLocalizedLabel(view.label, interfaceLanguage.current) || view.name;
51
+ }
52
+
53
+ const activeLabel = $derived.by(() => {
54
+ const view = views.find((v) => v.name === activeName) ?? views[0];
55
+ return view ? viewLabel(view) : '';
56
+ });
24
57
 
25
58
  const sizePresets = {
26
59
  responsive: null,
@@ -61,8 +94,36 @@
61
94
  </script>
62
95
 
63
96
  <div class="flex h-full flex-col overflow-hidden">
64
- <div class="flex items-center justify-center gap-2 border-b px-2 py-1.5">
65
- <ToggleGroup.Root type="single" bind:value={sizePreset} role="radiogroup">
97
+ <!--
98
+ Trzy strefy, nie jeden ciąg: kontekst (jaki widok) przy krawędzi wiodącej,
99
+ prezentacja (rozmiar urządzenia) na środku płótna, akcja poboczna (otwórz
100
+ w nowej karcie) przy końcowej. Grid `1fr auto 1fr` trzyma przełączniki
101
+ urządzeń dokładnie na środku niezależnie od długości nazwy widoku.
102
+ -->
103
+ <div class="grid grid-cols-[1fr_auto_1fr] items-center gap-2 border-b px-2 py-1.5">
104
+ <div class="justify-self-start">
105
+ {#if views.length > 1}
106
+ <Select.Root type="single" value={activeName} onValueChange={onSelectView}>
107
+ <Select.Trigger
108
+ class="hover:bg-accent hover:text-accent-foreground h-8 w-auto gap-1.5 border-0 bg-transparent px-2 text-sm font-medium shadow-none"
109
+ aria-label={t.view}
110
+ >
111
+ {activeLabel}
112
+ </Select.Trigger>
113
+ <Select.Content align="start">
114
+ {#each views as view (view.name)}
115
+ <Select.Item value={view.name} label={viewLabel(view)} />
116
+ {/each}
117
+ </Select.Content>
118
+ </Select.Root>
119
+ {/if}
120
+ </div>
121
+ <ToggleGroup.Root
122
+ type="single"
123
+ bind:value={sizePreset}
124
+ role="radiogroup"
125
+ class="justify-self-center"
126
+ >
66
127
  <ToggleGroup.Item value="responsive" aria-label="Responsive view">
67
128
  <ArrowsMaximize class="size-4" />
68
129
  </ToggleGroup.Item>
@@ -79,8 +140,10 @@
79
140
  <Button
80
141
  variant="ghost"
81
142
  size="icon"
82
- href="{collection.previewUrl}?preview={editingEntry.id}"
143
+ href={previewSrc}
83
144
  target="_blank"
145
+ aria-label={t.openInNewTab}
146
+ class="justify-self-end"
84
147
  >
85
148
  <Link class="size-4" />
86
149
  </Button>
@@ -90,7 +153,7 @@
90
153
  <iframe
91
154
  bind:this={previewIframe}
92
155
  class="h-full w-full border-0"
93
- src="{collection.previewUrl}?preview={editingEntry.id}"
156
+ src={previewSrc}
94
157
  title="preview"
95
158
  ></iframe>
96
159
  {:else if sizePreset === 'desktop'}
@@ -101,7 +164,7 @@
101
164
  class="shrink-0 border-0"
102
165
  width={sizePresets.desktop[0]}
103
166
  height={sizePresets.desktop[1]}
104
- src="{collection.previewUrl}?preview={editingEntry.id}"
167
+ src={previewSrc}
105
168
  title="preview"
106
169
  ></iframe>
107
170
  </div>
@@ -142,7 +205,7 @@
142
205
  "
143
206
  width={iw}
144
207
  height={ih}
145
- src="{collection.previewUrl}?preview={editingEntry.id}"
208
+ src={previewSrc}
146
209
  title="preview"
147
210
  ></iframe>
148
211
  </div>
@@ -1,11 +1,12 @@
1
- import type { DbEntryVersion } from '../../../../types/entries.js';
1
+ import type { PreviewView } from '../../../../types/config.js';
2
2
  import type { ElementSize } from 'runed';
3
3
  type SizePreset = 'responsive' | 'desktop' | 'tablet' | 'mobile';
4
4
  type Props = {
5
- collection: {
6
- previewUrl?: string;
7
- };
8
- editingEntry: DbEntryVersion;
5
+ /** Fully built preview URL of the active view. */
6
+ previewSrc: string;
7
+ views: PreviewView[];
8
+ activeName: string;
9
+ onSelectView: (name: string) => void;
9
10
  previewIframe?: HTMLIFrameElement;
10
11
  sizePreset: SizePreset;
11
12
  size: ElementSize;
@@ -0,0 +1,22 @@
1
+ import type { PreviewView } from '../../../types/config.js';
2
+ /**
3
+ * Holds which preview view is showing, plus the per-tab memory of manual picks:
4
+ * a view the user selected by hand on a tab wins over that tab's `preview`
5
+ * binding when they come back to it. Lives as long as the open entry — nothing
6
+ * is persisted to the URL or the database.
7
+ */
8
+ export declare function createPreviewViewsContext(views: PreviewView[]): {
9
+ readonly views: PreviewView[];
10
+ readonly activeName: string;
11
+ readonly activeView: PreviewView | undefined;
12
+ /** Manual pick — remember it for the tab the user is on. */
13
+ select(name: string): void;
14
+ /** A tab became active (on mount too, so a `?tab=` deep-link lands right). */
15
+ onTabActivate(tabValue: string, tabPreview?: string): void;
16
+ };
17
+ export type PreviewViewsContext = ReturnType<typeof createPreviewViewsContext>;
18
+ /**
19
+ * Optional — layout components also render outside the entry editor (Storybook,
20
+ * forms), where no preview context exists.
21
+ */
22
+ export declare function getPreviewViewsContext(): PreviewViewsContext | undefined;
@@ -0,0 +1,58 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { defaultView, findView, resolveActiveView } from './preview-views.js';
3
+ const PREVIEW_VIEWS_KEY = Symbol('preview-views');
4
+ /**
5
+ * Holds which preview view is showing, plus the per-tab memory of manual picks:
6
+ * a view the user selected by hand on a tab wins over that tab's `preview`
7
+ * binding when they come back to it. Lives as long as the open entry — nothing
8
+ * is persisted to the URL or the database.
9
+ */
10
+ export function createPreviewViewsContext(views) {
11
+ let activeName = $state(defaultView(views)?.name ?? '');
12
+ let activeTab = $state('');
13
+ const overrides = new Map();
14
+ const ctx = {
15
+ get views() {
16
+ return views;
17
+ },
18
+ get activeName() {
19
+ return activeName;
20
+ },
21
+ get activeView() {
22
+ return findView(views, activeName) ?? defaultView(views);
23
+ },
24
+ /** Manual pick — remember it for the tab the user is on. */
25
+ select(name) {
26
+ if (!findView(views, name))
27
+ return;
28
+ activeName = name;
29
+ if (activeTab)
30
+ overrides.set(activeTab, name);
31
+ },
32
+ /** A tab became active (on mount too, so a `?tab=` deep-link lands right). */
33
+ onTabActivate(tabValue, tabPreview) {
34
+ activeTab = tabValue;
35
+ activeName = resolveActiveView({
36
+ views,
37
+ current: activeName,
38
+ tabValue,
39
+ tabPreview,
40
+ overrides
41
+ });
42
+ }
43
+ };
44
+ setContext(PREVIEW_VIEWS_KEY, ctx);
45
+ return ctx;
46
+ }
47
+ /**
48
+ * Optional — layout components also render outside the entry editor (Storybook,
49
+ * forms), where no preview context exists.
50
+ */
51
+ export function getPreviewViewsContext() {
52
+ try {
53
+ return getContext(PREVIEW_VIEWS_KEY);
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
@@ -0,0 +1,27 @@
1
+ import type { PreviewView } from '../../../types/config.js';
2
+ /** The subset of a collection/single config this module cares about. */
3
+ export type PreviewSource = {
4
+ previewUrl?: string;
5
+ previews?: PreviewView[];
6
+ };
7
+ /**
8
+ * Collapse the two config shapes into one: `previewUrl` becomes a single view
9
+ * named `default`, so no caller has to know about the shorthand.
10
+ */
11
+ export declare function normalizePreviews(config: PreviewSource): PreviewView[];
12
+ /** The view marked `default: true`, else the first one. */
13
+ export declare function defaultView(views: PreviewView[]): PreviewView | undefined;
14
+ export declare function findView(views: PreviewView[], name: string): PreviewView | undefined;
15
+ /** `{url}?preview={entryId}&view={name}` — respects a query string already present in `url`. */
16
+ export declare function buildPreviewUrl(view: PreviewView, entryId: string): string;
17
+ /**
18
+ * Rule (c) — per-tab memory. A view the user picked by hand on this tab beats
19
+ * the tab's own binding; a tab without a binding leaves the current view alone.
20
+ */
21
+ export declare function resolveActiveView(params: {
22
+ views: PreviewView[];
23
+ current: string;
24
+ tabValue: string;
25
+ tabPreview?: string;
26
+ overrides: Map<string, string>;
27
+ }): string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Collapse the two config shapes into one: `previewUrl` becomes a single view
3
+ * named `default`, so no caller has to know about the shorthand.
4
+ */
5
+ export function normalizePreviews(config) {
6
+ if (config.previews?.length)
7
+ return config.previews;
8
+ if (config.previewUrl)
9
+ return [{ name: 'default', url: config.previewUrl }];
10
+ return [];
11
+ }
12
+ /** The view marked `default: true`, else the first one. */
13
+ export function defaultView(views) {
14
+ return views.find((v) => v.default) ?? views[0];
15
+ }
16
+ export function findView(views, name) {
17
+ return views.find((v) => v.name === name);
18
+ }
19
+ /** `{url}?preview={entryId}&view={name}` — respects a query string already present in `url`. */
20
+ export function buildPreviewUrl(view, entryId) {
21
+ const sep = view.url.includes('?') ? '&' : '?';
22
+ return `${view.url}${sep}preview=${encodeURIComponent(entryId)}&view=${encodeURIComponent(view.name)}`;
23
+ }
24
+ /**
25
+ * Rule (c) — per-tab memory. A view the user picked by hand on this tab beats
26
+ * the tab's own binding; a tab without a binding leaves the current view alone.
27
+ */
28
+ export function resolveActiveView(params) {
29
+ const next = params.overrides.get(params.tabValue) ?? params.tabPreview;
30
+ if (next && findView(params.views, next))
31
+ return next;
32
+ return params.current;
33
+ }
@@ -22,6 +22,9 @@ export { default as ShopOrderDetailPage } from './shop/shop-order-detail-page.sv
22
22
  export { default as ShopCouponsListPage } from './shop/coupons-list-page.svelte';
23
23
  export { default as ShopCouponNewPage } from './shop/coupon-new-page.svelte';
24
24
  export { default as ShopCouponEditPage } from './shop/coupon-edit-page.svelte';
25
+ export { default as BookingReservationsPage } from './booking/reservations-page.svelte';
26
+ export { default as BookingReservationDetailPage } from './booking/reservation-detail-page.svelte';
27
+ export { default as ModuleDashboardPage } from './modules/module-dashboard-page.svelte';
25
28
  export * from '../helpers/index.js';
26
29
  export * from '../ui/index.js';
27
30
  export { default as DataTable } from './collection/data-table.svelte';
@@ -22,6 +22,9 @@ export { default as ShopOrderDetailPage } from './shop/shop-order-detail-page.sv
22
22
  export { default as ShopCouponsListPage } from './shop/coupons-list-page.svelte';
23
23
  export { default as ShopCouponNewPage } from './shop/coupon-new-page.svelte';
24
24
  export { default as ShopCouponEditPage } from './shop/coupon-edit-page.svelte';
25
+ export { default as BookingReservationsPage } from './booking/reservations-page.svelte';
26
+ export { default as BookingReservationDetailPage } from './booking/reservation-detail-page.svelte';
27
+ export { default as ModuleDashboardPage } from './modules/module-dashboard-page.svelte';
25
28
  // Folded from `./admin/helpers` (dropped as separate export in 0.20.0)
26
29
  export * from '../helpers/index.js';
27
30
  // Folded from `./admin/ui` (dropped as separate export in 0.20.0)
@@ -0,0 +1,7 @@
1
+ import type { Component } from 'svelte';
2
+ /**
3
+ * Client-side registry of per-module dashboard components, keyed by module id.
4
+ * Dashboards live here (not in the serializable {@link import('../../../types/modules.js').CMSModule}
5
+ * descriptor) because Svelte components cannot cross the config/remote boundary.
6
+ */
7
+ export declare const moduleDashboards: Record<string, Component>;
@@ -0,0 +1,11 @@
1
+ import ShopDashboard from '../shop/shop-dashboard.svelte';
2
+ import BookingDashboard from '../booking/booking-dashboard.svelte';
3
+ /**
4
+ * Client-side registry of per-module dashboard components, keyed by module id.
5
+ * Dashboards live here (not in the serializable {@link import('../../../types/modules.js').CMSModule}
6
+ * descriptor) because Svelte components cannot cross the config/remote boundary.
7
+ */
8
+ export const moduleDashboards = {
9
+ shop: ShopDashboard,
10
+ booking: BookingDashboard
11
+ };
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import { getRemotes } from '../../../sveltekit/index.js';
3
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
4
+ import { getLocalizedLabel } from '../../utils/collectionLabel.js';
5
+ import PageHeader from '../../components/layout/page-header.svelte';
6
+ import { moduleDashboards } from './dashboard-registry.js';
7
+ import type { Localized } from '../../../types/languages.js';
8
+
9
+ let { moduleId }: { moduleId: string } = $props();
10
+
11
+ const remotes = getRemotes();
12
+ const interfaceLanguage = useInterfaceLanguage();
13
+
14
+ const modulesQuery = $derived(remotes.getModules());
15
+ const mod = $derived((modulesQuery.current ?? []).find((m) => m.id === moduleId) ?? null);
16
+ const title = $derived(
17
+ mod ? getLocalizedLabel(mod.label as Localized, interfaceLanguage.current) || moduleId : moduleId
18
+ );
19
+ const Dashboard = $derived(moduleDashboards[moduleId]);
20
+ </script>
21
+
22
+ <PageHeader {title} />
23
+
24
+ {#if Dashboard}
25
+ <Dashboard />
26
+ {/if}
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ moduleId: string;
3
+ };
4
+ declare const ModuleDashboardPage: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type ModuleDashboardPage = ReturnType<typeof ModuleDashboardPage>;
6
+ export default ModuleDashboardPage;
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ let {
5
+ label,
6
+ value,
7
+ hint,
8
+ children
9
+ }: {
10
+ label: string;
11
+ value: string | number;
12
+ hint?: string;
13
+ children?: Snippet;
14
+ } = $props();
15
+ </script>
16
+
17
+ <div class="border-border bg-surface/40 flex flex-col gap-1 rounded-xl border p-4">
18
+ <span class="text-muted-foreground text-xs font-medium tracking-wide uppercase">{label}</span>
19
+ <span class="text-foreground text-2xl font-extrabold tabular-nums">{value}</span>
20
+ {#if hint}<span class="text-muted-foreground text-xs">{hint}</span>{/if}
21
+ {#if children}{@render children()}{/if}
22
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ label: string;
4
+ value: string | number;
5
+ hint?: string;
6
+ children?: Snippet;
7
+ };
8
+ declare const StatCard: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type StatCard = ReturnType<typeof StatCard>;
10
+ export default StatCard;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { getRemotes } from '../../../sveltekit/index.js';
3
+ import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
4
+ import StatCard from '../modules/stat-card.svelte';
5
+ import { Button } from '../../../components/ui/button/index.js';
6
+
7
+ const remotes = getRemotes();
8
+ const interfaceLanguage = useInterfaceLanguage();
9
+
10
+ const ordersQuery = $derived(remotes.listOrdersAdmin({ limit: 1 }));
11
+ const totalOrders = $derived(ordersQuery.current?.total ?? 0);
12
+
13
+ const t = $derived(
14
+ interfaceLanguage.current === 'pl'
15
+ ? { orders: 'Zamówienia', manageProducts: 'Produkty', manageOrders: 'Zamówienia', coupons: 'Kody rabatowe' }
16
+ : { orders: 'Orders', manageProducts: 'Products', manageOrders: 'Orders', coupons: 'Coupons' }
17
+ );
18
+ </script>
19
+
20
+ <div class="flex flex-col gap-4 px-4 pb-6 sm:px-6">
21
+ <div class="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
22
+ <StatCard label={t.orders} value={ordersQuery.ready ? totalOrders : '—'} />
23
+ </div>
24
+ <div class="flex flex-wrap gap-2">
25
+ <Button href="/admin/shop/products" variant="outline" size="sm">{t.manageProducts}</Button>
26
+ <Button href="/admin/shop/orders" variant="outline" size="sm">{t.manageOrders}</Button>
27
+ <Button href="/admin/shop/coupons" variant="outline" size="sm">{t.coupons}</Button>
28
+ </div>
29
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const ShopDashboard: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ShopDashboard = ReturnType<typeof ShopDashboard>;
3
+ export default ShopDashboard;
@@ -422,8 +422,15 @@
422
422
  <Command.Empty>{t.noResults}</Command.Empty>
423
423
  <Command.Group>
424
424
  {#each pickerOptions as option (option.id)}
425
+ <!--
426
+ `value` to tożsamość pozycji w Command (filtrowanie, nawigacja
427
+ klawiaturą, podświetlenie) — musi być unikalna, inaczej dwa wpisy
428
+ o tej samej nazwie zlewają się w jeden. Szukanie po nazwie zapewnia
429
+ `keywords` (domyślny scorer skleja value + keywords).
430
+ -->
425
431
  <Command.Item
426
- value={option.label}
432
+ value={option.id}
433
+ keywords={[option.label]}
427
434
  onSelect={() => toggleItem(option.id)}
428
435
  class="flex items-center gap-2"
429
436
  >
@@ -523,8 +530,10 @@
523
530
  <Command.Empty>{t.noResults}</Command.Empty>
524
531
  <Command.Group>
525
532
  {#each pickerOptions as option (option.id)}
533
+ <!-- Tożsamość po id, szukanie po nazwie — patrz komentarz w wariancie multi. -->
526
534
  <Command.Item
527
- value={option.label}
535
+ value={option.id}
536
+ keywords={[option.label]}
528
537
  onSelect={() => {
529
538
  value = option.id;
530
539
  closePopoverAndFocus();
@@ -24,6 +24,10 @@ import IconTag from '@tabler/icons-svelte/icons/tag';
24
24
  import IconTags from '@tabler/icons-svelte/icons/tags';
25
25
  import IconReceipt from '@tabler/icons-svelte/icons/receipt';
26
26
  import IconCreditCard from '@tabler/icons-svelte/icons/credit-card';
27
+ import IconBuildingStore from '@tabler/icons-svelte/icons/building-store';
28
+ import IconPackage from '@tabler/icons-svelte/icons/package';
29
+ import IconTruck from '@tabler/icons-svelte/icons/truck';
30
+ import IconTicket from '@tabler/icons-svelte/icons/ticket';
27
31
  // Media
28
32
  import IconPhoto from '@tabler/icons-svelte/icons/photo';
29
33
  import IconCamera from '@tabler/icons-svelte/icons/camera';
@@ -89,6 +93,10 @@ export const iconMap = {
89
93
  // E-commerce
90
94
  'shopping-cart': IconShoppingCart,
91
95
  'shopping-bag': IconShoppingBag,
96
+ 'building-store': IconBuildingStore,
97
+ package: IconPackage,
98
+ truck: IconTruck,
99
+ ticket: IconTicket,
92
100
  tag: IconTag,
93
101
  tags: IconTags,
94
102
  receipt: IconReceipt,
@@ -6,43 +6,74 @@
6
6
  import NavCollections from './nav-collections.svelte';
7
7
  import NavFooter from './nav-footer.svelte';
8
8
  import NavForms from './nav-forms.svelte';
9
- import NavShop from './nav-shop.svelte';
9
+ import NavModule from './nav-module.svelte';
10
10
  import NavCustom from './nav-custom.svelte';
11
11
  import NavSearch from './nav-search.svelte';
12
+ import NavGlobal from './nav-global.svelte';
13
+ import ContextSwitcher from './context-switcher.svelte';
14
+ import { getIcon } from '../icons/icon-map.js';
15
+ import BoxIcon from '@tabler/icons-svelte/icons/box';
12
16
  import { resolve } from '$app/paths';
13
17
  import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
18
+ import { getRemotes } from '../../../sveltekit/index.js';
19
+ import { getActiveContext, CONTENT_CONTEXT } from '../../state/active-context.svelte.js';
14
20
  import { sidebarLang } from './lang.js';
15
21
 
16
22
  let { ...restProps }: ComponentProps<typeof Sidebar.Root> = $props();
17
23
 
18
24
  const interfaceLanguage = useInterfaceLanguage();
25
+ const remotes = getRemotes();
26
+
27
+ const modulesQuery = $derived(remotes.getModules());
28
+ const modules = $derived(modulesQuery.current ?? []);
29
+ const moduleIds = $derived(modules.map((m) => m.id));
30
+ const activeContext = $derived(getActiveContext(moduleIds));
31
+ const activeModule = $derived(modules.find((m) => m.id === activeContext) ?? null);
19
32
  </script>
20
33
 
21
- <Sidebar.Root collapsible="icon" class="bg-sidebar" {...restProps}>
34
+ <Sidebar.Root collapsible="icon" class="bg-sidebar" data-context={activeContext} {...restProps}>
22
35
  <Sidebar.Header
23
36
  class="border-sidebar-border h-[52px] shrink-0 flex-row items-center border-b px-3 group-data-[collapsible=icon]:px-2"
24
37
  >
25
38
  <a
26
39
  href={resolve('/admin')}
27
- class="flex items-baseline gap-px"
40
+ class="flex items-baseline gap-px group-data-[collapsible=icon]:hidden"
28
41
  aria-label="AriaCMS — strona główna"
29
42
  >
30
43
  <span class="text-primary text-base font-extrabold">Aria</span>
31
- <span
32
- class="text-primary text-[11px] font-light tracking-[0.15em] uppercase group-data-[collapsible=icon]:hidden"
33
- >CMS</span
34
- >
44
+ <span class="text-primary text-[11px] font-light tracking-[0.15em] uppercase">CMS</span>
35
45
  </a>
46
+ <ContextSwitcher />
36
47
  </Sidebar.Header>
37
48
  <Sidebar.Content class="gap-0">
38
49
  <NavSearch />
39
- <nav id="admin-nav" aria-label={sidebarLang[interfaceLanguage.current].nav.primary} class="py-1">
40
- <NavMain />
41
- <NavSingletons />
42
- <NavCollections />
43
- <NavForms />
44
- <NavShop />
45
- <NavCustom />
50
+ <nav
51
+ id="admin-nav"
52
+ aria-label={sidebarLang[interfaceLanguage.current].nav.primary}
53
+ class="flex flex-1 flex-col py-1"
54
+ >
55
+ {#if activeContext === CONTENT_CONTEXT || !activeModule}
56
+ <NavMain />
57
+ <NavSingletons />
58
+ <NavCollections />
59
+ <NavForms />
60
+ <NavCustom />
61
+ {:else}
62
+ <NavMain
63
+ url={activeModule.context.landingUrl}
64
+ icon={getIcon(activeModule.icon) ?? BoxIcon}
65
+ />
66
+ <NavModule module={activeModule} />
67
+ <NavCollections
68
+ filterSlugs={activeModule.context.surfacedCollections ?? []}
69
+ ctx={activeContext}
70
+ />
71
+ <NavSingletons
72
+ filterSlugs={activeModule.context.surfacedSingles ?? []}
73
+ ctx={activeContext}
74
+ />
75
+ {/if}
76
+ <NavGlobal />
46
77
  </nav>
47
78
  </Sidebar.Content>
48
79
  <Sidebar.Footer class="p-0">