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.
- package/API.md +97 -4
- package/CHANGELOG.md +118 -0
- package/DOCS.md +1 -1
- package/README.md +2 -0
- package/ROADMAP.md +14 -0
- package/dist/admin/auth-client.d.ts +42 -42
- package/dist/admin/client/account/lang.d.ts +1 -0
- package/dist/admin/client/account/lang.js +4 -2
- package/dist/admin/client/account/profile-section.svelte +2 -2
- package/dist/admin/client/account/security-section.svelte +27 -4
- package/dist/admin/client/account/sessions-section.svelte +1 -1
- package/dist/admin/client/admin/admin-after-login-layout-content.svelte +1 -1
- package/dist/admin/client/admin/admin-layout.svelte +12 -2
- package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
- package/dist/admin/client/admin/dashboard-page.svelte +34 -10
- package/dist/admin/client/collection/bulk-actions-bar.svelte +86 -44
- package/dist/admin/client/collection/bulk-actions-bar.svelte.d.ts +3 -1
- package/dist/admin/client/collection/collection-entries.svelte +52 -36
- package/dist/admin/client/collection/collection-entries.svelte.d.ts +3 -0
- package/dist/admin/client/collection/collection.svelte +28 -14
- package/dist/admin/client/collection/collection.svelte.d.ts +3 -0
- package/dist/admin/client/collection/data-table.svelte +240 -130
- package/dist/admin/client/collection/data-table.svelte.d.ts +9 -0
- package/dist/admin/client/collection/date-cell.svelte +4 -4
- package/dist/admin/client/collection/row-actions.svelte +2 -1
- package/dist/admin/client/collection/sortable-header.svelte +33 -9
- package/dist/admin/client/collection/state-display.svelte +102 -0
- package/dist/admin/client/collection/state-display.svelte.d.ts +12 -0
- package/dist/admin/client/collection/status-badge.svelte +99 -11
- package/dist/admin/client/collection/status-badge.svelte.d.ts +15 -1
- package/dist/admin/client/collection/table-pagination.svelte +21 -6
- package/dist/admin/client/collection/table-toolbar.svelte +105 -80
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -8
- package/dist/admin/client/entry/entry-form.svelte +36 -11
- package/dist/admin/client/entry/entry-form.svelte.d.ts +1 -0
- package/dist/admin/client/entry/entry-header.svelte +22 -15
- package/dist/admin/client/entry/entry-header.svelte.d.ts +1 -0
- package/dist/admin/client/entry/entry.svelte +269 -165
- package/dist/admin/client/entry/header/a11y-header-badge.svelte +47 -0
- package/dist/admin/client/entry/header/a11y-header-badge.svelte.d.ts +8 -0
- package/dist/admin/client/entry/header/publish-panel.svelte +69 -13
- package/dist/admin/client/entry/header/save-indicator.svelte +57 -28
- package/dist/admin/client/entry/header/save-indicator.svelte.d.ts +1 -0
- package/dist/admin/client/entry/header/status-badge.svelte +60 -15
- package/dist/admin/client/entry/header/status-badge.svelte.d.ts +1 -2
- package/dist/admin/client/entry/header/version-history-sheet.svelte +1 -1
- package/dist/admin/client/entry/hybrid/hybrid-layout.svelte +74 -23
- package/dist/admin/client/entry/hybrid/hybrid-preview.svelte +1 -1
- package/dist/admin/client/entry/utils.d.ts +14 -0
- package/dist/admin/client/entry/utils.js +28 -0
- package/dist/admin/client/form/form-submission/form-submission.svelte +2 -2
- package/dist/admin/client/form/form-submissions.svelte +143 -194
- package/dist/admin/client/form/form-submissions.svelte.d.ts +2 -0
- package/dist/admin/client/login/lang.d.ts +3 -0
- package/dist/admin/client/login/lang.js +10 -4
- package/dist/admin/client/login/login-form.svelte +8 -1
- package/dist/admin/client/login/reset-password-page.svelte +24 -3
- package/dist/admin/client/login/schema.d.ts +14 -2
- package/dist/admin/client/login/schema.js +19 -8
- package/dist/admin/client/maintenance/maintenance-page.svelte +16 -17
- package/dist/admin/client/media/media-page.svelte +1 -1
- package/dist/admin/client/shop/coupon-edit-page.svelte +117 -13
- package/dist/admin/client/shop/coupon-form.svelte +282 -138
- package/dist/admin/client/shop/coupon-form.svelte.d.ts +1 -9
- package/dist/admin/client/shop/coupon-new-page.svelte +40 -10
- package/dist/admin/client/shop/coupon-new-page.svelte.d.ts +2 -17
- package/dist/admin/client/shop/coupon-schema.d.ts +28 -0
- package/dist/admin/client/shop/coupon-schema.js +53 -0
- package/dist/admin/client/shop/coupons-list-page.svelte +262 -118
- package/dist/admin/client/shop/coupons-list-page.svelte.d.ts +16 -1
- package/dist/admin/client/shop/refund-dialog.svelte +37 -1
- package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
- package/dist/admin/client/shop/shipping-method-edit-page.svelte +108 -59
- package/dist/admin/client/shop/shipping-method-form.svelte +36 -9
- package/dist/admin/client/shop/shipping-method-new-page.svelte +44 -13
- package/dist/admin/client/shop/shipping-methods-list-page.svelte +101 -59
- package/dist/admin/client/shop/shop-order-detail-page.svelte +220 -84
- package/dist/admin/client/shop/shop-orders-list-page.svelte +302 -152
- package/dist/admin/client/shop/shop-orders-list-page.svelte.d.ts +18 -1
- package/dist/admin/client/shop/shop-products-list-page.svelte +355 -118
- package/dist/admin/client/shop/shop-products-list-page.svelte.d.ts +19 -1
- package/dist/admin/client/users/accept-invite-page.svelte +24 -3
- package/dist/admin/client/users/create-user-dialog.svelte +3 -8
- package/dist/admin/client/users/lang.d.ts +2 -0
- package/dist/admin/client/users/lang.js +4 -0
- package/dist/admin/client/users/pending-invitations.svelte +2 -9
- package/dist/admin/client/users/user-name-cell.svelte +20 -0
- package/dist/admin/client/users/user-name-cell.svelte.d.ts +9 -0
- package/dist/admin/client/users/user-role-badge.svelte +16 -0
- package/dist/admin/client/users/user-role-badge.svelte.d.ts +7 -0
- package/dist/admin/client/users/user-row-actions.svelte +72 -0
- package/dist/admin/client/users/user-row-actions.svelte.d.ts +20 -0
- package/dist/admin/client/users/user-sessions-sheet.svelte +2 -11
- package/dist/admin/client/users/users-page.svelte +283 -497
- package/dist/admin/client/users/users-page.svelte.d.ts +12 -1
- package/dist/admin/components/dashboard/form-submissions-widget.svelte +59 -74
- package/dist/admin/components/dashboard/recent-activity.svelte +17 -5
- package/dist/admin/components/dashboard/recent-entries.svelte +19 -7
- package/dist/admin/components/dialogs/confirmation-dialog.svelte +105 -0
- package/dist/admin/components/dialogs/confirmation-dialog.svelte.d.ts +13 -0
- package/dist/admin/components/fields/block-picker-modal.svelte +6 -0
- package/dist/admin/components/fields/blocks-field.svelte +46 -1
- package/dist/admin/components/fields/boolean-field.svelte +1 -1
- package/dist/admin/components/fields/field-renderer.svelte +29 -22
- package/dist/admin/components/fields/file-field.svelte +344 -30
- package/dist/admin/components/fields/icon-field.svelte +86 -0
- package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
- package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
- package/dist/admin/components/fields/media-field.svelte +16 -2
- package/dist/admin/components/fields/object-field.svelte +27 -7
- package/dist/admin/components/fields/radio-field.svelte +22 -0
- package/dist/admin/components/fields/relation-field.svelte +123 -97
- package/dist/admin/components/fields/relation-picker-dialog.svelte +2 -2
- package/dist/admin/components/fields/seo-field.svelte +60 -30
- package/dist/admin/components/fields/shop-field.svelte +219 -24
- package/dist/admin/components/fields/simple-array-field.svelte +321 -151
- package/dist/admin/components/fields/simple-array-field.svelte.d.ts +3 -0
- package/dist/admin/components/fields/slug-field.svelte +146 -21
- package/dist/admin/components/fields/text-field-wrapper.svelte +37 -20
- package/dist/admin/components/fields/text-field.svelte +7 -2
- package/dist/admin/components/fields/url-field-wrapper.svelte +10 -0
- package/dist/admin/components/fields/url-field.svelte +36 -23
- package/dist/admin/components/forms/form-error-summary.svelte +143 -0
- package/dist/admin/components/forms/form-error-summary.svelte.d.ts +27 -0
- package/dist/admin/components/layout/app-sidebar.svelte +7 -2
- package/dist/admin/components/layout/detail-page-shell.svelte +71 -0
- package/dist/admin/components/layout/detail-page-shell.svelte.d.ts +24 -0
- package/dist/admin/components/layout/lang.d.ts +5 -0
- package/dist/admin/components/layout/lang.js +10 -0
- package/dist/admin/components/layout/layout-renderer.svelte +71 -2
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/layout/layout-tabs.svelte +173 -0
- package/dist/admin/components/layout/layout-tabs.svelte.d.ts +24 -0
- package/dist/admin/components/layout/nav-breadcrumbs.svelte +25 -7
- package/dist/admin/components/layout/nav-collections.svelte +23 -36
- package/dist/admin/components/layout/nav-forms.svelte +19 -35
- package/dist/admin/components/layout/nav-main.svelte +3 -28
- package/dist/admin/components/layout/nav-search.svelte +70 -2
- package/dist/admin/components/layout/nav-section.svelte +77 -0
- package/dist/admin/components/layout/nav-section.svelte.d.ts +22 -0
- package/dist/admin/components/layout/nav-shop.svelte +3 -27
- package/dist/admin/components/layout/nav-singletons.svelte +16 -28
- package/dist/admin/components/layout/page-header.stories.svelte +93 -0
- package/dist/admin/components/layout/page-header.stories.svelte.d.ts +27 -0
- package/dist/admin/components/layout/page-header.svelte +68 -0
- package/dist/admin/components/layout/page-header.svelte.d.ts +17 -0
- package/dist/admin/components/layout/site-header.svelte +9 -0
- package/dist/admin/components/layout/site-header.svelte.d.ts +2 -17
- package/dist/admin/components/media/file/file-name-input.svelte +6 -2
- package/dist/admin/components/media/file/file-preview.svelte +130 -17
- package/dist/admin/components/media/file-upload.svelte +16 -7
- package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
- package/dist/admin/components/media/files-list.svelte +153 -53
- package/dist/admin/components/media/files-list.svelte.d.ts +1 -0
- package/dist/admin/components/media/media-library.svelte +577 -198
- package/dist/admin/components/media/media-library.svelte.d.ts +4 -0
- package/dist/admin/components/media/media-selector.svelte +4 -2
- package/dist/admin/components/media/media-selector.svelte.d.ts +1 -0
- package/dist/admin/components/media/tag-sidebar.svelte +4 -4
- package/dist/admin/components/tiptap/FigureNodeView.svelte +10 -0
- package/dist/admin/components/tiptap/bubble-menu.svelte +104 -0
- package/dist/admin/components/tiptap/bubble-menu.svelte.d.ts +19 -0
- package/dist/admin/components/tiptap/content-editor.svelte +28 -24
- package/dist/admin/components/tiptap/editor-toolbar.svelte +7 -7
- package/dist/admin/components/tiptap/extensions.js +5 -1
- package/dist/admin/components/tiptap/image-dialog.svelte +5 -1
- package/dist/admin/components/tiptap/link-dialog.svelte +2 -0
- package/dist/admin/components/tiptap/tiptap-editor.svelte +18 -20
- package/dist/admin/components/tiptap/video-dialog.svelte +1 -1
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
- package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
- package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
- package/dist/admin/helpers/build-icon-set-map.js +16 -0
- package/dist/admin/helpers/index.d.ts +2 -0
- package/dist/admin/helpers/index.js +2 -0
- package/dist/admin/i18n/errors.d.ts +140 -0
- package/dist/admin/i18n/errors.js +151 -0
- package/dist/admin/remote/entry.remote.d.ts +59 -4
- package/dist/admin/remote/entry.remote.js +239 -62
- package/dist/admin/remote/shop.remote.d.ts +87 -48
- package/dist/admin/remote/shop.remote.js +70 -8
- package/dist/admin/shared/password-generate.d.ts +6 -0
- package/dist/admin/shared/password-generate.js +40 -0
- package/dist/admin/shared/password-schema.d.ts +6 -0
- package/dist/admin/shared/password-schema.js +10 -3
- package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
- package/dist/admin/state/icon-sets.svelte.js +20 -0
- package/dist/admin/styles/admin.css +23 -6
- package/dist/admin/styles/tokens.md +244 -0
- package/dist/admin/utils/accordionActivation.d.ts +13 -0
- package/dist/admin/utils/accordionActivation.js +35 -0
- package/dist/admin/utils/entryLabel.d.ts +23 -0
- package/dist/admin/utils/entryLabel.js +51 -12
- package/dist/admin/utils/field-a11y.d.ts +29 -0
- package/dist/admin/utils/field-a11y.js +23 -0
- package/dist/admin/utils/fieldPathElement.d.ts +9 -0
- package/dist/admin/utils/fieldPathElement.js +18 -0
- package/dist/admin/utils/fileDisplay.d.ts +10 -0
- package/dist/admin/utils/fileDisplay.js +26 -0
- package/dist/admin/utils/flattenFormErrors.d.ts +19 -0
- package/dist/admin/utils/flattenFormErrors.js +102 -0
- package/dist/admin/utils/formatters.d.ts +12 -0
- package/dist/admin/utils/{formatDate.js → formatters.js} +23 -2
- package/dist/admin/utils/scrollWithin.d.ts +9 -0
- package/dist/admin/utils/scrollWithin.js +32 -0
- package/dist/admin/utils/tabActivation.d.ts +12 -0
- package/dist/admin/utils/tabActivation.js +24 -0
- package/dist/cli/scaffold/admin.js +2 -2
- package/dist/cms/runtime/schema.d.ts +1 -0
- package/dist/cms/runtime/schema.js +1 -0
- package/dist/cms/runtime/types.d.ts +80 -7
- package/dist/components/ui/accordion/accordion-content.svelte +17 -3
- package/dist/components/ui/accordion/accordion.stories.svelte +21 -1
- package/dist/components/ui/alert/alert.stories.svelte +14 -0
- package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte +45 -0
- package/dist/components/ui/alert-dialog/alert-dialog.stories.svelte.d.ts +27 -0
- package/dist/components/ui/avatar/avatar.stories.svelte +27 -0
- package/dist/components/ui/badge/badge.stories.svelte +15 -0
- package/dist/components/ui/breadcrumb/breadcrumb.stories.svelte +47 -0
- package/dist/components/ui/breadcrumb/breadcrumb.svelte +1 -1
- package/dist/components/ui/button/button.stories.svelte +53 -6
- package/dist/components/ui/button/button.svelte +39 -5
- package/dist/components/ui/button/button.svelte.d.ts +4 -0
- package/dist/components/ui/button-group/button-group.stories.svelte +44 -0
- package/dist/components/ui/button-group/button-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/calendar/calendar.stories.svelte +36 -0
- package/dist/components/ui/calendar/calendar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/card/card.stories.svelte +7 -0
- package/dist/components/ui/carousel/carousel.stories.svelte +43 -0
- package/dist/components/ui/carousel/carousel.stories.svelte.d.ts +27 -0
- package/dist/components/ui/checkbox/checkbox.stories.svelte +67 -0
- package/dist/components/ui/checkbox/checkbox.stories.svelte.d.ts +27 -0
- package/dist/components/ui/checkbox/checkbox.svelte +1 -1
- package/dist/components/ui/command/command.stories.svelte +18 -0
- package/dist/components/ui/data-table/data-table.stories.svelte +61 -0
- package/dist/components/ui/data-table/data-table.stories.svelte.d.ts +18 -0
- package/dist/components/ui/dialog/dialog-content.svelte +5 -0
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +2 -0
- package/dist/components/ui/dialog/dialog.stories.svelte +35 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte +74 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.stories.svelte.d.ts +27 -0
- package/dist/components/ui/field/field-context.svelte.d.ts +22 -0
- package/dist/components/ui/field/field-context.svelte.js +9 -0
- package/dist/components/ui/field/field-control.svelte +18 -0
- package/dist/components/ui/field/field-control.svelte.d.ts +8 -0
- package/dist/components/ui/field/field-description.svelte +12 -0
- package/dist/components/ui/field/field-error.svelte +14 -6
- package/dist/components/ui/field/field-label.svelte +10 -0
- package/dist/components/ui/field/field.stories.svelte +95 -9
- package/dist/components/ui/field/field.svelte +57 -0
- package/dist/components/ui/field/field.svelte.d.ts +2 -0
- package/dist/components/ui/field/index.d.ts +3 -1
- package/dist/components/ui/field/index.js +4 -2
- package/dist/components/ui/form/form-field-errors.svelte +1 -1
- package/dist/components/ui/form/form.stories.svelte +25 -0
- package/dist/components/ui/form/form.stories.svelte.d.ts +26 -0
- package/dist/components/ui/input/input.stories.svelte +26 -0
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group.stories.svelte +43 -0
- package/dist/components/ui/input-group/input-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/item/item.stories.svelte +61 -0
- package/dist/components/ui/item/item.stories.svelte.d.ts +27 -0
- package/dist/components/ui/label/label.stories.svelte +7 -0
- package/dist/components/ui/live-region/index.d.ts +1 -0
- package/dist/components/ui/live-region/index.js +1 -0
- package/dist/components/ui/live-region/live-region-demo.svelte +32 -0
- package/dist/components/ui/live-region/live-region-demo.svelte.d.ts +7 -0
- package/dist/components/ui/live-region/live-region.stories.svelte +23 -0
- package/dist/components/ui/live-region/live-region.stories.svelte.d.ts +26 -0
- package/dist/components/ui/live-region/live-region.svelte +12 -0
- package/dist/components/ui/live-region/live-region.svelte.d.ts +8 -0
- package/dist/components/ui/popover/popover.stories.svelte +34 -0
- package/dist/components/ui/radio-group/radio-group.stories.svelte +58 -0
- package/dist/components/ui/radio-group/radio-group.stories.svelte.d.ts +27 -0
- package/dist/components/ui/resizable/resizable.stories.svelte +56 -0
- package/dist/components/ui/resizable/resizable.stories.svelte.d.ts +27 -0
- package/dist/components/ui/select/select.stories.svelte +49 -0
- package/dist/components/ui/separator/separator.stories.svelte +18 -0
- package/dist/components/ui/sheet/sheet.stories.svelte +34 -0
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-menu-button.svelte +1 -0
- package/dist/components/ui/sidebar/sidebar-trigger.svelte +1 -1
- package/dist/components/ui/sidebar/sidebar.stories.svelte +72 -0
- package/dist/components/ui/sidebar/sidebar.stories.svelte.d.ts +27 -0
- package/dist/components/ui/skeleton/skeleton.stories.svelte +39 -0
- package/dist/components/ui/skeleton/skeleton.stories.svelte.d.ts +27 -0
- package/dist/components/ui/skeleton/skeleton.svelte +6 -0
- package/dist/components/ui/sonner/index.d.ts +1 -1
- package/dist/components/ui/sonner/index.js +1 -1
- package/dist/components/ui/sonner/sonner.stories.svelte +7 -0
- package/dist/components/ui/sonner/sonner.svelte +17 -1
- package/dist/components/ui/sonner/sonner.svelte.d.ts +6 -0
- package/dist/components/ui/spinner/spinner.stories.svelte +30 -0
- package/dist/components/ui/spinner/spinner.stories.svelte.d.ts +27 -0
- package/dist/components/ui/switch/switch.stories.svelte +56 -0
- package/dist/components/ui/switch/switch.stories.svelte.d.ts +27 -0
- package/dist/components/ui/table/table-cell.svelte +1 -1
- package/dist/components/ui/table/table-head.svelte +1 -1
- package/dist/components/ui/table/table.stories.svelte +68 -0
- package/dist/components/ui/table/table.stories.svelte.d.ts +27 -0
- package/dist/components/ui/table/table.svelte +1 -1
- package/dist/components/ui/tabs/tabs.stories.svelte +48 -0
- package/dist/components/ui/tabs/tabs.stories.svelte.d.ts +27 -0
- package/dist/components/ui/textarea/textarea.stories.svelte +21 -0
- package/dist/components/ui/toggle/toggle.stories.svelte +23 -0
- package/dist/components/ui/toggle-group/toggle-group.stories.svelte +43 -0
- package/dist/components/ui/tooltip/tooltip.stories.svelte +46 -6
- package/dist/core/cms.d.ts +11 -2
- package/dist/core/cms.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.d.ts +7 -0
- package/dist/core/fields/fieldSchemaToTs.js +241 -90
- package/dist/core/fields/layoutUtils.d.ts +4 -1
- package/dist/core/fields/layoutUtils.js +41 -4
- package/dist/core/fields/resolveSeo.d.ts +70 -0
- package/dist/core/fields/resolveSeo.js +88 -0
- package/dist/core/fields/seoFieldDescriptor.d.ts +43 -0
- package/dist/core/fields/seoFieldDescriptor.js +74 -0
- package/dist/core/fields/slugPath.d.ts +13 -0
- package/dist/core/fields/slugPath.js +32 -0
- package/dist/core/fields/urlUtils.d.ts +8 -0
- package/dist/core/fields/urlUtils.js +27 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/server/entries/operations/create.js +13 -0
- package/dist/core/server/entries/operations/get.d.ts +7 -0
- package/dist/core/server/entries/operations/get.js +10 -6
- package/dist/core/server/entries/operations/slugUniqueness.d.ts +37 -0
- package/dist/core/server/entries/operations/slugUniqueness.js +116 -0
- package/dist/core/server/entries/operations/update.d.ts +6 -1
- package/dist/core/server/entries/operations/update.js +24 -1
- package/dist/core/server/fields/slugResolver.d.ts +3 -13
- package/dist/core/server/fields/slugResolver.js +8 -37
- package/dist/core/server/generator/fields.d.ts +2 -0
- package/dist/core/server/generator/fields.js +44 -18
- package/dist/core/server/generator/formFields.js +2 -1
- package/dist/core/server/generator/generator.js +6 -5
- package/dist/core/server/generator/utils.d.ts +1 -0
- package/dist/core/server/generator/utils.js +4 -0
- package/dist/db-postgres/schema/shop/order.d.ts +37 -1
- package/dist/db-postgres/schema/shop/order.js +3 -1
- package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
- package/dist/db-postgres/schema/shop/payment.js +4 -1
- package/dist/db-postgres/schema/shop/product.d.ts +20 -0
- package/dist/db-postgres/schema/shop/product.js +3 -1
- package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
- package/dist/db-postgres/schema/shop/productVariant.js +22 -0
- package/dist/shop/cart/types.d.ts +1 -0
- package/dist/shop/client/index.d.ts +54 -0
- package/dist/shop/client/index.js +5 -1
- package/dist/shop/expiry.d.ts +35 -0
- package/dist/shop/expiry.js +68 -0
- package/dist/shop/http/balance-handler.d.ts +20 -0
- package/dist/shop/http/balance-handler.js +91 -0
- package/dist/shop/http/cart-handler.js +19 -0
- package/dist/shop/http/checkout-handler.js +19 -1
- package/dist/shop/http/index.d.ts +2 -0
- package/dist/shop/http/index.js +2 -0
- package/dist/shop/http/upcoming-handler.d.ts +16 -0
- package/dist/shop/http/upcoming-handler.js +65 -0
- package/dist/shop/http/webhook-handler.js +46 -9
- package/dist/shop/index.d.ts +4 -1
- package/dist/shop/index.js +7 -1
- package/dist/shop/server/balance-payment.d.ts +40 -0
- package/dist/shop/server/balance-payment.js +140 -0
- package/dist/shop/server/cart-hydrate.js +2 -0
- package/dist/shop/server/init.d.ts +14 -0
- package/dist/shop/server/init.js +35 -0
- package/dist/shop/server/orders.d.ts +35 -0
- package/dist/shop/server/orders.js +155 -2
- package/dist/shop/server/payment-policy.d.ts +35 -0
- package/dist/shop/server/payment-policy.js +55 -0
- package/dist/shop/server/payments.d.ts +29 -0
- package/dist/shop/server/payments.js +64 -0
- package/dist/shop/server/populate.d.ts +1 -1
- package/dist/shop/server/refund.d.ts +17 -12
- package/dist/shop/server/refund.js +96 -13
- package/dist/shop/server/shop-data.d.ts +6 -1
- package/dist/shop/server/shop-data.js +44 -7
- package/dist/shop/template.d.ts +13 -0
- package/dist/shop/template.js +98 -0
- package/dist/shop/types.d.ts +142 -1
- package/dist/shop/variant-attributes.d.ts +28 -0
- package/dist/shop/variant-attributes.js +69 -0
- package/dist/sveltekit/server/handle.js +17 -0
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/types/cms.d.ts +4 -3
- package/dist/types/cms.schema.d.ts +1 -1
- package/dist/types/cms.schema.js +13 -2
- package/dist/types/fields.d.ts +56 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -1
- package/dist/types/layout.d.ts +35 -2
- package/dist/types/plugins.d.ts +40 -0
- package/dist/types/plugins.js +4 -1
- package/dist/updates/0.26.0/index.d.ts +2 -0
- package/dist/updates/0.26.0/index.js +51 -0
- package/dist/updates/0.26.1/index.d.ts +2 -0
- package/dist/updates/0.26.1/index.js +19 -0
- package/dist/updates/0.27.0/index.d.ts +2 -0
- package/dist/updates/0.27.0/index.js +50 -0
- package/dist/updates/index.js +7 -1
- package/package.json +29 -7
- package/dist/admin/client/collection/empty-state.svelte +0 -28
- package/dist/admin/client/collection/empty-state.svelte.d.ts +0 -9
- package/dist/admin/client/form/submission-status-badge.svelte +0 -41
- package/dist/admin/client/form/submission-status-badge.svelte.d.ts +0 -7
- package/dist/admin/components/media/file-preview.svelte +0 -51
- package/dist/admin/components/media/file-preview.svelte.d.ts +0 -6
- package/dist/admin/utils/formatDate.d.ts +0 -5
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { and, eq, sum } from 'drizzle-orm';
|
|
2
|
-
import {
|
|
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
|
-
|
|
74
|
-
|
|
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', `
|
|
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
|
|
145
|
+
let resultProviderRef = null;
|
|
98
146
|
try {
|
|
99
147
|
const providerResult = await adapter.refund({
|
|
100
|
-
providerRef
|
|
148
|
+
providerRef,
|
|
101
149
|
amount,
|
|
102
150
|
currency: order.currency,
|
|
103
151
|
reason: input.reason
|
|
104
152
|
});
|
|
105
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
176
|
-
const
|
|
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:
|
|
189
|
-
draftData:
|
|
190
|
-
|
|
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
|
+
}
|
package/dist/shop/types.d.ts
CHANGED
|
@@ -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
|
+
}
|