@ikas/code-components-mcp 1.4.0-beta.2 → 1.4.0-beta.200
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 +208 -78
- package/data/migration.json +196 -25
- package/data/section-templates/account-info-section/children/AccountFavorites/ikas-config-snippet.json +16 -3
- package/data/section-templates/account-info-section/children/AccountOrders/index.tsx +1 -1
- package/data/section-templates/account-info-section/ikas-config-snippet.json +24 -5
- package/data/section-templates/add-to-cart/ikas-config-snippet.json +17 -0
- package/data/section-templates/add-to-cart/index.tsx +16 -2
- package/data/section-templates/add-to-cart/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/add-to-cart/utils/cartLimits.ts +24 -0
- 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/hooks/useBundleProducts.ts +7 -6
- 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 +66 -24
- package/data/section-templates/cart-section/sub-components/CartItem/styles.css +14 -0
- package/data/section-templates/cart-section/sub-components/QuantitySelector/index.tsx +11 -3
- 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 +16 -1
- 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 +24 -3
- 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 +16 -3
- package/data/section-templates/favorites/ikas-config-snippet.json +18 -1
- package/data/section-templates/features-section/ikas-config-snippet.json +14 -1
- package/data/section-templates/footer-section/ikas-config-snippet.json +16 -1
- 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 +8 -1
- 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 +30 -3
- package/data/section-templates/header-section/children/Navbar/index.tsx +6 -5
- package/data/section-templates/header-section/ikas-config-snippet.json +16 -3
- package/data/section-templates/header-section/sub-components/CartItem/index.tsx +66 -24
- package/data/section-templates/header-section/sub-components/CartItem/styles.css +14 -0
- package/data/section-templates/header-section/sub-components/QuantitySelector/index.tsx +11 -3
- 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 +14 -1
- package/data/section-templates/image-handling/ikas-config-snippet.json +66 -13
- 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 +30 -3
- package/data/section-templates/navigation/index.tsx +6 -5
- package/data/section-templates/navigation/sub-components/CartItem/index.tsx +66 -24
- package/data/section-templates/navigation/sub-components/CartItem/styles.css +14 -0
- package/data/section-templates/navigation/sub-components/QuantitySelector/index.tsx +11 -3
- 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/ProductDetailAddToCart/index.tsx +16 -2
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/components/BackInStockForm/index.tsx +92 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +175 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx +141 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css +47 -0
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts +25 -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 +28 -1
- package/data/section-templates/product-detail-section/children/ProductDetailFeatures/ikas-config-snippet.json +30 -1
- 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/hooks/useBundleProducts.ts +7 -6
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +66 -13
- package/data/section-templates/product-detail-section/index.tsx +5 -1
- package/data/section-templates/product-detail-section/sub-components/Modal/index.tsx +120 -0
- package/data/section-templates/product-detail-section/sub-components/Modal/styles.css +70 -0
- package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx +11 -3
- package/data/section-templates/product-detail-section/utils/cartLimits.ts +24 -0
- 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 +20 -3
- 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 +819 -1443
- package/data/storefront-types.json +9644 -6132
- package/dist/index.js +2978 -347
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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();
|
|
@@ -10,10 +10,23 @@
|
|
|
10
10
|
"displayName": "Components",
|
|
11
11
|
"type": "COMPONENT_LIST",
|
|
12
12
|
"required": false,
|
|
13
|
+
"defaultValue": {
|
|
14
|
+
"components": [
|
|
15
|
+
{
|
|
16
|
+
"codeComponentId": "nb34u3yu-announcements"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"codeComponentId": "nb34u3yu-navbar"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"codeComponentId": "nb34u3yu-cookie-bar"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
13
26
|
"filteredComponentIds": [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
27
|
+
"<id-of-Navbar>",
|
|
28
|
+
"<id-of-Announcements>",
|
|
29
|
+
"<id-of-CookieBar>"
|
|
17
30
|
]
|
|
18
31
|
},
|
|
19
32
|
{
|
|
@@ -14,9 +14,12 @@ import {
|
|
|
14
14
|
getOrderLineItemFormattedOverridenPriceWithQuantity,
|
|
15
15
|
hasOrderLineItemDiscount,
|
|
16
16
|
getOrderLineItemOverridenPriceWithQuantity,
|
|
17
|
+
cartStore,
|
|
18
|
+
isOrderLineItemAutoCreated,
|
|
17
19
|
} from "@ikas/bp-storefront";
|
|
18
20
|
import { resolveAspectRatio, resolveObjectFit } from "../../utils/media";
|
|
19
21
|
import { cx } from "../../utils/cx";
|
|
22
|
+
import { useCartLineLimits } from "../../utils/cartLineLimits";
|
|
20
23
|
import type { AspectRatio, ObjectFit } from "../../global-types";
|
|
21
24
|
|
|
22
25
|
interface CartItemProps {
|
|
@@ -25,6 +28,7 @@ interface CartItemProps {
|
|
|
25
28
|
variant?: "sidebar" | "page";
|
|
26
29
|
aspectRatio?: AspectRatio;
|
|
27
30
|
objectFit?: ObjectFit;
|
|
31
|
+
giftLabel?: string;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const CartItem = observer(function CartItem({
|
|
@@ -33,9 +37,29 @@ const CartItem = observer(function CartItem({
|
|
|
33
37
|
variant = "sidebar",
|
|
34
38
|
aspectRatio,
|
|
35
39
|
objectFit,
|
|
40
|
+
giftLabel = "Gift",
|
|
36
41
|
}: CartItemProps) {
|
|
37
42
|
const isPage = variant === "page";
|
|
38
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
|
+
|
|
48
|
+
// Lines a campaign added automatically (e.g. a "buy 1 get 1" gift) cannot be
|
|
49
|
+
// edited or removed on their own, so render them read-only with a badge.
|
|
50
|
+
const isAutoCreated =
|
|
51
|
+
!!cartStore.cart && isOrderLineItemAutoCreated(cartStore.cart, item);
|
|
52
|
+
const giftBadge = isAutoCreated ? (
|
|
53
|
+
<span className="kombos-cart-item__gift-badge text-xs-medium">
|
|
54
|
+
{giftLabel}
|
|
55
|
+
</span>
|
|
56
|
+
) : null;
|
|
57
|
+
const staticQuantity = (
|
|
58
|
+
<span className="kombos-cart-item__qty-static text-sm-medium">
|
|
59
|
+
×{item.quantity}
|
|
60
|
+
</span>
|
|
61
|
+
);
|
|
62
|
+
|
|
39
63
|
const mediaStyle: Record<string, string> = {
|
|
40
64
|
aspectRatio: resolveAspectRatio(aspectRatio),
|
|
41
65
|
objectFit: resolveObjectFit(objectFit),
|
|
@@ -103,6 +127,7 @@ const CartItem = observer(function CartItem({
|
|
|
103
127
|
<a href={href} className={nameClass}>
|
|
104
128
|
{item.variant?.name}
|
|
105
129
|
</a>
|
|
130
|
+
{giftBadge}
|
|
106
131
|
{variantValues.length > 0 && (
|
|
107
132
|
<p className="kombos-cart-item__variant-text text-sm-regular">
|
|
108
133
|
{variantValues
|
|
@@ -113,19 +138,22 @@ const CartItem = observer(function CartItem({
|
|
|
113
138
|
</p>
|
|
114
139
|
)}
|
|
115
140
|
</div>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
{!isAutoCreated && (
|
|
142
|
+
<button
|
|
143
|
+
className="kombos-cart-item__remove"
|
|
144
|
+
onClick={() => onRemove(item)}
|
|
145
|
+
aria-label="Remove"
|
|
146
|
+
>
|
|
147
|
+
<TrashSVG />
|
|
148
|
+
</button>
|
|
149
|
+
)}
|
|
123
150
|
</div>
|
|
124
151
|
) : (
|
|
125
152
|
<>
|
|
126
153
|
<a href={href} className={nameClass}>
|
|
127
154
|
{item.variant?.name}
|
|
128
155
|
</a>
|
|
156
|
+
{giftBadge}
|
|
129
157
|
|
|
130
158
|
{variantValues.length > 0 && (
|
|
131
159
|
<div className="kombos-cart-item__variants">
|
|
@@ -151,11 +179,17 @@ const CartItem = observer(function CartItem({
|
|
|
151
179
|
|
|
152
180
|
{isPage ? (
|
|
153
181
|
<div className="kombos-cart-item__bottom">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
182
|
+
{isAutoCreated ? (
|
|
183
|
+
staticQuantity
|
|
184
|
+
) : (
|
|
185
|
+
<QuantitySelector
|
|
186
|
+
size="sm"
|
|
187
|
+
value={item.quantity}
|
|
188
|
+
min={limits?.min ?? 1}
|
|
189
|
+
max={limits?.max}
|
|
190
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
191
|
+
/>
|
|
192
|
+
)}
|
|
159
193
|
<div className="kombos-cart-item__prices">
|
|
160
194
|
{hasDiscount && (
|
|
161
195
|
<span className="kombos-cart-item__original-price text-sm-regular-strike">
|
|
@@ -193,18 +227,26 @@ const CartItem = observer(function CartItem({
|
|
|
193
227
|
</div>
|
|
194
228
|
|
|
195
229
|
<div className="kombos-cart-item__actions">
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
230
|
+
{isAutoCreated ? (
|
|
231
|
+
staticQuantity
|
|
232
|
+
) : (
|
|
233
|
+
<>
|
|
234
|
+
<QuantitySelector
|
|
235
|
+
size="sm"
|
|
236
|
+
value={item.quantity}
|
|
237
|
+
min={limits?.min ?? 1}
|
|
238
|
+
max={limits?.max}
|
|
239
|
+
onChange={(qty) => changeItemQuantity(item, qty)}
|
|
240
|
+
/>
|
|
241
|
+
<button
|
|
242
|
+
className="kombos-cart-item__remove"
|
|
243
|
+
onClick={() => onRemove(item)}
|
|
244
|
+
aria-label="Remove"
|
|
245
|
+
>
|
|
246
|
+
<TrashSVG />
|
|
247
|
+
</button>
|
|
248
|
+
</>
|
|
249
|
+
)}
|
|
208
250
|
</div>
|
|
209
251
|
</>
|
|
210
252
|
)}
|
|
@@ -168,3 +168,17 @@
|
|
|
168
168
|
flex-shrink: 0;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/* Auto-created (campaign gift) line: badge + read-only quantity */
|
|
172
|
+
.kombos-cart-item__gift-badge {
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
align-self: flex-start;
|
|
175
|
+
padding: 0.125rem 0.5rem;
|
|
176
|
+
border-radius: 999px;
|
|
177
|
+
background: var(--kombos-gray-100);
|
|
178
|
+
color: var(--kombos-gray-500);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.kombos-cart-item__qty-static {
|
|
182
|
+
color: var(--kombos-gray-500);
|
|
183
|
+
}
|
|
184
|
+
|
|
@@ -5,20 +5,27 @@ interface Props {
|
|
|
5
5
|
value: number;
|
|
6
6
|
onChange: (value: number) => void;
|
|
7
7
|
size?: "default" | "sm";
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export default function QuantitySelector({
|
|
11
13
|
value,
|
|
12
14
|
onChange,
|
|
13
15
|
size = "default",
|
|
16
|
+
min = 1,
|
|
17
|
+
max,
|
|
14
18
|
}: Props) {
|
|
19
|
+
const canDecrease = value > min;
|
|
20
|
+
const canIncrease = max == null || value < max;
|
|
21
|
+
|
|
15
22
|
return (
|
|
16
23
|
<div className={cx("kombos-pd__qty", size === "sm" && "kombos-pd__qty--sm")}>
|
|
17
24
|
<button
|
|
18
25
|
type="button"
|
|
19
26
|
className="kombos-pd__qty-btn"
|
|
20
|
-
onClick={() => onChange(Math.max(
|
|
21
|
-
disabled={
|
|
27
|
+
onClick={() => onChange(Math.max(min, value - 1))}
|
|
28
|
+
disabled={!canDecrease}
|
|
22
29
|
aria-label="Decrease quantity"
|
|
23
30
|
>
|
|
24
31
|
<MinusSVG />
|
|
@@ -31,7 +38,8 @@ export default function QuantitySelector({
|
|
|
31
38
|
<button
|
|
32
39
|
type="button"
|
|
33
40
|
className="kombos-pd__qty-btn"
|
|
34
|
-
onClick={() => onChange(value + 1)}
|
|
41
|
+
onClick={() => onChange(max == null ? value + 1 : Math.min(max, value + 1))}
|
|
42
|
+
disabled={!canIncrease}
|
|
35
43
|
aria-label="Increase quantity"
|
|
36
44
|
>
|
|
37
45
|
<PlusSVG />
|
|
@@ -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
|
+
}
|
package/data/section-templates/hero-slider-section/children/HeroSliderItem/ikas-config-snippet.json
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"displayName": "Image Fit",
|
|
26
26
|
"type": "ENUM",
|
|
27
27
|
"required": false,
|
|
28
|
+
"defaultValue": "Cover",
|
|
28
29
|
"groupId": "desktop",
|
|
29
30
|
"enumTypeId": "{{ENUM_ID:ObjectFit}}"
|
|
30
31
|
},
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
"displayName": "Title Alignment",
|
|
41
42
|
"type": "ENUM",
|
|
42
43
|
"required": false,
|
|
44
|
+
"defaultValue": "Middle",
|
|
43
45
|
"groupId": "desktop",
|
|
44
46
|
"enumTypeId": "{{ENUM_ID:VerticalAlignment}}"
|
|
45
47
|
},
|
|
@@ -48,6 +50,7 @@
|
|
|
48
50
|
"displayName": "Text Color",
|
|
49
51
|
"type": "COLOR",
|
|
50
52
|
"required": false,
|
|
53
|
+
"defaultValue": "#FFFFFF",
|
|
51
54
|
"groupId": "desktop"
|
|
52
55
|
},
|
|
53
56
|
{
|
|
@@ -55,6 +58,7 @@
|
|
|
55
58
|
"displayName": "Overlay",
|
|
56
59
|
"type": "BOOLEAN",
|
|
57
60
|
"required": false,
|
|
61
|
+
"defaultValue": false,
|
|
58
62
|
"groupId": "desktop"
|
|
59
63
|
},
|
|
60
64
|
{
|
|
@@ -62,6 +66,7 @@
|
|
|
62
66
|
"displayName": "Rounded Corners",
|
|
63
67
|
"type": "BOOLEAN",
|
|
64
68
|
"required": false,
|
|
69
|
+
"defaultValue": false,
|
|
65
70
|
"groupId": "desktop"
|
|
66
71
|
},
|
|
67
72
|
{
|
|
@@ -84,6 +89,7 @@
|
|
|
84
89
|
"displayName": "Image Fit",
|
|
85
90
|
"type": "ENUM",
|
|
86
91
|
"required": false,
|
|
92
|
+
"defaultValue": "Cover",
|
|
87
93
|
"groupId": "mobile",
|
|
88
94
|
"enumTypeId": "{{ENUM_ID:ObjectFit}}"
|
|
89
95
|
},
|
|
@@ -99,6 +105,7 @@
|
|
|
99
105
|
"displayName": "Title Alignment",
|
|
100
106
|
"type": "ENUM",
|
|
101
107
|
"required": false,
|
|
108
|
+
"defaultValue": "Top",
|
|
102
109
|
"groupId": "mobile",
|
|
103
110
|
"enumTypeId": "{{ENUM_ID:VerticalAlignment}}"
|
|
104
111
|
},
|
|
@@ -107,6 +114,7 @@
|
|
|
107
114
|
"displayName": "Text Color",
|
|
108
115
|
"type": "COLOR",
|
|
109
116
|
"required": false,
|
|
117
|
+
"defaultValue": "#FFFFFF",
|
|
110
118
|
"groupId": "mobile"
|
|
111
119
|
},
|
|
112
120
|
{
|
|
@@ -114,6 +122,7 @@
|
|
|
114
122
|
"displayName": "Overlay",
|
|
115
123
|
"type": "BOOLEAN",
|
|
116
124
|
"required": false,
|
|
125
|
+
"defaultValue": false,
|
|
117
126
|
"groupId": "mobile"
|
|
118
127
|
},
|
|
119
128
|
{
|
|
@@ -121,6 +130,7 @@
|
|
|
121
130
|
"displayName": "Rounded Corners",
|
|
122
131
|
"type": "BOOLEAN",
|
|
123
132
|
"required": false,
|
|
133
|
+
"defaultValue": false,
|
|
124
134
|
"groupId": "mobile"
|
|
125
135
|
},
|
|
126
136
|
{
|
|
@@ -143,6 +153,7 @@
|
|
|
143
153
|
"displayName": "Color",
|
|
144
154
|
"type": "COLOR",
|
|
145
155
|
"required": false,
|
|
156
|
+
"defaultValue": "#14141a",
|
|
146
157
|
"groupId": "desktopButton"
|
|
147
158
|
},
|
|
148
159
|
{
|
|
@@ -150,6 +161,7 @@
|
|
|
150
161
|
"displayName": "Background Color",
|
|
151
162
|
"type": "COLOR",
|
|
152
163
|
"required": false,
|
|
164
|
+
"defaultValue": "#ffffff",
|
|
153
165
|
"groupId": "desktopButton"
|
|
154
166
|
},
|
|
155
167
|
{
|
|
@@ -157,6 +169,7 @@
|
|
|
157
169
|
"displayName": "Color (Hover)",
|
|
158
170
|
"type": "COLOR",
|
|
159
171
|
"required": false,
|
|
172
|
+
"defaultValue": "#14141a",
|
|
160
173
|
"groupId": "desktopButton"
|
|
161
174
|
},
|
|
162
175
|
{
|
|
@@ -164,6 +177,7 @@
|
|
|
164
177
|
"displayName": "Background Color (Hover)",
|
|
165
178
|
"type": "COLOR",
|
|
166
179
|
"required": false,
|
|
180
|
+
"defaultValue": "#fafafa",
|
|
167
181
|
"groupId": "desktopButton"
|
|
168
182
|
},
|
|
169
183
|
{
|
|
@@ -186,6 +200,7 @@
|
|
|
186
200
|
"displayName": "Color",
|
|
187
201
|
"type": "COLOR",
|
|
188
202
|
"required": false,
|
|
203
|
+
"defaultValue": "#14141a",
|
|
189
204
|
"groupId": "mobileButton"
|
|
190
205
|
},
|
|
191
206
|
{
|
|
@@ -193,6 +208,7 @@
|
|
|
193
208
|
"displayName": "Background Color",
|
|
194
209
|
"type": "COLOR",
|
|
195
210
|
"required": false,
|
|
211
|
+
"defaultValue": "#ffffff",
|
|
196
212
|
"groupId": "mobileButton"
|
|
197
213
|
},
|
|
198
214
|
{
|
|
@@ -200,6 +216,7 @@
|
|
|
200
216
|
"displayName": "Color (Hover)",
|
|
201
217
|
"type": "COLOR",
|
|
202
218
|
"required": false,
|
|
219
|
+
"defaultValue": "#14141a",
|
|
203
220
|
"groupId": "mobileButton"
|
|
204
221
|
},
|
|
205
222
|
{
|
|
@@ -207,6 +224,7 @@
|
|
|
207
224
|
"displayName": "Background Color (Hover)",
|
|
208
225
|
"type": "COLOR",
|
|
209
226
|
"required": false,
|
|
227
|
+
"defaultValue": "#fafafa",
|
|
210
228
|
"groupId": "mobileButton"
|
|
211
229
|
}
|
|
212
230
|
],
|
|
@@ -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,8 +50,18 @@
|
|
|
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
|
]
|
|
53
66
|
}
|
|
54
67
|
],
|