@ikas/code-components-mcp 2.8.0 → 2.9.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/data/framework.json +53 -26
- package/data/section-templates/account-info-section/children/AccountFavorites/ikas-config-snippet.json +13 -0
- package/data/section-templates/account-info-section/children/AccountOrders/index.tsx +1 -1
- package/data/section-templates/account-info-section/ikas-config-snippet.json +19 -0
- package/data/section-templates/add-to-cart/ikas-config-snippet.json +17 -0
- package/data/section-templates/add-to-cart/utils/cartLimits.ts +4 -3
- package/data/section-templates/blog-home-section/ikas-config-snippet.json +2 -0
- package/data/section-templates/blog-home-section/index.tsx +2 -1
- package/data/section-templates/blog-post-section/ikas-config-snippet.json +2 -0
- package/data/section-templates/blog-post-section/index.tsx +3 -2
- package/data/section-templates/bundle-products/ikas-config-snippet.json +20 -0
- package/data/section-templates/cart-section/components/EmptyState/index.tsx +1 -1
- package/data/section-templates/cart-section/sub-components/CartItem/index.tsx +9 -0
- package/data/section-templates/cart-section/utils/cartLimits.ts +24 -0
- package/data/section-templates/cart-section/utils/cartLineLimits.ts +100 -0
- package/data/section-templates/category-images-section/children/CategoryImageItem/ikas-config-snippet.json +4 -0
- package/data/section-templates/category-images-section/ikas-config-snippet.json +15 -0
- package/data/section-templates/category-list-section/children/CardProductName/ikas-config-snippet.json +2 -1
- package/data/section-templates/category-list-section/ikas-config-snippet.json +21 -0
- package/data/section-templates/category-list-section/index.tsx +2 -1
- package/data/section-templates/component-renderer/additional/ProductDetail/index.tsx +5 -1
- package/data/section-templates/component-renderer/ikas-config-snippet.json +13 -0
- package/data/section-templates/favorites/ikas-config-snippet.json +18 -1
- package/data/section-templates/features-section/ikas-config-snippet.json +13 -0
- package/data/section-templates/footer-section/ikas-config-snippet.json +15 -0
- package/data/section-templates/forgot-password-section/components/ForgotPasswordForm/index.tsx +1 -1
- package/data/section-templates/header-section/children/Announcements/ikas-config-snippet.json +7 -0
- package/data/section-templates/header-section/children/CookieBar/ikas-config-snippet.json +3 -0
- package/data/section-templates/header-section/children/Navbar/components/CartSidebar/index.tsx +2 -1
- package/data/section-templates/header-section/children/Navbar/components/SearchModal/index.tsx +2 -1
- package/data/section-templates/header-section/children/Navbar/ikas-config-snippet.json +27 -0
- package/data/section-templates/header-section/children/Navbar/index.tsx +6 -5
- package/data/section-templates/header-section/ikas-config-snippet.json +13 -0
- package/data/section-templates/header-section/sub-components/CartItem/index.tsx +9 -0
- package/data/section-templates/header-section/utils/cartLimits.ts +24 -0
- package/data/section-templates/header-section/utils/cartLineLimits.ts +100 -0
- package/data/section-templates/hero-slider-section/children/HeroSliderItem/ikas-config-snippet.json +18 -0
- package/data/section-templates/hero-slider-section/children/HeroSliderItem/index.tsx +12 -7
- package/data/section-templates/hero-slider-section/ikas-config-snippet.json +13 -0
- package/data/section-templates/image-handling/ikas-config-snippet.json +52 -0
- package/data/section-templates/image-handling/index.tsx +5 -1
- package/data/section-templates/login-section/ikas-config-snippet.json +2 -0
- package/data/section-templates/navigation/components/CartSidebar/index.tsx +2 -1
- package/data/section-templates/navigation/components/SearchModal/index.tsx +2 -1
- package/data/section-templates/navigation/ikas-config-snippet.json +27 -0
- package/data/section-templates/navigation/index.tsx +6 -5
- package/data/section-templates/navigation/sub-components/CartItem/index.tsx +9 -0
- package/data/section-templates/navigation/utils/cartLimits.ts +24 -0
- package/data/section-templates/navigation/utils/cartLineLimits.ts +100 -0
- package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/ikas-config-snippet.json +17 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +16 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBundleFurniture/ikas-config-snippet.json +18 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBundleProduct/ikas-config-snippet.json +20 -0
- package/data/section-templates/product-detail-section/children/ProductDetailDescription/ikas-config-snippet.json +27 -0
- package/data/section-templates/product-detail-section/children/ProductDetailFeatures/ikas-config-snippet.json +29 -0
- package/data/section-templates/product-detail-section/children/ProductDetailNameFavorite/ikas-config-snippet.json +18 -1
- package/data/section-templates/product-detail-section/children/ProductDetailOffer/ikas-config-snippet.json +20 -0
- package/data/section-templates/product-detail-section/children/ProductDetailOptionSet/ikas-config-snippet.json +16 -0
- package/data/section-templates/product-detail-section/children/ProductDetailPrices/ikas-config-snippet.json +16 -0
- package/data/section-templates/product-detail-section/children/ProductDetailProductGroup/ikas-config-snippet.json +16 -0
- package/data/section-templates/product-detail-section/children/ProductDetailSku/ikas-config-snippet.json +16 -0
- package/data/section-templates/product-detail-section/children/ProductDetailVariant/ikas-config-snippet.json +17 -0
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +52 -0
- package/data/section-templates/product-detail-section/index.tsx +5 -1
- package/data/section-templates/product-detail-section/utils/cartLimits.ts +4 -3
- package/data/section-templates/product-pricing/ikas-config-snippet.json +16 -0
- package/data/section-templates/product-slider-section/children/CardProductName/ikas-config-snippet.json +2 -1
- package/data/section-templates/product-slider-section/ikas-config-snippet.json +17 -0
- package/data/section-templates/register-section/ikas-config-snippet.json +2 -0
- package/data/section-templates/variant-selection/ikas-config-snippet.json +17 -0
- package/data/storefront-api.json +301 -17
- package/data/storefront-types.json +221 -20
- package/dist/index.js +408 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
createMediaSrcset,
|
|
5
5
|
getFormattedFontSize,
|
|
6
6
|
Router,
|
|
7
|
+
withRoutePrefix,
|
|
7
8
|
} from "@ikas/bp-storefront";
|
|
8
9
|
import { Props } from "./types";
|
|
9
10
|
import {
|
|
@@ -113,15 +114,19 @@ export function HeroSliderItem({
|
|
|
113
114
|
mobileRoundedCorners && "kombos-hero-slider-item--mobile-rounded",
|
|
114
115
|
);
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// `target.href` already carries the routing prefix — it comes from a LINK prop the storefront
|
|
118
|
+
// resolves per request. Only the fallback is a literal, so only it needs prefixing.
|
|
119
|
+
const navigateToLink = (target: typeof link) => {
|
|
120
|
+
if (!target) return;
|
|
121
|
+
Router.navigate(
|
|
122
|
+
target.href || withRoutePrefix("/"),
|
|
123
|
+
false,
|
|
124
|
+
target.openInNewTab ?? false,
|
|
125
|
+
);
|
|
119
126
|
};
|
|
120
127
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
Router.navigate(mobLink.href || "/", false, mobLink.openInNewTab ?? false);
|
|
124
|
-
};
|
|
128
|
+
const handleDesktopClick = () => navigateToLink(link);
|
|
129
|
+
const handleMobileClick = () => navigateToLink(mobLink);
|
|
125
130
|
|
|
126
131
|
const formattedDesktopFontSize = getFormattedFontSize(buttonFontSize);
|
|
127
132
|
const formattedMobileFontSize = getFormattedFontSize(mobBtnFontSize);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"displayName": "Autoplay",
|
|
19
19
|
"type": "BOOLEAN",
|
|
20
20
|
"required": false,
|
|
21
|
+
"defaultValue": false,
|
|
21
22
|
"groupId": "settings"
|
|
22
23
|
},
|
|
23
24
|
{
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"displayName": "Full Width",
|
|
34
35
|
"type": "BOOLEAN",
|
|
35
36
|
"required": false,
|
|
37
|
+
"defaultValue": false,
|
|
36
38
|
"groupId": "settings"
|
|
37
39
|
},
|
|
38
40
|
{
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
"displayName": "Show Arrows",
|
|
41
43
|
"type": "BOOLEAN",
|
|
42
44
|
"required": false,
|
|
45
|
+
"defaultValue": false,
|
|
43
46
|
"groupId": "settings"
|
|
44
47
|
},
|
|
45
48
|
{
|
|
@@ -47,6 +50,16 @@
|
|
|
47
50
|
"displayName": "Slides",
|
|
48
51
|
"type": "COMPONENT_LIST",
|
|
49
52
|
"required": false,
|
|
53
|
+
"defaultValue": {
|
|
54
|
+
"components": [
|
|
55
|
+
{
|
|
56
|
+
"codeComponentId": "nb34u3yu-hero-slide"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"codeComponentId": "nb34u3yu-hero-slide"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
50
63
|
"filteredComponentIds": [
|
|
51
64
|
"<id-of-HeroSliderItem>"
|
|
52
65
|
]
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"displayName": "Aspect Ratio",
|
|
17
17
|
"type": "ENUM",
|
|
18
18
|
"required": false,
|
|
19
|
+
"defaultValue": "Square",
|
|
19
20
|
"groupId": "image-settings",
|
|
20
21
|
"enumTypeId": "{{ENUM_ID:AspectRatio}}"
|
|
21
22
|
},
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"displayName": "Image Fit",
|
|
25
26
|
"type": "ENUM",
|
|
26
27
|
"required": false,
|
|
28
|
+
"defaultValue": "Cover",
|
|
27
29
|
"groupId": "image-settings",
|
|
28
30
|
"enumTypeId": "{{ENUM_ID:ObjectFit}}"
|
|
29
31
|
},
|
|
@@ -32,6 +34,46 @@
|
|
|
32
34
|
"displayName": "Info Components",
|
|
33
35
|
"type": "COMPONENT_LIST",
|
|
34
36
|
"required": false,
|
|
37
|
+
"defaultValue": {
|
|
38
|
+
"components": [
|
|
39
|
+
{
|
|
40
|
+
"codeComponentId": "nb34u3yu-product-detail-name-favorite"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"codeComponentId": "nb34u3yu-product-detail-sku"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"codeComponentId": "nb34u3yu-product-detail-prices"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"codeComponentId": "nb34u3yu-product-detail-product-group"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"codeComponentId": "nb34u3yu-product-detail-bundle-product"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"codeComponentId": "nb34u3yu-product-detail-variant"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"codeComponentId": "nb34u3yu-product-detail-option-set"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"codeComponentId": "nb34u3yu-product-detail-add-to-cart"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"codeComponentId": "nb34u3yu-product-detail-back-in-stock"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"codeComponentId": "nb34u3yu-product-detail-offer"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"codeComponentId": "nb34u3yu-product-detail-features"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"codeComponentId": "nb34u3yu-product-detail-description"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
35
77
|
"filteredComponentIds": [
|
|
36
78
|
"<id-of-ProductDetailNameFavorite>",
|
|
37
79
|
"<id-of-ProductDetailSku>",
|
|
@@ -52,6 +94,16 @@
|
|
|
52
94
|
"displayName": "Sub Components",
|
|
53
95
|
"type": "COMPONENT_LIST",
|
|
54
96
|
"required": false,
|
|
97
|
+
"defaultValue": {
|
|
98
|
+
"components": [
|
|
99
|
+
{
|
|
100
|
+
"codeComponentId": "nb34u3yu-product-detail-bundle-furniture"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"codeComponentId": "nb34u3yu-product-detail-offer"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
55
107
|
"filteredComponentIds": [
|
|
56
108
|
"<id-of-ProductDetailBundleFurniture>",
|
|
57
109
|
"<id-of-ProductDetailOffer>"
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
IkasImage,
|
|
7
7
|
isNotEmpty,
|
|
8
8
|
IkasComponentRenderer,
|
|
9
|
+
withRoutePrefix,
|
|
9
10
|
} from "@ikas/bp-storefront";
|
|
10
11
|
import { Props } from "./types";
|
|
11
12
|
import Breadcrumb from "../../sub-components/Breadcrumb";
|
|
@@ -42,7 +43,10 @@ export function ProductDetail(props: Props) {
|
|
|
42
43
|
<div className="kombos-container kombos-pd__container">
|
|
43
44
|
<Breadcrumb
|
|
44
45
|
items={[
|
|
45
|
-
{
|
|
46
|
+
{
|
|
47
|
+
label: homepageText,
|
|
48
|
+
href: withRoutePrefix("/"),
|
|
49
|
+
} as BreadcrumbItem,
|
|
46
50
|
...(isNotEmpty(categoryPath)
|
|
47
51
|
? categoryPath.map(
|
|
48
52
|
(pathItem: any) =>
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"displayName": "Google Login",
|
|
91
91
|
"type": "BOOLEAN",
|
|
92
92
|
"required": false,
|
|
93
|
+
"defaultValue": false,
|
|
93
94
|
"groupId": "google"
|
|
94
95
|
},
|
|
95
96
|
{
|
|
@@ -105,6 +106,7 @@
|
|
|
105
106
|
"displayName": "Facebook Login",
|
|
106
107
|
"type": "BOOLEAN",
|
|
107
108
|
"required": false,
|
|
109
|
+
"defaultValue": false,
|
|
108
110
|
"groupId": "facebook"
|
|
109
111
|
},
|
|
110
112
|
{
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getCheckoutUrlFromCartStore,
|
|
20
20
|
IkasOrderLineItem,
|
|
21
21
|
Router,
|
|
22
|
+
withRoutePrefix,
|
|
22
23
|
} from "@ikas/bp-storefront";
|
|
23
24
|
import type { AspectRatio, ObjectFit } from "../../../../global-types";
|
|
24
25
|
import CartItem from "../../../../sub-components/CartItem";
|
|
@@ -164,7 +165,7 @@ const CartSidebar = observer(function CartSidebar({
|
|
|
164
165
|
</div>
|
|
165
166
|
<div className="kombos-cart-sidebar__footer-buttons">
|
|
166
167
|
<a
|
|
167
|
-
href="/cart"
|
|
168
|
+
href={withRoutePrefix("/cart")}
|
|
168
169
|
className="kombos-cart-sidebar__view-cart-link"
|
|
169
170
|
onClick={(e: Event) => {
|
|
170
171
|
e.preventDefault();
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
createMediaSrcset,
|
|
13
13
|
getProductOptionSet,
|
|
14
14
|
IkasComponentRenderer,
|
|
15
|
+
withRoutePrefix,
|
|
15
16
|
} from "@ikas/bp-storefront";
|
|
16
17
|
import type { IkasProduct } from "@ikas/bp-storefront";
|
|
17
18
|
import { cx } from "../../../../utils/cx";
|
|
@@ -199,7 +200,7 @@ const SearchModal = observer(function SearchModal({
|
|
|
199
200
|
<div className="kombos-search__logo-area">
|
|
200
201
|
{logo && (
|
|
201
202
|
<a
|
|
202
|
-
href="/"
|
|
203
|
+
href={withRoutePrefix("/")}
|
|
203
204
|
className="kombos-search__logo"
|
|
204
205
|
style={{
|
|
205
206
|
"--search-logo-h-desktop": logoSizeDesktop,
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"displayName": "Desktop Logo Size",
|
|
18
18
|
"type": "TYPE",
|
|
19
19
|
"required": false,
|
|
20
|
+
"defaultValue": {
|
|
21
|
+
"value": 60,
|
|
22
|
+
"unit": "px"
|
|
23
|
+
},
|
|
20
24
|
"groupId": "logo-settings",
|
|
21
25
|
"typeId": "@ikas/bp-storefront-models-HeightStyleType"
|
|
22
26
|
},
|
|
@@ -25,6 +29,10 @@
|
|
|
25
29
|
"displayName": "Mobile Logo Size",
|
|
26
30
|
"type": "TYPE",
|
|
27
31
|
"required": false,
|
|
32
|
+
"defaultValue": {
|
|
33
|
+
"value": 48,
|
|
34
|
+
"unit": "px"
|
|
35
|
+
},
|
|
28
36
|
"groupId": "logo-settings",
|
|
29
37
|
"typeId": "@ikas/bp-storefront-models-HeightStyleType"
|
|
30
38
|
},
|
|
@@ -40,6 +48,7 @@
|
|
|
40
48
|
"displayName": "Link Color",
|
|
41
49
|
"type": "COLOR",
|
|
42
50
|
"required": false,
|
|
51
|
+
"defaultValue": "#2e2e33",
|
|
43
52
|
"groupId": "navigation"
|
|
44
53
|
},
|
|
45
54
|
{
|
|
@@ -54,6 +63,7 @@
|
|
|
54
63
|
"displayName": "Link Color",
|
|
55
64
|
"type": "COLOR",
|
|
56
65
|
"required": false,
|
|
66
|
+
"defaultValue": "#2e2e33",
|
|
57
67
|
"groupId": "colored-links"
|
|
58
68
|
},
|
|
59
69
|
{
|
|
@@ -140,6 +150,7 @@
|
|
|
140
150
|
"displayName": "Hide Add to Cart Button",
|
|
141
151
|
"type": "BOOLEAN",
|
|
142
152
|
"required": false,
|
|
153
|
+
"defaultValue": false,
|
|
143
154
|
"groupId": "search-card-settings"
|
|
144
155
|
},
|
|
145
156
|
{
|
|
@@ -219,6 +230,19 @@
|
|
|
219
230
|
"displayName": "Components",
|
|
220
231
|
"type": "COMPONENT_LIST",
|
|
221
232
|
"required": false,
|
|
233
|
+
"defaultValue": {
|
|
234
|
+
"components": [
|
|
235
|
+
{
|
|
236
|
+
"codeComponentId": "nb34u3yu-card-product-name"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"codeComponentId": "nb34u3yu-card-product-variants"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"codeComponentId": "nb34u3yu-card-product-price"
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
},
|
|
222
246
|
"privateVarMap": {
|
|
223
247
|
"product": {
|
|
224
248
|
"id": "pvm_1772803767210_2",
|
|
@@ -236,6 +260,7 @@
|
|
|
236
260
|
"displayName": "Image Ratio",
|
|
237
261
|
"type": "ENUM",
|
|
238
262
|
"required": false,
|
|
263
|
+
"defaultValue": "Square",
|
|
239
264
|
"groupId": "search-image",
|
|
240
265
|
"enumTypeId": "{{ENUM_ID:AspectRatio}}"
|
|
241
266
|
},
|
|
@@ -244,6 +269,7 @@
|
|
|
244
269
|
"displayName": "Image Fit",
|
|
245
270
|
"type": "ENUM",
|
|
246
271
|
"required": false,
|
|
272
|
+
"defaultValue": "Cover",
|
|
247
273
|
"groupId": "search-image",
|
|
248
274
|
"enumTypeId": "{{ENUM_ID:ObjectFit}}"
|
|
249
275
|
},
|
|
@@ -260,6 +286,7 @@
|
|
|
260
286
|
"displayName": "Enable Sticky",
|
|
261
287
|
"type": "BOOLEAN",
|
|
262
288
|
"required": false,
|
|
289
|
+
"defaultValue": false,
|
|
263
290
|
"description": "When enabled, the navbar stays fixed at the top of the page and remains visible while scrolling",
|
|
264
291
|
"groupId": "settings"
|
|
265
292
|
},
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getFormattedHeightSize,
|
|
9
9
|
Router,
|
|
10
10
|
createMediaSrcset,
|
|
11
|
+
withRoutePrefix,
|
|
11
12
|
} from "@ikas/bp-storefront";
|
|
12
13
|
import { Props } from "./types";
|
|
13
14
|
import CartSidebar from "./components/CartSidebar";
|
|
@@ -104,10 +105,10 @@ export function Navbar(props: Props) {
|
|
|
104
105
|
{logo && (
|
|
105
106
|
<a
|
|
106
107
|
className="kombos-navbar__logo"
|
|
107
|
-
href="/"
|
|
108
|
+
href={withRoutePrefix("/")}
|
|
108
109
|
onClick={(e) => {
|
|
109
110
|
e.preventDefault();
|
|
110
|
-
Router.
|
|
111
|
+
Router.navigateToPage("INDEX");
|
|
111
112
|
}}
|
|
112
113
|
style={{
|
|
113
114
|
"--logo-h-desktop": logoSizeDesktop,
|
|
@@ -145,7 +146,7 @@ export function Navbar(props: Props) {
|
|
|
145
146
|
{/* Search */}
|
|
146
147
|
<a
|
|
147
148
|
className="kombos-navbar__icon-btn"
|
|
148
|
-
href="/search"
|
|
149
|
+
href={withRoutePrefix("/search")}
|
|
149
150
|
onClick={(e) => {
|
|
150
151
|
e.preventDefault();
|
|
151
152
|
e.stopPropagation();
|
|
@@ -159,7 +160,7 @@ export function Navbar(props: Props) {
|
|
|
159
160
|
{/* Account */}
|
|
160
161
|
<a
|
|
161
162
|
className="kombos-navbar__icon-btn"
|
|
162
|
-
href={isLoggedIn ? "/account" : "/login"}
|
|
163
|
+
href={withRoutePrefix(isLoggedIn ? "/account" : "/account/login")}
|
|
163
164
|
onClick={(e) => {
|
|
164
165
|
e.preventDefault();
|
|
165
166
|
e.stopPropagation();
|
|
@@ -173,7 +174,7 @@ export function Navbar(props: Props) {
|
|
|
173
174
|
{/* Cart */}
|
|
174
175
|
<a
|
|
175
176
|
className="kombos-navbar__icon-btn kombos-navbar__cart-trigger"
|
|
176
|
-
href="/cart"
|
|
177
|
+
href={withRoutePrefix("/cart")}
|
|
177
178
|
onClick={(e) => {
|
|
178
179
|
e.preventDefault();
|
|
179
180
|
e.stopPropagation();
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "@ikas/bp-storefront";
|
|
20
20
|
import { resolveAspectRatio, resolveObjectFit } from "../../utils/media";
|
|
21
21
|
import { cx } from "../../utils/cx";
|
|
22
|
+
import { useCartLineLimits } from "../../utils/cartLineLimits";
|
|
22
23
|
import type { AspectRatio, ObjectFit } from "../../global-types";
|
|
23
24
|
|
|
24
25
|
interface CartItemProps {
|
|
@@ -40,6 +41,10 @@ const CartItem = observer(function CartItem({
|
|
|
40
41
|
}: CartItemProps) {
|
|
41
42
|
const isPage = variant === "page";
|
|
42
43
|
|
|
44
|
+
// The line's IkasOrderLineVariant carries no salesChannels, so the per-cart
|
|
45
|
+
// quantity limits come from a batched product fetch shared across all lines.
|
|
46
|
+
const limits = useCartLineLimits(item.variant?.productId);
|
|
47
|
+
|
|
43
48
|
// Lines a campaign added automatically (e.g. a "buy 1 get 1" gift) cannot be
|
|
44
49
|
// edited or removed on their own, so render them read-only with a badge.
|
|
45
50
|
const isAutoCreated =
|
|
@@ -180,6 +185,8 @@ const CartItem = observer(function CartItem({
|
|
|
180
185
|
<QuantitySelector
|
|
181
186
|
size="sm"
|
|
182
187
|
value={item.quantity}
|
|
188
|
+
min={limits?.min ?? 1}
|
|
189
|
+
max={limits?.max}
|
|
183
190
|
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
184
191
|
/>
|
|
185
192
|
)}
|
|
@@ -227,6 +234,8 @@ const CartItem = observer(function CartItem({
|
|
|
227
234
|
<QuantitySelector
|
|
228
235
|
size="sm"
|
|
229
236
|
value={item.quantity}
|
|
237
|
+
min={limits?.min ?? 1}
|
|
238
|
+
max={limits?.max}
|
|
230
239
|
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
231
240
|
/>
|
|
232
241
|
<button
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { IkasProduct, IkasStorefrontConfig } from "@ikas/bp-storefront";
|
|
2
|
+
|
|
3
|
+
export interface CartQuantityLimits {
|
|
4
|
+
min: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the per-cart quantity limits for a product from its active sales
|
|
10
|
+
* channel. Falls back to a minimum of 1 and no maximum when the channel or its
|
|
11
|
+
* limits are not set. `addItemToCart` clamps to the same limits internally only
|
|
12
|
+
* when it creates a NEW cart line — incrementing an existing line goes through
|
|
13
|
+
* `changeItemQuantity`, which never clamps — so enforcing these in the stepper
|
|
14
|
+
* is the only guard that covers both paths.
|
|
15
|
+
*/
|
|
16
|
+
export function getProductCartLimits(product: IkasProduct): CartQuantityLimits {
|
|
17
|
+
const channel = product.salesChannels?.find(
|
|
18
|
+
(sc) => sc.id === IkasStorefrontConfig.salesChannelId,
|
|
19
|
+
);
|
|
20
|
+
return {
|
|
21
|
+
min: channel?.minQuantityPerCart ?? 1,
|
|
22
|
+
max: channel?.maxQuantityPerCart ?? undefined,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useEffect, useState } from "preact/hooks";
|
|
2
|
+
import { baseStore, bs_searchProductsById } from "@ikas/bp-storefront";
|
|
3
|
+
import { CartQuantityLimits, getProductCartLimits } from "./cartLimits";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Per-cart quantity limits for cart lines.
|
|
7
|
+
*
|
|
8
|
+
* A cart line's `IkasOrderLineVariant` carries no `salesChannels`, so the
|
|
9
|
+
* min/max per-cart limits can only be read from the full `IkasProduct`. This
|
|
10
|
+
* store batch-fetches the line products with `bs_searchProductsById` (lines
|
|
11
|
+
* register their productId one by one; the ids are flushed as ONE request on
|
|
12
|
+
* the next tick) and maps the resolved limits by product id for the cart
|
|
13
|
+
* steppers to read. Each product is fetched at most once; results stay cached
|
|
14
|
+
* for the session.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const limitsByProductId = new Map<string, CartQuantityLimits>();
|
|
18
|
+
const requestedProductIds = new Set<string>();
|
|
19
|
+
const listeners = new Set<() => void>();
|
|
20
|
+
|
|
21
|
+
let pendingProductIds: string[] = [];
|
|
22
|
+
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
23
|
+
|
|
24
|
+
function emit() {
|
|
25
|
+
for (const listener of listeners) listener();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function flush() {
|
|
29
|
+
flushTimer = null;
|
|
30
|
+
const batch = pendingProductIds;
|
|
31
|
+
pendingProductIds = [];
|
|
32
|
+
|
|
33
|
+
// bs_searchProductsById never throws — API/network failures resolve with
|
|
34
|
+
// isSuccess: false — so the retry unmark must live on the result check.
|
|
35
|
+
const result = await bs_searchProductsById(baseStore, { productIds: batch }).catch(
|
|
36
|
+
() => null,
|
|
37
|
+
);
|
|
38
|
+
if (!result?.isSuccess || !result.products) {
|
|
39
|
+
// Limits stay unenforced for now; unmark the batch so a later
|
|
40
|
+
// ensureCartLineLimits call (e.g. the next cart open) can retry.
|
|
41
|
+
for (const id of batch) requestedProductIds.delete(id);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const product of result.products) {
|
|
46
|
+
limitsByProductId.set(product.id, getProductCartLimits(product));
|
|
47
|
+
}
|
|
48
|
+
emit();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Register a cart line's productId for limit resolution. Ids already requested
|
|
53
|
+
* are skipped; new ids are collected and fetched in a single batched request
|
|
54
|
+
* on the next tick, so per-line calls do not fan out into per-line requests.
|
|
55
|
+
*/
|
|
56
|
+
export function ensureCartLineLimits(productId: string) {
|
|
57
|
+
if (requestedProductIds.has(productId)) return;
|
|
58
|
+
requestedProductIds.add(productId);
|
|
59
|
+
pendingProductIds.push(productId);
|
|
60
|
+
if (!flushTimer) flushTimer = setTimeout(flush, 0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolved limits for a product — undefined until the batch fetch lands. */
|
|
64
|
+
export function getCartLineLimits(
|
|
65
|
+
productId: string | null | undefined,
|
|
66
|
+
): CartQuantityLimits | undefined {
|
|
67
|
+
return productId ? limitsByProductId.get(productId) : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Subscribe to limit resolutions; returns the unsubscribe function. */
|
|
71
|
+
export function subscribeCartLineLimits(listener: () => void) {
|
|
72
|
+
listeners.add(listener);
|
|
73
|
+
return () => {
|
|
74
|
+
listeners.delete(listener);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Hook: resolved limits for one cart line's product — undefined until the
|
|
80
|
+
* batched fetch lands. Encapsulates the subscription protocol so components
|
|
81
|
+
* cannot get the ordering wrong.
|
|
82
|
+
*/
|
|
83
|
+
export function useCartLineLimits(
|
|
84
|
+
productId: string | null | undefined,
|
|
85
|
+
): CartQuantityLimits | undefined {
|
|
86
|
+
const [, setVersion] = useState(0);
|
|
87
|
+
const limits = getCartLineLimits(productId);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!productId) return;
|
|
90
|
+
// Subscribe BEFORE registering the id: a batch already in flight can
|
|
91
|
+
// resolve between render and this effect, and its emit must not be missed.
|
|
92
|
+
const unsubscribe = subscribeCartLineLimits(() => setVersion((v) => v + 1));
|
|
93
|
+
ensureCartLineLimits(productId);
|
|
94
|
+
// Re-check once for a result that landed before the subscription armed —
|
|
95
|
+
// skipped when the arming render already saw it (no redundant re-render).
|
|
96
|
+
if (!limits && getCartLineLimits(productId)) setVersion((v) => v + 1);
|
|
97
|
+
return unsubscribe;
|
|
98
|
+
}, [productId]);
|
|
99
|
+
return limits;
|
|
100
|
+
}
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"displayName": "Top Spacing",
|
|
41
41
|
"type": "TYPE",
|
|
42
42
|
"required": false,
|
|
43
|
+
"defaultValue": {
|
|
44
|
+
"value": 0,
|
|
45
|
+
"unit": "px"
|
|
46
|
+
},
|
|
43
47
|
"groupId": "mobile",
|
|
44
48
|
"typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
|
|
45
49
|
},
|
|
@@ -48,6 +52,10 @@
|
|
|
48
52
|
"displayName": "Bottom Spacing",
|
|
49
53
|
"type": "TYPE",
|
|
50
54
|
"required": false,
|
|
55
|
+
"defaultValue": {
|
|
56
|
+
"value": 0,
|
|
57
|
+
"unit": "px"
|
|
58
|
+
},
|
|
51
59
|
"groupId": "mobile",
|
|
52
60
|
"typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
|
|
53
61
|
},
|
|
@@ -56,6 +64,10 @@
|
|
|
56
64
|
"displayName": "Top Spacing",
|
|
57
65
|
"type": "TYPE",
|
|
58
66
|
"required": false,
|
|
67
|
+
"defaultValue": {
|
|
68
|
+
"value": 0,
|
|
69
|
+
"unit": "px"
|
|
70
|
+
},
|
|
59
71
|
"groupId": "desktop",
|
|
60
72
|
"typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
|
|
61
73
|
},
|
|
@@ -64,6 +76,10 @@
|
|
|
64
76
|
"displayName": "Bottom Spacing",
|
|
65
77
|
"type": "TYPE",
|
|
66
78
|
"required": false,
|
|
79
|
+
"defaultValue": {
|
|
80
|
+
"value": 0,
|
|
81
|
+
"unit": "px"
|
|
82
|
+
},
|
|
67
83
|
"groupId": "desktop",
|
|
68
84
|
"typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
|
|
69
85
|
},
|
|
@@ -72,6 +88,7 @@
|
|
|
72
88
|
"displayName": "Hide Quantity Selector",
|
|
73
89
|
"type": "BOOLEAN",
|
|
74
90
|
"required": false,
|
|
91
|
+
"defaultValue": false,
|
|
75
92
|
"groupId": "settings"
|
|
76
93
|
},
|
|
77
94
|
{
|
|
@@ -104,6 +104,10 @@
|
|
|
104
104
|
"displayName": "Top Spacing",
|
|
105
105
|
"type": "TYPE",
|
|
106
106
|
"required": false,
|
|
107
|
+
"defaultValue": {
|
|
108
|
+
"value": 0,
|
|
109
|
+
"unit": "px"
|
|
110
|
+
},
|
|
107
111
|
"groupId": "mobile",
|
|
108
112
|
"typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
|
|
109
113
|
},
|
|
@@ -112,6 +116,10 @@
|
|
|
112
116
|
"displayName": "Bottom Spacing",
|
|
113
117
|
"type": "TYPE",
|
|
114
118
|
"required": false,
|
|
119
|
+
"defaultValue": {
|
|
120
|
+
"value": 0,
|
|
121
|
+
"unit": "px"
|
|
122
|
+
},
|
|
115
123
|
"groupId": "mobile",
|
|
116
124
|
"typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
|
|
117
125
|
},
|
|
@@ -120,6 +128,10 @@
|
|
|
120
128
|
"displayName": "Top Spacing",
|
|
121
129
|
"type": "TYPE",
|
|
122
130
|
"required": false,
|
|
131
|
+
"defaultValue": {
|
|
132
|
+
"value": 0,
|
|
133
|
+
"unit": "px"
|
|
134
|
+
},
|
|
123
135
|
"groupId": "desktop",
|
|
124
136
|
"typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
|
|
125
137
|
},
|
|
@@ -128,6 +140,10 @@
|
|
|
128
140
|
"displayName": "Bottom Spacing",
|
|
129
141
|
"type": "TYPE",
|
|
130
142
|
"required": false,
|
|
143
|
+
"defaultValue": {
|
|
144
|
+
"value": 0,
|
|
145
|
+
"unit": "px"
|
|
146
|
+
},
|
|
131
147
|
"groupId": "desktop",
|
|
132
148
|
"typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
|
|
133
149
|
}
|
|
@@ -80,6 +80,10 @@
|
|
|
80
80
|
"displayName": "Top Spacing",
|
|
81
81
|
"type": "TYPE",
|
|
82
82
|
"required": false,
|
|
83
|
+
"defaultValue": {
|
|
84
|
+
"value": 0,
|
|
85
|
+
"unit": "px"
|
|
86
|
+
},
|
|
83
87
|
"groupId": "mobile",
|
|
84
88
|
"typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
|
|
85
89
|
},
|
|
@@ -88,6 +92,10 @@
|
|
|
88
92
|
"displayName": "Bottom Spacing",
|
|
89
93
|
"type": "TYPE",
|
|
90
94
|
"required": false,
|
|
95
|
+
"defaultValue": {
|
|
96
|
+
"value": 0,
|
|
97
|
+
"unit": "px"
|
|
98
|
+
},
|
|
91
99
|
"groupId": "mobile",
|
|
92
100
|
"typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
|
|
93
101
|
},
|
|
@@ -96,6 +104,10 @@
|
|
|
96
104
|
"displayName": "Top Spacing",
|
|
97
105
|
"type": "TYPE",
|
|
98
106
|
"required": false,
|
|
107
|
+
"defaultValue": {
|
|
108
|
+
"value": 0,
|
|
109
|
+
"unit": "px"
|
|
110
|
+
},
|
|
99
111
|
"groupId": "desktop",
|
|
100
112
|
"typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
|
|
101
113
|
},
|
|
@@ -104,6 +116,10 @@
|
|
|
104
116
|
"displayName": "Bottom Spacing",
|
|
105
117
|
"type": "TYPE",
|
|
106
118
|
"required": false,
|
|
119
|
+
"defaultValue": {
|
|
120
|
+
"value": 0,
|
|
121
|
+
"unit": "px"
|
|
122
|
+
},
|
|
107
123
|
"groupId": "desktop",
|
|
108
124
|
"typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
|
|
109
125
|
},
|
|
@@ -112,6 +128,7 @@
|
|
|
112
128
|
"displayName": "Aspect Ratio",
|
|
113
129
|
"type": "ENUM",
|
|
114
130
|
"required": false,
|
|
131
|
+
"defaultValue": "Square",
|
|
115
132
|
"groupId": "appearance",
|
|
116
133
|
"enumTypeId": "{{ENUM_ID:AspectRatio}}"
|
|
117
134
|
},
|
|
@@ -120,6 +137,7 @@
|
|
|
120
137
|
"displayName": "Image Fit",
|
|
121
138
|
"type": "ENUM",
|
|
122
139
|
"required": false,
|
|
140
|
+
"defaultValue": "Cover",
|
|
123
141
|
"groupId": "appearance",
|
|
124
142
|
"enumTypeId": "{{ENUM_ID:ObjectFit}}"
|
|
125
143
|
}
|