includio-cms 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. package/API.md +97 -4
  2. package/CHANGELOG.md +118 -0
  3. package/DOCS.md +1 -1
  4. package/README.md +2 -0
  5. package/ROADMAP.md +14 -0
  6. package/dist/admin/auth-client.d.ts +42 -42
  7. package/dist/admin/client/account/lang.d.ts +1 -0
  8. package/dist/admin/client/account/lang.js +4 -2
  9. package/dist/admin/client/account/profile-section.svelte +2 -2
  10. package/dist/admin/client/account/security-section.svelte +27 -4
  11. package/dist/admin/client/account/sessions-section.svelte +1 -1
  12. package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
  13. package/dist/admin/client/admin/admin-layout.svelte +12 -2
  14. package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
  15. package/dist/admin/client/admin/dashboard-page.svelte +34 -10
  16. package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
  17. package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
  18. package/dist/admin/client/collection/collection-entries.svelte +52 -36
  19. package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
  20. package/dist/admin/client/collection/collection.svelte +28 -14
  21. package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
  22. package/dist/admin/client/collection/data-table.svelte +240 -130
  23. package/dist/admin/client/collection/data-table.svelte.d.ts +9 -0
  24. package/dist/admin/client/collection/date-cell.svelte +4 -4
  25. package/dist/admin/client/collection/row-actions.svelte +2 -1
  26. package/dist/admin/client/collection/sortable-header.svelte +33 -9
  27. package/dist/admin/client/collection/state-display.svelte +102 -0
  28. package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
  29. package/dist/admin/client/collection/status-badge.svelte +99 -11
  30. package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
  31. package/dist/admin/client/collection/table-pagination.svelte +21 -6
  32. package/dist/admin/client/collection/table-toolbar.svelte +105 -80
  33. package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
  34. package/dist/admin/client/entry/entry-form.svelte +36 -11
  35. package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
  36. package/dist/admin/client/entry/entry-header.svelte +22 -15
  37. package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
  38. package/dist/admin/client/entry/entry.svelte +269 -165
  39. package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
  40. package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
  41. package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
  42. package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
  43. package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
  44. package/dist/admin/client/entry/header/status-badge.svelte +60 -15
  45. package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
  46. package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
  47. package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
  48. package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
  49. package/dist/admin/client/entry/utils.d.ts +14 -0
  50. package/dist/admin/client/entry/utils.js +28 -0
  51. package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
  52. package/dist/admin/client/form/form-submissions.svelte +143 -194
  53. package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
  54. package/dist/admin/client/login/lang.d.ts +3 -0
  55. package/dist/admin/client/login/lang.js +10 -4
  56. package/dist/admin/client/login/login-form.svelte +8 -1
  57. package/dist/admin/client/login/reset-password-page.svelte +24 -3
  58. package/dist/admin/client/login/schema.d.ts +14 -2
  59. package/dist/admin/client/login/schema.js +19 -8
  60. package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
  61. package/dist/admin/client/media/media-page.svelte +1 -1
  62. package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
  63. package/dist/admin/client/shop/coupon-form.svelte +282 -138
  64. package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
  65. package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
  66. package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
  67. package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
  68. package/dist/admin/client/shop/coupon-schema.js +53 -0
  69. package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
  70. package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
  71. package/dist/admin/client/shop/refund-dialog.svelte +37 -1
  72. package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
  73. package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
  74. package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
  75. package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
  76. package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
  77. package/dist/admin/client/shop/shop-order-detail-page.svelte +220 -84
  78. package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
  79. package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
  80. package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
  81. package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
  82. package/dist/admin/client/users/accept-invite-page.svelte +24 -3
  83. package/dist/admin/client/users/create-user-dialog.svelte +3 -8
  84. package/dist/admin/client/users/lang.d.ts +2 -0
  85. package/dist/admin/client/users/lang.js +4 -0
  86. package/dist/admin/client/users/pending-invitations.svelte +2 -9
  87. package/dist/admin/client/users/user-name-cell.svelte +20 -0
  88. package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
  89. package/dist/admin/client/users/user-role-badge.svelte +16 -0
  90. package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
  91. package/dist/admin/client/users/user-row-actions.svelte +72 -0
  92. package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
  93. package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
  94. package/dist/admin/client/users/users-page.svelte +283 -497
  95. package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
  96. package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
  97. package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
  98. package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
  99. package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
  100. package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
  101. package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
  102. package/dist/admin/components/fields/blocks-field.svelte +46 -1
  103. package/dist/admin/components/fields/boolean-field.svelte +1 -1
  104. package/dist/admin/components/fields/field-renderer.svelte +29 -22
  105. package/dist/admin/components/fields/file-field.svelte +344 -30
  106. package/dist/admin/components/fields/icon-field.svelte +86 -0
  107. package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
  108. package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
  109. package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
  110. package/dist/admin/components/fields/media-field.svelte +16 -2
  111. package/dist/admin/components/fields/object-field.svelte +27 -7
  112. package/dist/admin/components/fields/radio-field.svelte +22 -0
  113. package/dist/admin/components/fields/relation-field.svelte +123 -97
  114. package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
  115. package/dist/admin/components/fields/seo-field.svelte +60 -30
  116. package/dist/admin/components/fields/shop-field.svelte +219 -24
  117. package/dist/admin/components/fields/simple-array-field.svelte +321 -151
  118. package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
  119. package/dist/admin/components/fields/slug-field.svelte +146 -21
  120. package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
  121. package/dist/admin/components/fields/text-field.svelte +7 -2
  122. package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
  123. package/dist/admin/components/fields/url-field.svelte +36 -23
  124. package/dist/admin/components/forms/form-error-summary.svelte +143 -0
  125. package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
  126. package/dist/admin/components/layout/app-sidebar.svelte +7 -2
  127. package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
  128. package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
  129. package/dist/admin/components/layout/lang.d.ts +5 -0
  130. package/dist/admin/components/layout/lang.js +10 -0
  131. package/dist/admin/components/layout/layout-renderer.svelte +71 -2
  132. package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
  133. package/dist/admin/components/layout/layout-tabs.svelte +173 -0
  134. package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
  135. package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
  136. package/dist/admin/components/layout/nav-collections.svelte +23 -36
  137. package/dist/admin/components/layout/nav-forms.svelte +19 -35
  138. package/dist/admin/components/layout/nav-main.svelte +3 -28
  139. package/dist/admin/components/layout/nav-search.svelte +70 -2
  140. package/dist/admin/components/layout/nav-section.svelte +77 -0
  141. package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
  142. package/dist/admin/components/layout/nav-shop.svelte +3 -27
  143. package/dist/admin/components/layout/nav-singletons.svelte +16 -28
  144. package/dist/admin/components/layout/page-header.stories.svelte +93 -0
  145. package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
  146. package/dist/admin/components/layout/page-header.svelte +68 -0
  147. package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
  148. package/dist/admin/components/layout/site-header.svelte +9 -0
  149. package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
  150. package/dist/admin/components/media/file/file-name-input.svelte +6 -2
  151. package/dist/admin/components/media/file/file-preview.svelte +130 -17
  152. package/dist/admin/components/media/file-upload.svelte +16 -7
  153. package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
  154. package/dist/admin/components/media/files-list.svelte +153 -53
  155. package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
  156. package/dist/admin/components/media/media-library.svelte +577 -198
  157. package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
  158. package/dist/admin/components/media/media-selector.svelte +4 -2
  159. package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
  160. package/dist/admin/components/media/tag-sidebar.svelte +4 -4
  161. package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
  162. package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
  163. package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
  164. package/dist/admin/components/tiptap/content-editor.svelte +28 -24
  165. package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
  166. package/dist/admin/components/tiptap/extensions.js +5 -1
  167. package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
  168. package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
  169. package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
  170. package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
  171. package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
  172. package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
  173. package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
  174. package/dist/admin/helpers/build-icon-set-map.js +16 -0
  175. package/dist/admin/helpers/index.d.ts +2 -0
  176. package/dist/admin/helpers/index.js +2 -0
  177. package/dist/admin/i18n/errors.d.ts +140 -0
  178. package/dist/admin/i18n/errors.js +151 -0
  179. package/dist/admin/remote/entry.remote.d.ts +59 -4
  180. package/dist/admin/remote/entry.remote.js +239 -62
  181. package/dist/admin/remote/shop.remote.d.ts +87 -48
  182. package/dist/admin/remote/shop.remote.js +70 -8
  183. package/dist/admin/shared/password-generate.d.ts +6 -0
  184. package/dist/admin/shared/password-generate.js +40 -0
  185. package/dist/admin/shared/password-schema.d.ts +6 -0
  186. package/dist/admin/shared/password-schema.js +10 -3
  187. package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
  188. package/dist/admin/state/icon-sets.svelte.js +20 -0
  189. package/dist/admin/styles/admin.css +23 -6
  190. package/dist/admin/styles/tokens.md +244 -0
  191. package/dist/admin/utils/accordionActivation.d.ts +13 -0
  192. package/dist/admin/utils/accordionActivation.js +35 -0
  193. package/dist/admin/utils/entryLabel.d.ts +23 -0
  194. package/dist/admin/utils/entryLabel.js +51 -12
  195. package/dist/admin/utils/field-a11y.d.ts +29 -0
  196. package/dist/admin/utils/field-a11y.js +23 -0
  197. package/dist/admin/utils/fieldPathElement.d.ts +9 -0
  198. package/dist/admin/utils/fieldPathElement.js +18 -0
  199. package/dist/admin/utils/fileDisplay.d.ts +10 -0
  200. package/dist/admin/utils/fileDisplay.js +26 -0
  201. package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
  202. package/dist/admin/utils/flattenFormErrors.js +102 -0
  203. package/dist/admin/utils/formatters.d.ts +12 -0
  204. package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
  205. package/dist/admin/utils/scrollWithin.d.ts +9 -0
  206. package/dist/admin/utils/scrollWithin.js +32 -0
  207. package/dist/admin/utils/tabActivation.d.ts +12 -0
  208. package/dist/admin/utils/tabActivation.js +24 -0
  209. package/dist/cli/scaffold/admin.js +2 -2
  210. package/dist/cms/runtime/schema.d.ts +1 -0
  211. package/dist/cms/runtime/schema.js +1 -0
  212. package/dist/cms/runtime/types.d.ts +80 -7
  213. package/dist/components/ui/accordion/accordion-content.svelte +17 -3
  214. package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
  215. package/dist/components/ui/alert/alert.stories.svelte +14 -0
  216. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
  217. package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
  218. package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
  219. package/dist/components/ui/badge/badge.stories.svelte +15 -0
  220. package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
  221. package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
  222. package/dist/components/ui/button/button.stories.svelte +53 -6
  223. package/dist/components/ui/button/button.svelte +39 -5
  224. package/dist/components/ui/button/button.svelte.d.ts +4 -0
  225. package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
  226. package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
  227. package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
  228. package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
  229. package/dist/components/ui/card/card.stories.svelte +7 -0
  230. package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
  231. package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
  232. package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
  233. package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
  234. package/dist/components/ui/checkbox/checkbox.svelte +1 -1
  235. package/dist/components/ui/command/command.stories.svelte +18 -0
  236. package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
  237. package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
  238. package/dist/components/ui/dialog/dialog-content.svelte +5 -0
  239. package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
  240. package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
  241. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
  242. package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
  243. package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
  244. package/dist/components/ui/field/field-context.svelte.js +9 -0
  245. package/dist/components/ui/field/field-control.svelte +18 -0
  246. package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
  247. package/dist/components/ui/field/field-description.svelte +12 -0
  248. package/dist/components/ui/field/field-error.svelte +14 -6
  249. package/dist/components/ui/field/field-label.svelte +10 -0
  250. package/dist/components/ui/field/field.stories.svelte +95 -9
  251. package/dist/components/ui/field/field.svelte +57 -0
  252. package/dist/components/ui/field/field.svelte.d.ts +2 -0
  253. package/dist/components/ui/field/index.d.ts +3 -1
  254. package/dist/components/ui/field/index.js +4 -2
  255. package/dist/components/ui/form/form-field-errors.svelte +1 -1
  256. package/dist/components/ui/form/form.stories.svelte +25 -0
  257. package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
  258. package/dist/components/ui/input/input.stories.svelte +26 -0
  259. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
  260. package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
  261. package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
  262. package/dist/components/ui/item/item.stories.svelte +61 -0
  263. package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
  264. package/dist/components/ui/label/label.stories.svelte +7 -0
  265. package/dist/components/ui/live-region/index.d.ts +1 -0
  266. package/dist/components/ui/live-region/index.js +1 -0
  267. package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
  268. package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
  269. package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
  270. package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
  271. package/dist/components/ui/live-region/live-region.svelte +12 -0
  272. package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
  273. package/dist/components/ui/popover/popover.stories.svelte +34 -0
  274. package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
  275. package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
  276. package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
  277. package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
  278. package/dist/components/ui/select/select.stories.svelte +49 -0
  279. package/dist/components/ui/separator/separator.stories.svelte +18 -0
  280. package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
  281. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
  282. package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
  283. package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
  284. package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
  285. package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
  286. package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
  287. package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
  288. package/dist/components/ui/skeleton/skeleton.svelte +6 -0
  289. package/dist/components/ui/sonner/index.d.ts +1 -1
  290. package/dist/components/ui/sonner/index.js +1 -1
  291. package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
  292. package/dist/components/ui/sonner/sonner.svelte +17 -1
  293. package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
  294. package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
  295. package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
  296. package/dist/components/ui/switch/switch.stories.svelte +56 -0
  297. package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
  298. package/dist/components/ui/table/table-cell.svelte +1 -1
  299. package/dist/components/ui/table/table-head.svelte +1 -1
  300. package/dist/components/ui/table/table.stories.svelte +68 -0
  301. package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
  302. package/dist/components/ui/table/table.svelte +1 -1
  303. package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
  304. package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
  305. package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
  306. package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
  307. package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
  308. package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
  309. package/dist/core/cms.d.ts +11 -2
  310. package/dist/core/cms.js +29 -0
  311. package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
  312. package/dist/core/fields/fieldSchemaToTs.js +241 -90
  313. package/dist/core/fields/layoutUtils.d.ts +4 -1
  314. package/dist/core/fields/layoutUtils.js +41 -4
  315. package/dist/core/fields/resolveSeo.d.ts +70 -0
  316. package/dist/core/fields/resolveSeo.js +88 -0
  317. package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
  318. package/dist/core/fields/seoFieldDescriptor.js +74 -0
  319. package/dist/core/fields/slugPath.d.ts +13 -0
  320. package/dist/core/fields/slugPath.js +32 -0
  321. package/dist/core/fields/urlUtils.d.ts +8 -0
  322. package/dist/core/fields/urlUtils.js +27 -0
  323. package/dist/core/index.d.ts +1 -0
  324. package/dist/core/index.js +1 -0
  325. package/dist/core/server/entries/operations/create.js +13 -0
  326. package/dist/core/server/entries/operations/get.d.ts +7 -0
  327. package/dist/core/server/entries/operations/get.js +10 -6
  328. package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
  329. package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
  330. package/dist/core/server/entries/operations/update.d.ts +6 -1
  331. package/dist/core/server/entries/operations/update.js +24 -1
  332. package/dist/core/server/fields/slugResolver.d.ts +3 -13
  333. package/dist/core/server/fields/slugResolver.js +8 -37
  334. package/dist/core/server/generator/fields.d.ts +2 -0
  335. package/dist/core/server/generator/fields.js +44 -18
  336. package/dist/core/server/generator/formFields.js +2 -1
  337. package/dist/core/server/generator/generator.js +6 -5
  338. package/dist/core/server/generator/utils.d.ts +1 -0
  339. package/dist/core/server/generator/utils.js +4 -0
  340. package/dist/db-postgres/schema/shop/order.d.ts +37 -1
  341. package/dist/db-postgres/schema/shop/order.js +3 -1
  342. package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
  343. package/dist/db-postgres/schema/shop/payment.js +4 -1
  344. package/dist/db-postgres/schema/shop/product.d.ts +20 -0
  345. package/dist/db-postgres/schema/shop/product.js +3 -1
  346. package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
  347. package/dist/db-postgres/schema/shop/productVariant.js +22 -0
  348. package/dist/shop/cart/types.d.ts +1 -0
  349. package/dist/shop/client/index.d.ts +54 -0
  350. package/dist/shop/client/index.js +5 -1
  351. package/dist/shop/expiry.d.ts +35 -0
  352. package/dist/shop/expiry.js +68 -0
  353. package/dist/shop/http/balance-handler.d.ts +20 -0
  354. package/dist/shop/http/balance-handler.js +91 -0
  355. package/dist/shop/http/cart-handler.js +19 -0
  356. package/dist/shop/http/checkout-handler.js +19 -1
  357. package/dist/shop/http/index.d.ts +2 -0
  358. package/dist/shop/http/index.js +2 -0
  359. package/dist/shop/http/upcoming-handler.d.ts +16 -0
  360. package/dist/shop/http/upcoming-handler.js +65 -0
  361. package/dist/shop/http/webhook-handler.js +46 -9
  362. package/dist/shop/index.d.ts +4 -1
  363. package/dist/shop/index.js +7 -1
  364. package/dist/shop/server/balance-payment.d.ts +40 -0
  365. package/dist/shop/server/balance-payment.js +140 -0
  366. package/dist/shop/server/cart-hydrate.js +2 -0
  367. package/dist/shop/server/init.d.ts +14 -0
  368. package/dist/shop/server/init.js +35 -0
  369. package/dist/shop/server/orders.d.ts +35 -0
  370. package/dist/shop/server/orders.js +155 -2
  371. package/dist/shop/server/payment-policy.d.ts +35 -0
  372. package/dist/shop/server/payment-policy.js +55 -0
  373. package/dist/shop/server/payments.d.ts +29 -0
  374. package/dist/shop/server/payments.js +64 -0
  375. package/dist/shop/server/populate.d.ts +1 -1
  376. package/dist/shop/server/refund.d.ts +17 -12
  377. package/dist/shop/server/refund.js +96 -13
  378. package/dist/shop/server/shop-data.d.ts +6 -1
  379. package/dist/shop/server/shop-data.js +44 -7
  380. package/dist/shop/template.d.ts +13 -0
  381. package/dist/shop/template.js +98 -0
  382. package/dist/shop/types.d.ts +142 -1
  383. package/dist/shop/variant-attributes.d.ts +28 -0
  384. package/dist/shop/variant-attributes.js +69 -0
  385. package/dist/sveltekit/server/handle.js +17 -0
  386. package/dist/sveltekit/server/index.d.ts +1 -0
  387. package/dist/sveltekit/server/index.js +2 -0
  388. package/dist/types/cms.d.ts +4 -3
  389. package/dist/types/cms.schema.d.ts +1 -1
  390. package/dist/types/cms.schema.js +13 -2
  391. package/dist/types/fields.d.ts +56 -2
  392. package/dist/types/index.d.ts +2 -2
  393. package/dist/types/index.js +1 -1
  394. package/dist/types/layout.d.ts +35 -2
  395. package/dist/types/plugins.d.ts +40 -0
  396. package/dist/types/plugins.js +4 -1
  397. package/dist/updates/0.26.0/index.d.ts +2 -0
  398. package/dist/updates/0.26.0/index.js +51 -0
  399. package/dist/updates/0.26.1/index.d.ts +2 -0
  400. package/dist/updates/0.26.1/index.js +19 -0
  401. package/dist/updates/0.27.0/index.d.ts +2 -0
  402. package/dist/updates/0.27.0/index.js +50 -0
  403. package/dist/updates/index.js +7 -1
  404. package/package.json +29 -7
  405. package/dist/admin/client/collection/empty-state.svelte +0 -28
  406. package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
  407. package/dist/admin/client/form/submission-status-badge.svelte +0 -41
  408. package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
  409. package/dist/admin/components/media/file-preview.svelte +0 -51
  410. package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
  411. package/dist/admin/utils/formatDate.d.ts +0 -5
@@ -1,7 +1,8 @@
1
- import { and, eq, sum } from 'drizzle-orm';
2
- import { shopOrdersTable, shopRefundsTable } from '../../db-postgres/schema/shop/index.js';
1
+ import { and, eq, sql, sum } from 'drizzle-orm';
2
+ import { shopOrderItemsTable, shopProductVariantsTable, shopRefundsTable } from '../../db-postgres/schema/shop/index.js';
3
3
  import { getShopDb, requireShopConfig } from './db.js';
4
4
  import { getOrderById, updateOrderStatus } from './orders.js';
5
+ import { listOrderPayments } from './payments.js';
5
6
  export class RefundError extends Error {
6
7
  code;
7
8
  cause;
@@ -46,6 +47,42 @@ function findAdapter(provider) {
46
47
  * `pending` refund row is marked `failed` on provider error so admin can see
47
48
  * what was attempted.
48
49
  */
50
+ async function resolveTargetPayment(orderId, requestedKind) {
51
+ const payments = await listOrderPayments(orderId);
52
+ const paid = payments.filter((p) => p.status === 'paid');
53
+ if (requestedKind) {
54
+ const match = paid.find((p) => p.kind === requestedKind);
55
+ if (!match) {
56
+ throw new RefundError('no_payment_kind', `No paid ${requestedKind} payment row on order to refund`);
57
+ }
58
+ return { payment: match, kind: requestedKind };
59
+ }
60
+ // No explicit kind: prefer a `full` row; with exactly one paid row of any
61
+ // kind, derive from it (keeps single-payment-row orders refunding their
62
+ // only paid payment). Legacy orders with no shop_payments rows fall back
63
+ // to the `'full'` path that uses order.paymentProviderRef.
64
+ const full = paid.find((p) => p.kind === 'full');
65
+ if (full)
66
+ return { payment: full, kind: 'full' };
67
+ if (paid.length === 1)
68
+ return { payment: paid[0], kind: paid[0].kind };
69
+ return { payment: null, kind: 'full' };
70
+ }
71
+ async function releaseOrderStock(orderId) {
72
+ const db = getShopDb();
73
+ const items = await db
74
+ .select()
75
+ .from(shopOrderItemsTable)
76
+ .where(eq(shopOrderItemsTable.orderId, orderId));
77
+ for (const item of items) {
78
+ if (!item.variantId)
79
+ continue;
80
+ await db
81
+ .update(shopProductVariantsTable)
82
+ .set({ stock: sql `${shopProductVariantsTable.stock} + ${item.qty}` })
83
+ .where(and(eq(shopProductVariantsTable.id, item.variantId), sql `${shopProductVariantsTable.stock} IS NOT NULL`));
84
+ }
85
+ }
49
86
  export async function refundOrder(input) {
50
87
  const db = getShopDb();
51
88
  const order = await getOrderById(input.orderId);
@@ -60,9 +97,6 @@ export async function refundOrder(input) {
60
97
  if (!order.paymentMethod) {
61
98
  throw new RefundError('unknown_provider', `Order ${order.number} has no payment method`);
62
99
  }
63
- if (!order.paymentProviderRef) {
64
- throw new RefundError('no_provider_ref', `Order ${order.number} has no payment provider reference`);
65
- }
66
100
  const adapter = findAdapter(order.paymentMethod);
67
101
  if (!adapter) {
68
102
  throw new RefundError('unknown_provider', `Payment provider "${order.paymentMethod}" is not configured`);
@@ -70,10 +104,23 @@ export async function refundOrder(input) {
70
104
  if (typeof adapter.refund !== 'function') {
71
105
  throw new RefundError('refund_unsupported', `Payment provider "${adapter.id}" does not support refunds`);
72
106
  }
73
- const alreadyRefunded = await getRefundedAmount(order.id);
74
- const remaining = order.totalGross - alreadyRefunded;
107
+ // Pick the payment row to target — explicit kind, single paid row, or
108
+ // legacy fallback to order.paymentProviderRef when no rows exist.
109
+ const target = await resolveTargetPayment(order.id, input.kind);
110
+ const providerRef = target.payment?.providerRef ?? order.paymentProviderRef;
111
+ if (!providerRef) {
112
+ throw new RefundError('no_provider_ref', `Order ${order.number} has no payment provider reference`);
113
+ }
114
+ // Per-kind cap: refund up to the matched row's amount (not order total)
115
+ // minus any prior refunds against that same payment row. Legacy path
116
+ // (no payment row) caps at order.totalGross.
117
+ const refundCeiling = target.payment ? target.payment.amount : order.totalGross;
118
+ const priorOnTarget = target.payment
119
+ ? await getRefundedAmountForPayment(target.payment.id)
120
+ : await getRefundedAmount(order.id);
121
+ const remaining = refundCeiling - priorOnTarget;
75
122
  if (remaining <= 0) {
76
- throw new RefundError('amount_exceeds_remaining', `Order ${order.number} already fully refunded`);
123
+ throw new RefundError('amount_exceeds_remaining', `Payment already fully refunded (${target.kind})`);
77
124
  }
78
125
  const amount = input.amount ?? remaining;
79
126
  if (!Number.isInteger(amount) || amount <= 0) {
@@ -86,6 +133,7 @@ export async function refundOrder(input) {
86
133
  .insert(shopRefundsTable)
87
134
  .values({
88
135
  orderId: order.id,
136
+ paymentId: target.payment?.id ?? null,
89
137
  provider: adapter.id,
90
138
  amount,
91
139
  currency: order.currency,
@@ -94,15 +142,15 @@ export async function refundOrder(input) {
94
142
  createdBy: input.createdBy ?? null
95
143
  })
96
144
  .returning();
97
- let providerRef = null;
145
+ let resultProviderRef = null;
98
146
  try {
99
147
  const providerResult = await adapter.refund({
100
- providerRef: order.paymentProviderRef,
148
+ providerRef,
101
149
  amount,
102
150
  currency: order.currency,
103
151
  reason: input.reason
104
152
  });
105
- providerRef = providerResult.providerRef;
153
+ resultProviderRef = providerResult.providerRef;
106
154
  }
107
155
  catch (err) {
108
156
  await db
@@ -118,23 +166,58 @@ export async function refundOrder(input) {
118
166
  .update(shopRefundsTable)
119
167
  .set({
120
168
  status: 'succeeded',
121
- providerRef,
169
+ providerRef: resultProviderRef,
122
170
  updatedAt: new Date()
123
171
  })
124
172
  .where(eq(shopRefundsTable.id, pending.id))
125
173
  .returning();
126
174
  const newRemaining = remaining - amount;
127
175
  let orderStatusChanged = false;
128
- if (newRemaining === 0) {
176
+ // Transition order to `refunded` only when the cumulative refund across
177
+ // ALL paid payment rows equals the total collected. For deposit-only
178
+ // orders pre-balance the "collected" = deposit row.amount; with balance
179
+ // paid it's deposit+balance.
180
+ const allCollected = await getCollectedAmount(order.id, order.totalGross);
181
+ const totalRefundedAfter = (await getRefundedAmount(order.id)) + 0; // succeeded row already counted
182
+ if (totalRefundedAfter >= allCollected) {
129
183
  await updateOrderStatus(order.id, 'refunded', {
130
184
  note: `Refund ${amount}${input.reason ? ` — ${input.reason}` : ''}`,
131
185
  changedBy: input.createdBy ?? 'admin'
132
186
  });
133
187
  orderStatusChanged = true;
134
188
  }
189
+ if (input.releaseStock) {
190
+ await releaseOrderStock(order.id);
191
+ }
135
192
  return {
136
193
  refund: succeeded,
137
194
  remainingRefundable: newRemaining,
138
195
  orderStatusChanged
139
196
  };
140
197
  }
198
+ /** @internal Sum of succeeded refunds attached to a specific payment row. */
199
+ async function getRefundedAmountForPayment(paymentId) {
200
+ const db = getShopDb();
201
+ const [row] = await db
202
+ .select({ total: sum(shopRefundsTable.amount) })
203
+ .from(shopRefundsTable)
204
+ .where(and(eq(shopRefundsTable.paymentId, paymentId), eq(shopRefundsTable.status, 'succeeded')));
205
+ const raw = row?.total;
206
+ if (raw == null)
207
+ return 0;
208
+ const n = typeof raw === 'string' ? Number(raw) : raw;
209
+ return Number.isFinite(n) ? n : 0;
210
+ }
211
+ /**
212
+ * @internal
213
+ * Total collected amount on an order — sum of `paid` payment row amounts
214
+ * when shop_payments rows exist; falls back to `order.totalGross` for
215
+ * legacy orders (no rows). Used to decide when to transition to `refunded`.
216
+ */
217
+ async function getCollectedAmount(orderId, fallback) {
218
+ const payments = await listOrderPayments(orderId);
219
+ const paid = payments.filter((p) => p.status === 'paid');
220
+ if (paid.length === 0)
221
+ return fallback;
222
+ return paid.reduce((sum, p) => sum + p.amount, 0);
223
+ }
@@ -1,4 +1,5 @@
1
1
  import { shopProductsTable, shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
2
+ import type { PaymentPolicy } from '../types.js';
2
3
  type RawShopDataRow = typeof shopProductsTable.$inferSelect;
3
4
  type RawVariantRow = typeof shopProductVariantsTable.$inferSelect;
4
5
  export type ShopDataRow = Omit<RawShopDataRow, 'basePrice'> & {
@@ -15,6 +16,8 @@ export interface ShopDataInput {
15
16
  vatRate: number;
16
17
  isActive?: boolean;
17
18
  sortOrder?: number | null;
19
+ /** Per-product payment policy. Null = full payment (legacy). */
20
+ paymentPolicy?: PaymentPolicy | null;
18
21
  }
19
22
  export interface VariantInput {
20
23
  id?: string;
@@ -22,7 +25,7 @@ export interface VariantInput {
22
25
  name?: Record<string, string> | null;
23
26
  priceDelta?: number;
24
27
  stock?: number | null;
25
- attributes?: Record<string, string> | null;
28
+ attributes?: Record<string, unknown> | null;
26
29
  }
27
30
  declare function validateShopData(input: ShopDataInput): void;
28
31
  export declare function getShopDataByEntry(entryId: string): Promise<ShopDataWithVariants | null>;
@@ -51,6 +54,8 @@ export interface ShopEntryListItem {
51
54
  updatedAt: Date;
52
55
  publishedData: Record<string, unknown> | null;
53
56
  draftData: Record<string, unknown> | null;
57
+ publishedByLang: Record<string, Record<string, unknown>>;
58
+ draftByLang: Record<string, Record<string, unknown>>;
54
59
  published: boolean;
55
60
  }
56
61
  export declare function listShopEntries(opts?: ListShopEntriesOptions): Promise<ShopEntryListItem[]>;
@@ -2,7 +2,10 @@ import { asc, eq, inArray } from 'drizzle-orm';
2
2
  import { shopProductsTable, shopProductVariantsTable } from '../../db-postgres/schema/shop/index.js';
3
3
  import { entriesTable } from '../../db-postgres/schema/entry.js';
4
4
  import { entryVersionsTable } from '../../db-postgres/schema/entryVersion.js';
5
+ import { getCMS } from '../../core/cms.js';
5
6
  import { getShopDb } from './db.js';
7
+ import { validateVariantAttributes } from '../variant-attributes.js';
8
+ import { validatePaymentPolicy } from './payment-policy.js';
6
9
  const MAX_PLN = 1e9;
7
10
  function validateShopData(input) {
8
11
  if (!Number.isFinite(input.basePrice) || input.basePrice < 0 || input.basePrice > MAX_PLN) {
@@ -11,6 +14,8 @@ function validateShopData(input) {
11
14
  if (!Number.isInteger(input.vatRate) || input.vatRate < 0 || input.vatRate > 100) {
12
15
  throw new Error('vatRate must be an integer between 0 and 100.');
13
16
  }
17
+ if (input.paymentPolicy != null)
18
+ validatePaymentPolicy(input.paymentPolicy);
14
19
  }
15
20
  function mapShopRow(r) {
16
21
  return { ...r, basePrice: Number(r.basePrice) };
@@ -40,13 +45,18 @@ export async function upsertShopData(entryId, input, variants) {
40
45
  let productId;
41
46
  const basePriceSql = String(input.basePrice);
42
47
  if (existing) {
48
+ // `paymentPolicy === undefined` (caller didn't include the key) preserves
49
+ // the existing policy — admin clients that don't surface the field
50
+ // must not silently wipe it. Explicit `null` clears.
51
+ const nextPolicy = input.paymentPolicy === undefined ? existing.paymentPolicy : input.paymentPolicy;
43
52
  const [updated] = await db
44
53
  .update(shopProductsTable)
45
54
  .set({
46
55
  basePrice: basePriceSql,
47
56
  vatRate: input.vatRate,
48
57
  isActive: input.isActive ?? existing.isActive,
49
- sortOrder: input.sortOrder ?? existing.sortOrder,
58
+ sortOrder: input.sortOrder ?? existing.sortOrder ?? null,
59
+ paymentPolicy: nextPolicy,
50
60
  updatedAt: new Date()
51
61
  })
52
62
  .where(eq(shopProductsTable.entryId, entryId))
@@ -61,12 +71,24 @@ export async function upsertShopData(entryId, input, variants) {
61
71
  basePrice: basePriceSql,
62
72
  vatRate: input.vatRate,
63
73
  isActive: input.isActive ?? true,
64
- sortOrder: input.sortOrder
74
+ sortOrder: input.sortOrder ?? null,
75
+ paymentPolicy: input.paymentPolicy ?? null
65
76
  })
66
77
  .returning();
67
78
  productId = created.id;
68
79
  }
69
80
  if (variants !== undefined) {
81
+ // Validate against the schema declared in defineShop({ variantAttributes }).
82
+ // Missing/extra keys, type mismatches → InvalidVariantAttributesError
83
+ // (code INVALID_VARIANT_ATTRIBUTES). Skipped if no shop config or empty
84
+ // schema (legacy untyped behavior preserved).
85
+ const shopConfig = getCMS().shopConfig;
86
+ const attrSchema = shopConfig?.variantAttributes ?? {};
87
+ if (Object.keys(attrSchema).length > 0) {
88
+ for (const v of variants) {
89
+ validateVariantAttributes(v.attributes, attrSchema);
90
+ }
91
+ }
70
92
  const submittedIds = new Set(variants.filter((v) => v.id).map((v) => v.id));
71
93
  const currentVariants = existing?.variants ?? [];
72
94
  for (const v of currentVariants) {
@@ -172,8 +194,21 @@ export async function listShopEntries(opts = {}) {
172
194
  const variants = allVariants.filter((v) => v.productId === r.shopId);
173
195
  const stocks = variants.map((v) => v.stock).filter((s) => s != null);
174
196
  const versions = allVersions.filter((v) => v.entryId === r.entryId);
175
- const published = versions.find((v) => v.publishedAt != null);
176
- const draft = versions.find((v) => v.publishedAt == null);
197
+ const publishedByLang = {};
198
+ const draftByLang = {};
199
+ for (const v of versions) {
200
+ const data = v.data;
201
+ if (v.publishedAt != null) {
202
+ if (!publishedByLang[v.lang])
203
+ publishedByLang[v.lang] = data;
204
+ }
205
+ else {
206
+ if (!draftByLang[v.lang])
207
+ draftByLang[v.lang] = data;
208
+ }
209
+ }
210
+ const firstPublished = versions.find((v) => v.publishedAt != null);
211
+ const firstDraft = versions.find((v) => v.publishedAt == null);
177
212
  return {
178
213
  entryId: r.entryId,
179
214
  collectionSlug: r.collectionSlug,
@@ -185,9 +220,11 @@ export async function listShopEntries(opts = {}) {
185
220
  totalStock: stocks.length > 0 ? stocks.reduce((a, b) => a + b, 0) : null,
186
221
  createdAt: r.createdAt,
187
222
  updatedAt: r.updatedAt,
188
- publishedData: published?.data ?? null,
189
- draftData: draft?.data ?? null,
190
- published: published != null
223
+ publishedData: firstPublished?.data ?? null,
224
+ draftData: firstDraft?.data ?? null,
225
+ publishedByLang,
226
+ draftByLang,
227
+ published: firstPublished != null
191
228
  };
192
229
  });
193
230
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * String interpolation engine used by `defineShop({ variantLabel.template })`
3
+ * to render variant names from typed `variantAttributes`.
4
+ *
5
+ * Syntax: `{key}` or `{key|filter}` or `{key|filter:arg}`.
6
+ * Supported filters: `date` (long|medium|short), `currency` (PLN by default),
7
+ * `uppercase`. Unknown filter → value passes through unchanged. Unknown key
8
+ * → empty string + `console.warn` in dev. Malformed template (unclosed brace)
9
+ * → raw template + warn.
10
+ *
11
+ * @public
12
+ */
13
+ export declare function interpolateTemplate(template: string, vars: Record<string, unknown>, locale: string): string;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * String interpolation engine used by `defineShop({ variantLabel.template })`
3
+ * to render variant names from typed `variantAttributes`.
4
+ *
5
+ * Syntax: `{key}` or `{key|filter}` or `{key|filter:arg}`.
6
+ * Supported filters: `date` (long|medium|short), `currency` (PLN by default),
7
+ * `uppercase`. Unknown filter → value passes through unchanged. Unknown key
8
+ * → empty string + `console.warn` in dev. Malformed template (unclosed brace)
9
+ * → raw template + warn.
10
+ *
11
+ * @public
12
+ */
13
+ export function interpolateTemplate(template, vars, locale) {
14
+ if (!hasBalancedBraces(template)) {
15
+ console.warn(`[interpolateTemplate] Malformed template (unclosed brace): ${template}`);
16
+ return template;
17
+ }
18
+ return template.replace(/\{([^}]+)\}/g, (_match, body) => {
19
+ const { key, filter, arg } = parsePlaceholder(body);
20
+ if (!(key in vars)) {
21
+ console.warn(`[interpolateTemplate] Unknown key "${key}" in template: ${template}`);
22
+ return '';
23
+ }
24
+ const raw = vars[key];
25
+ if (raw === null || raw === undefined)
26
+ return '';
27
+ if (!filter)
28
+ return String(raw);
29
+ return applyFilter(raw, filter, arg, locale);
30
+ });
31
+ }
32
+ /** @internal */
33
+ function parsePlaceholder(body) {
34
+ const [keyPart, filterPart] = body.split('|', 2);
35
+ const key = keyPart.trim();
36
+ if (!filterPart)
37
+ return { key, filter: null, arg: null };
38
+ const [filterName, ...argParts] = filterPart.split(':');
39
+ return {
40
+ key,
41
+ filter: filterName.trim(),
42
+ arg: argParts.length > 0 ? argParts.join(':').trim() : null
43
+ };
44
+ }
45
+ /** @internal */
46
+ function applyFilter(value, filter, arg, locale) {
47
+ switch (filter) {
48
+ case 'date':
49
+ return formatDate(value, arg ?? 'medium', locale);
50
+ case 'currency':
51
+ return formatCurrency(value, arg ?? 'PLN', locale);
52
+ case 'uppercase':
53
+ return String(value).toUpperCase();
54
+ default:
55
+ return String(value);
56
+ }
57
+ }
58
+ /** @internal */
59
+ function formatDate(value, style, locale) {
60
+ const date = new Date(String(value));
61
+ if (Number.isNaN(date.getTime()))
62
+ return String(value);
63
+ const dateStyle = ['long', 'medium', 'short'].includes(style)
64
+ ? style
65
+ : 'medium';
66
+ try {
67
+ return new Intl.DateTimeFormat(locale, { dateStyle }).format(date);
68
+ }
69
+ catch {
70
+ return String(value);
71
+ }
72
+ }
73
+ /** @internal */
74
+ function formatCurrency(value, currency, locale) {
75
+ const num = typeof value === 'number' ? value : Number(value);
76
+ if (!Number.isFinite(num))
77
+ return String(value);
78
+ try {
79
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(num);
80
+ }
81
+ catch {
82
+ return String(value);
83
+ }
84
+ }
85
+ /** @internal */
86
+ function hasBalancedBraces(template) {
87
+ let depth = 0;
88
+ for (const ch of template) {
89
+ if (ch === '{')
90
+ depth++;
91
+ else if (ch === '}') {
92
+ depth--;
93
+ if (depth < 0)
94
+ return false;
95
+ }
96
+ }
97
+ return depth === 0;
98
+ }
@@ -147,6 +147,124 @@ export interface CouponRef {
147
147
  value: number;
148
148
  discountAmount: number;
149
149
  }
150
+ /**
151
+ * Schema descriptor for one product-variant attribute (city, startsAt, …).
152
+ * Drives Zod validation, ts-gen typings, GIN indexes, and admin renderers.
153
+ * @public
154
+ */
155
+ export type VariantAttribute = VariantAttributeText | VariantAttributeNumber | VariantAttributeDatetime | VariantAttributeSelect | VariantAttributeBoolean | VariantAttributeImage | VariantAttributeEntry | VariantAttributeSlug;
156
+ interface VariantAttributeBase {
157
+ label: I18nText;
158
+ required?: boolean;
159
+ /**
160
+ * Generate a `GIN ((attributes->'<key>'))` index on the variants table.
161
+ * Enable for keys filtered on storefront (`city`, `startsAt`, …).
162
+ */
163
+ indexable?: boolean;
164
+ }
165
+ /** @public */
166
+ export interface VariantAttributeText extends VariantAttributeBase {
167
+ type: 'text';
168
+ }
169
+ /** @public */
170
+ export interface VariantAttributeNumber extends VariantAttributeBase {
171
+ type: 'number';
172
+ }
173
+ /** @public — value stored as ISO-8601 string. */
174
+ export interface VariantAttributeDatetime extends VariantAttributeBase {
175
+ type: 'datetime';
176
+ }
177
+ /** @public */
178
+ export interface VariantAttributeSelect extends VariantAttributeBase {
179
+ type: 'select';
180
+ options: {
181
+ value: string;
182
+ label: I18nText;
183
+ }[];
184
+ }
185
+ /** @public */
186
+ export interface VariantAttributeBoolean extends VariantAttributeBase {
187
+ type: 'boolean';
188
+ }
189
+ /** @public — value = media file id (UUID). */
190
+ export interface VariantAttributeImage extends VariantAttributeBase {
191
+ type: 'image';
192
+ }
193
+ /** @public — value = entry id (UUID) from given collection. */
194
+ export interface VariantAttributeEntry extends VariantAttributeBase {
195
+ type: 'entry';
196
+ entryCollection: string;
197
+ }
198
+ /** @public — value matches slug regex `^[a-z0-9-]+$`. */
199
+ export interface VariantAttributeSlug extends VariantAttributeBase {
200
+ type: 'slug';
201
+ }
202
+ /**
203
+ * Template used to auto-generate `variant.name` from `variantAttributes`.
204
+ * Syntax: `{key|filter:arg}`. Filters: `date` (long|medium|short),
205
+ * `currency` (currency code), `uppercase`. Admin pre-fills the name input
206
+ * with the interpolated string; once the editor types into the name field,
207
+ * pre-fill stops for that variant (per-session dirty flag).
208
+ * @public
209
+ */
210
+ export interface VariantLabelConfig {
211
+ template: string;
212
+ }
213
+ /**
214
+ * Per-product payment policy. `full` (default) charges the full order total
215
+ * immediately; `deposit` charges only a deposit at checkout and leaves a
216
+ * remaining balance owed by the customer, redeemable via a signed balance
217
+ * link sent by the admin. Set on `ShopFieldData.paymentPolicy` per product
218
+ * entry — not globally on `defineShop`.
219
+ * @public
220
+ */
221
+ export type PaymentPolicy = {
222
+ type: 'full';
223
+ } | {
224
+ type: 'deposit';
225
+ depositAmount: DepositAmount;
226
+ };
227
+ /**
228
+ * Deposit amount specifier. `percent` charges `floor(base * value / 100)` of
229
+ * the line total; `amount` charges a fixed minor-unit value (clamped to the
230
+ * line total). Validation: `value > 0` always; `percent` ≤ 100; `amount` in
231
+ * the same minor-unit as `order.totalGross` (grosze for PLN).
232
+ * @public
233
+ */
234
+ export type DepositAmount = {
235
+ type: 'percent';
236
+ value: number;
237
+ } | {
238
+ type: 'amount';
239
+ value: number;
240
+ };
241
+ /**
242
+ * Persisted partial-payment summary on `order.partialPayment` when the order
243
+ * was placed under a deposit policy. Amounts are in minor currency units
244
+ * (grosze for PLN). `paidAt` is the ISO timestamp when the deposit cleared
245
+ * (or `null` while still pending). On balance payment, `paidAmount` is
246
+ * bumped to the full order total and `paidAt` is refreshed.
247
+ * @public
248
+ */
249
+ export interface PartialPayment {
250
+ kind: 'deposit';
251
+ paidAmount: number;
252
+ balanceAmount: number;
253
+ paidAt: string | null;
254
+ }
255
+ /**
256
+ * Opt-in storefront filter that hides variants whose datetime attribute
257
+ * (`source`, e.g. `startsAt`) has already passed. `offsetDays` shifts the
258
+ * cut-off: `0` = expire exactly when the source datetime hits now, `1` = one
259
+ * day after (grace period), `-1` = one day before. Admin still shows expired
260
+ * variants with a "Zakończony" badge; storefront `listUpcoming` filters them
261
+ * out and cart/checkout reject them with `VARIANT_EXPIRED`.
262
+ * @public
263
+ */
264
+ export interface VariantExpiryConfig {
265
+ source: string;
266
+ offsetDays: number;
267
+ }
150
268
  export interface ShopConfig {
151
269
  currency: Currency;
152
270
  vatRates: number[];
@@ -168,11 +286,34 @@ export interface ShopConfig {
168
286
  * automatic admin notifications. Omitted = those alerts are no-ops.
169
287
  */
170
288
  adminEmail?: string;
289
+ /**
290
+ * Typed variant attribute schema. Each key declares one attribute (city,
291
+ * startsAt, …) — drives Zod validation, ts-gen typings, GIN indexes, and
292
+ * admin variant form renderer. Omitted = legacy untyped string map.
293
+ * @public
294
+ */
295
+ variantAttributes?: Record<string, VariantAttribute>;
296
+ /**
297
+ * Template used by admin to auto-prefill `variant.name` from typed
298
+ * `variantAttributes`. Omitted = no pre-fill (editor enters name manually).
299
+ * @public
300
+ */
301
+ variantLabel?: VariantLabelConfig;
302
+ /**
303
+ * Opt-in storefront filter for time-bound variants (events, courses).
304
+ * Omitted = no filtering (legacy behavior — every variant always listed).
305
+ * @public
306
+ */
307
+ variantExpiry?: VariantExpiryConfig;
171
308
  }
172
- export interface ResolvedShopConfig extends ShopConfig {
309
+ export interface ResolvedShopConfig extends Omit<ShopConfig, 'variantLabel' | 'variantExpiry'> {
173
310
  features: Required<ShopFeatures>;
174
311
  rateLimit: Required<ShopRateLimit>;
175
312
  carriers: CarrierAdapter[];
176
313
  consents: ConsentConfig[];
177
314
  orderViewUrl: string;
315
+ variantAttributes: Record<string, VariantAttribute>;
316
+ variantLabel: VariantLabelConfig | null;
317
+ variantExpiry: VariantExpiryConfig | null;
178
318
  }
319
+ export {};
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import type { VariantAttribute } from './types.js';
3
+ /**
4
+ * Build a Zod object schema from a `defineShop({ variantAttributes })` map.
5
+ * Required attributes are mandatory keys; optional ones use `.optional()`.
6
+ * Empty map → `z.object({})`.
7
+ * @internal
8
+ */
9
+ export declare function buildVariantAttributesSchema(attrs: Record<string, VariantAttribute>): z.ZodObject<Record<string, z.ZodType>>;
10
+ /**
11
+ * @public
12
+ * Thrown by `upsertShopData` (and `validateVariantAttributes`) when a
13
+ * `variant.attributes` payload fails the schema derived from
14
+ * `defineShop({ variantAttributes })`. Carries the raw Zod issues.
15
+ */
16
+ export declare class InvalidVariantAttributesError extends Error {
17
+ readonly code = "INVALID_VARIANT_ATTRIBUTES";
18
+ readonly issues: z.ZodIssue[];
19
+ constructor(issues: z.ZodIssue[]);
20
+ }
21
+ /**
22
+ * Validate a variant attributes payload against a shop config.
23
+ * Throws `InvalidVariantAttributesError` (code `INVALID_VARIANT_ATTRIBUTES`) on
24
+ * failure. Returns the parsed (and coerced where applicable) value on success.
25
+ * `null` / `undefined` payloads are treated as `{}`.
26
+ * @internal
27
+ */
28
+ export declare function validateVariantAttributes(value: unknown, attrs: Record<string, VariantAttribute>): Record<string, unknown>;
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ const SLUG_REGEX = /^[a-z0-9-]+$/;
3
+ function buildLeafSchema(attr) {
4
+ switch (attr.type) {
5
+ case 'text':
6
+ return attr.required ? z.string().min(1) : z.string();
7
+ case 'number':
8
+ return z.number().finite();
9
+ case 'datetime':
10
+ // Accept full ISO 8601: UTC (`...Z`) AND offset forms (`+02:00`).
11
+ // Admin DatetimeField produces `Z` via toISOString(); imports/seeds
12
+ // may use local offsets. Both are valid ISO 8601.
13
+ return z.string().datetime({ offset: true });
14
+ case 'select':
15
+ return z.enum(attr.options.map((o) => o.value));
16
+ case 'boolean':
17
+ return z.boolean();
18
+ case 'image':
19
+ case 'entry':
20
+ return z.string().uuid();
21
+ case 'slug':
22
+ return z.string().regex(SLUG_REGEX);
23
+ }
24
+ }
25
+ /**
26
+ * Build a Zod object schema from a `defineShop({ variantAttributes })` map.
27
+ * Required attributes are mandatory keys; optional ones use `.optional()`.
28
+ * Empty map → `z.object({})`.
29
+ * @internal
30
+ */
31
+ export function buildVariantAttributesSchema(attrs) {
32
+ const shape = {};
33
+ for (const [key, attr] of Object.entries(attrs)) {
34
+ const leaf = buildLeafSchema(attr);
35
+ shape[key] = attr.required ? leaf : leaf.optional();
36
+ }
37
+ return z.object(shape);
38
+ }
39
+ /**
40
+ * @public
41
+ * Thrown by `upsertShopData` (and `validateVariantAttributes`) when a
42
+ * `variant.attributes` payload fails the schema derived from
43
+ * `defineShop({ variantAttributes })`. Carries the raw Zod issues.
44
+ */
45
+ export class InvalidVariantAttributesError extends Error {
46
+ code = 'INVALID_VARIANT_ATTRIBUTES';
47
+ issues;
48
+ constructor(issues) {
49
+ super(`Invalid variant attributes: ${issues.map((i) => i.message).join('; ')}`);
50
+ this.name = 'InvalidVariantAttributesError';
51
+ this.issues = issues;
52
+ }
53
+ }
54
+ /**
55
+ * Validate a variant attributes payload against a shop config.
56
+ * Throws `InvalidVariantAttributesError` (code `INVALID_VARIANT_ATTRIBUTES`) on
57
+ * failure. Returns the parsed (and coerced where applicable) value on success.
58
+ * `null` / `undefined` payloads are treated as `{}`.
59
+ * @internal
60
+ */
61
+ export function validateVariantAttributes(value, attrs) {
62
+ const schema = buildVariantAttributesSchema(attrs);
63
+ const input = value ?? {};
64
+ const result = schema.safeParse(input);
65
+ if (!result.success) {
66
+ throw new InvalidVariantAttributesError(result.error.issues);
67
+ }
68
+ return result.data;
69
+ }