@ikas/code-components-mcp 1.4.0-beta.139 → 1.4.0-beta.140
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 +2 -32
- package/data/section-templates/add-to-cart/index.tsx +2 -16
- package/data/section-templates/add-to-cart/sub-components/QuantitySelector/index.tsx +3 -11
- package/data/section-templates/cart-section/sub-components/CartItem/index.tsx +24 -57
- package/data/section-templates/cart-section/sub-components/CartItem/styles.css +0 -14
- package/data/section-templates/cart-section/sub-components/QuantitySelector/index.tsx +3 -11
- package/data/section-templates/header-section/sub-components/CartItem/index.tsx +24 -57
- package/data/section-templates/header-section/sub-components/CartItem/styles.css +0 -14
- package/data/section-templates/header-section/sub-components/QuantitySelector/index.tsx +3 -11
- package/data/section-templates/image-handling/ikas-config-snippet.json +0 -1
- package/data/section-templates/navigation/sub-components/CartItem/index.tsx +24 -57
- package/data/section-templates/navigation/sub-components/CartItem/styles.css +0 -14
- package/data/section-templates/navigation/sub-components/QuantitySelector/index.tsx +3 -11
- package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/index.tsx +2 -16
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +0 -1
- package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx +3 -11
- package/data/storefront-api.json +1 -1
- package/data/storefront-types.json +1 -1
- package/package.json +1 -1
- package/data/section-templates/add-to-cart/utils/cartLimits.ts +0 -23
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/components/BackInStockForm/index.tsx +0 -92
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +0 -159
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx +0 -141
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css +0 -47
- package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts +0 -25
- package/data/section-templates/product-detail-section/sub-components/Modal/index.tsx +0 -120
- package/data/section-templates/product-detail-section/sub-components/Modal/styles.css +0 -70
- package/data/section-templates/product-detail-section/utils/cartLimits.ts +0 -23
package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSelectedProductVariant,
|
|
3
|
-
hasProductStock,
|
|
4
|
-
hasProductVariantStock,
|
|
5
|
-
getProductVariantIsBackInStockEnabled,
|
|
6
|
-
getProductVariantIsBackInStockCustomerLoginRequired,
|
|
7
|
-
saveProductVariantBackInStockReminder,
|
|
8
|
-
getFormattedMarginTopSize,
|
|
9
|
-
getFormattedMarginBottomSize,
|
|
10
|
-
customerStore,
|
|
11
|
-
Router,
|
|
12
|
-
} from "@ikas/bp-storefront";
|
|
13
|
-
import { useEffect, useState } from "preact/hooks";
|
|
14
|
-
import { Props } from "./types";
|
|
15
|
-
import Button from "../../sub-components/Button";
|
|
16
|
-
import Modal from "../../sub-components/Modal";
|
|
17
|
-
import { showToast } from "../../utils/toast";
|
|
18
|
-
import BackInStockForm from "./components/BackInStockForm";
|
|
19
|
-
|
|
20
|
-
export function ProductDetailBackInStock({
|
|
21
|
-
product,
|
|
22
|
-
notifyButtonText = "Notify Me When Available",
|
|
23
|
-
reminderSavedText = "We'll notify you",
|
|
24
|
-
alreadySavedMessage = "You're already on the list — we'll email you when it's back.",
|
|
25
|
-
savedMessage = "Great! We'll email you when this product is back in stock.",
|
|
26
|
-
loginRequiredMessage = "Please sign in so we can notify you.",
|
|
27
|
-
errorMessage = "Something went wrong. Please try again.",
|
|
28
|
-
modalTitle = "Notify me when available",
|
|
29
|
-
modalDescription = "Enter your email and we'll let you know the moment this product is back in stock.",
|
|
30
|
-
emailLabel = "Email",
|
|
31
|
-
submitButtonText = "Notify Me",
|
|
32
|
-
submittingText = "Saving...",
|
|
33
|
-
mobileMarginTop,
|
|
34
|
-
mobileMarginBottom,
|
|
35
|
-
desktopMarginTop,
|
|
36
|
-
desktopMarginBottom,
|
|
37
|
-
}: Props) {
|
|
38
|
-
const [isEmailModalOpen, setIsEmailModalOpen] = useState(false);
|
|
39
|
-
const [reminderSaved, setReminderSaved] = useState(false);
|
|
40
|
-
|
|
41
|
-
const selectedVariant = product
|
|
42
|
-
? getSelectedProductVariant(product)
|
|
43
|
-
: undefined;
|
|
44
|
-
|
|
45
|
-
// Reset local UI state when the selected variant changes, seeding "saved"
|
|
46
|
-
// from the variant's own persisted reminder state.
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
setReminderSaved(!!selectedVariant?.isBackInStockReminderSaved);
|
|
49
|
-
setIsEmailModalOpen(false);
|
|
50
|
-
}, [selectedVariant?.id]);
|
|
51
|
-
|
|
52
|
-
if (!product || !selectedVariant) return null;
|
|
53
|
-
|
|
54
|
-
// This component owns the notify-me flow only. Stay out of the way unless the
|
|
55
|
-
// variant is out of stock AND back-in-stock reminders are enabled for it.
|
|
56
|
-
const isOutOfStock =
|
|
57
|
-
!hasProductStock(product) || !hasProductVariantStock(selectedVariant);
|
|
58
|
-
if (!isOutOfStock || !getProductVariantIsBackInStockEnabled(selectedVariant)) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Login-required branch: register a logged-in customer immediately, otherwise
|
|
63
|
-
// send an anonymous visitor to login first.
|
|
64
|
-
const registerWithCustomerEmail = async () => {
|
|
65
|
-
const email = customerStore.customer?.email;
|
|
66
|
-
if (!email) {
|
|
67
|
-
showToast(loginRequiredMessage, "error");
|
|
68
|
-
Router.navigateToPage("LOGIN");
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const ok = await saveProductVariantBackInStockReminder(selectedVariant, email);
|
|
72
|
-
setReminderSaved(ok);
|
|
73
|
-
showToast(ok ? savedMessage : errorMessage, ok ? "success" : "error");
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const handleNotifyClick = () => {
|
|
77
|
-
if (reminderSaved) {
|
|
78
|
-
showToast(alreadySavedMessage, "success");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (getProductVariantIsBackInStockCustomerLoginRequired(selectedVariant)) {
|
|
82
|
-
void registerWithCustomerEmail();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
// No login required: collect the email through the form.
|
|
86
|
-
setIsEmailModalOpen(true);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const handleFormSaved = () => {
|
|
90
|
-
setReminderSaved(true);
|
|
91
|
-
setIsEmailModalOpen(false);
|
|
92
|
-
showToast(savedMessage, "success");
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<div
|
|
97
|
-
className="kombos-pd-bis"
|
|
98
|
-
style={{
|
|
99
|
-
"--mobile-mt": getFormattedMarginTopSize(mobileMarginTop),
|
|
100
|
-
"--mobile-mb": getFormattedMarginBottomSize(mobileMarginBottom),
|
|
101
|
-
"--desktop-mt": getFormattedMarginTopSize(desktopMarginTop),
|
|
102
|
-
"--desktop-mb": getFormattedMarginBottomSize(desktopMarginBottom),
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
{reminderSaved ? (
|
|
106
|
-
<div className="kombos-pd-bis__saved" aria-live="polite">
|
|
107
|
-
{reminderSavedText}
|
|
108
|
-
</div>
|
|
109
|
-
) : (
|
|
110
|
-
<Button
|
|
111
|
-
variant="secondary"
|
|
112
|
-
size="s"
|
|
113
|
-
className="kombos-pd-bis__btn"
|
|
114
|
-
onClick={handleNotifyClick}
|
|
115
|
-
>
|
|
116
|
-
{notifyButtonText}
|
|
117
|
-
</Button>
|
|
118
|
-
)}
|
|
119
|
-
|
|
120
|
-
{isEmailModalOpen && (
|
|
121
|
-
<Modal
|
|
122
|
-
title={modalTitle}
|
|
123
|
-
onClose={() => setIsEmailModalOpen(false)}
|
|
124
|
-
footer={null}
|
|
125
|
-
>
|
|
126
|
-
<BackInStockForm
|
|
127
|
-
variant={selectedVariant}
|
|
128
|
-
description={modalDescription}
|
|
129
|
-
emailLabel={emailLabel}
|
|
130
|
-
submitButtonText={submitButtonText}
|
|
131
|
-
submittingText={submittingText}
|
|
132
|
-
errorMessage={errorMessage}
|
|
133
|
-
onSaved={handleFormSaved}
|
|
134
|
-
/>
|
|
135
|
-
</Modal>
|
|
136
|
-
)}
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default ProductDetailBackInStock;
|
package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/* ===== ProductDetailBackInStock ===== */
|
|
2
|
-
|
|
3
|
-
.kombos-pd-bis {
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-direction: column;
|
|
6
|
-
gap: 0.75rem;
|
|
7
|
-
margin-top: var(--mobile-mt);
|
|
8
|
-
margin-bottom: var(--mobile-mb);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.kombos-pd-bis__btn {
|
|
12
|
-
width: 100%;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.kombos-pd-bis__saved {
|
|
16
|
-
padding: 0.75rem 1rem;
|
|
17
|
-
border: 1px solid var(--color-border, #e5e5e5);
|
|
18
|
-
border-radius: 0.5rem;
|
|
19
|
-
text-align: center;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.kombos-pd-bis__form {
|
|
23
|
-
display: flex;
|
|
24
|
-
flex-direction: column;
|
|
25
|
-
gap: 1rem;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.kombos-pd-bis__desc {
|
|
29
|
-
margin: 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.kombos-pd-bis__error {
|
|
33
|
-
margin: 0;
|
|
34
|
-
color: var(--color-danger, #d92d20);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.kombos-pd-bis__submit {
|
|
38
|
-
width: 100%;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/* ===== Desktop (>=1024px) ===== */
|
|
42
|
-
@media (min-width: 1024px) {
|
|
43
|
-
.kombos-pd-bis {
|
|
44
|
-
margin-top: var(--desktop-mt, var(--mobile-mt));
|
|
45
|
-
margin-bottom: var(--desktop-mb, var(--mobile-mb));
|
|
46
|
-
}
|
|
47
|
-
}
|
package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// This file is auto-generated — do not edit manually.
|
|
2
|
-
import type {
|
|
3
|
-
IkasProduct,
|
|
4
|
-
MarginTopStyleType,
|
|
5
|
-
MarginBottomStyleType,
|
|
6
|
-
} from "@ikas/bp-storefront";
|
|
7
|
-
|
|
8
|
-
export interface Props {
|
|
9
|
-
product?: IkasProduct | null;
|
|
10
|
-
notifyButtonText?: string;
|
|
11
|
-
reminderSavedText?: string;
|
|
12
|
-
alreadySavedMessage?: string;
|
|
13
|
-
savedMessage?: string;
|
|
14
|
-
loginRequiredMessage?: string;
|
|
15
|
-
errorMessage?: string;
|
|
16
|
-
modalTitle?: string;
|
|
17
|
-
modalDescription?: string;
|
|
18
|
-
emailLabel?: string;
|
|
19
|
-
submitButtonText?: string;
|
|
20
|
-
submittingText?: string;
|
|
21
|
-
mobileMarginTop?: MarginTopStyleType;
|
|
22
|
-
mobileMarginBottom?: MarginBottomStyleType;
|
|
23
|
-
desktopMarginTop?: MarginTopStyleType;
|
|
24
|
-
desktopMarginBottom?: MarginBottomStyleType;
|
|
25
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useId } from "preact/hooks";
|
|
2
|
-
import { createPortal } from "preact/compat";
|
|
3
|
-
import type { ComponentChildren } from "preact";
|
|
4
|
-
import { useScrollLock } from "../../hooks/useScrollLock";
|
|
5
|
-
import { cx } from "../../utils/cx";
|
|
6
|
-
import { XSVG } from "../icons";
|
|
7
|
-
import Button, { type ButtonProps } from "../Button";
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
onClose: () => void;
|
|
11
|
-
title: string;
|
|
12
|
-
maxWidth?: string;
|
|
13
|
-
children: ComponentChildren;
|
|
14
|
-
className?: string;
|
|
15
|
-
okText?: string;
|
|
16
|
-
cancelText?: string;
|
|
17
|
-
onOk?: (e: Event) => void;
|
|
18
|
-
okButtonProps?: ButtonProps;
|
|
19
|
-
cancelButtonProps?: ButtonProps;
|
|
20
|
-
footer?: ComponentChildren | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default function Modal({
|
|
24
|
-
onClose,
|
|
25
|
-
title,
|
|
26
|
-
maxWidth = "37.5rem",
|
|
27
|
-
children,
|
|
28
|
-
className,
|
|
29
|
-
okText,
|
|
30
|
-
cancelText,
|
|
31
|
-
onOk,
|
|
32
|
-
okButtonProps,
|
|
33
|
-
cancelButtonProps,
|
|
34
|
-
footer,
|
|
35
|
-
}: Props) {
|
|
36
|
-
const titleId = useId();
|
|
37
|
-
const [visible, setVisible] = useState(false);
|
|
38
|
-
const onCloseRef = useRef(onClose);
|
|
39
|
-
onCloseRef.current = onClose;
|
|
40
|
-
|
|
41
|
-
const handleCloseRef = useRef(() => {});
|
|
42
|
-
handleCloseRef.current = () => {
|
|
43
|
-
setVisible(false);
|
|
44
|
-
setTimeout(() => onCloseRef.current(), 300);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
useScrollLock();
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const rafId = requestAnimationFrame(() => setVisible(true));
|
|
51
|
-
return () => cancelAnimationFrame(rafId);
|
|
52
|
-
}, []);
|
|
53
|
-
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
56
|
-
if (e.key === "Escape") handleCloseRef.current();
|
|
57
|
-
};
|
|
58
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
59
|
-
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
60
|
-
}, []);
|
|
61
|
-
|
|
62
|
-
return createPortal(
|
|
63
|
-
<div
|
|
64
|
-
className={cx("kombos-modal", visible && "kombos-modal--open", className)}
|
|
65
|
-
role="dialog"
|
|
66
|
-
aria-modal="true"
|
|
67
|
-
aria-labelledby={titleId}
|
|
68
|
-
>
|
|
69
|
-
<div className="kombos-modal__panel" style={{ maxWidth }}>
|
|
70
|
-
<div className="kombos-modal__header">
|
|
71
|
-
<h2 id={titleId} className="kombos-modal__title text-md-medium">
|
|
72
|
-
{title}
|
|
73
|
-
</h2>
|
|
74
|
-
<button
|
|
75
|
-
type="button"
|
|
76
|
-
className="kombos-modal__close"
|
|
77
|
-
onClick={() => handleCloseRef.current()}
|
|
78
|
-
aria-label="Close"
|
|
79
|
-
>
|
|
80
|
-
<XSVG />
|
|
81
|
-
</button>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div className="kombos-modal__body">{children}</div>
|
|
85
|
-
|
|
86
|
-
{footer !== null &&
|
|
87
|
-
(footer !== undefined ? (
|
|
88
|
-
<div className="kombos-modal__footer">{footer}</div>
|
|
89
|
-
) : (
|
|
90
|
-
(cancelText || okText) && (
|
|
91
|
-
<div className="kombos-modal__footer">
|
|
92
|
-
{cancelText && (
|
|
93
|
-
<Button
|
|
94
|
-
variant="secondary"
|
|
95
|
-
size="s"
|
|
96
|
-
type="button"
|
|
97
|
-
onClick={() => handleCloseRef.current()}
|
|
98
|
-
{...cancelButtonProps}
|
|
99
|
-
>
|
|
100
|
-
{cancelText}
|
|
101
|
-
</Button>
|
|
102
|
-
)}
|
|
103
|
-
{okText && (
|
|
104
|
-
<Button
|
|
105
|
-
variant="primary"
|
|
106
|
-
size="s"
|
|
107
|
-
onClick={onOk}
|
|
108
|
-
{...okButtonProps}
|
|
109
|
-
>
|
|
110
|
-
{okText}
|
|
111
|
-
</Button>
|
|
112
|
-
)}
|
|
113
|
-
</div>
|
|
114
|
-
)
|
|
115
|
-
))}
|
|
116
|
-
</div>
|
|
117
|
-
</div>,
|
|
118
|
-
document.body,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/* ── Modal ── */
|
|
2
|
-
.kombos-modal {
|
|
3
|
-
position: fixed;
|
|
4
|
-
inset: 0;
|
|
5
|
-
z-index: var(--kombos-z-overlay);
|
|
6
|
-
background: rgba(0, 0, 0, 0.35);
|
|
7
|
-
display: flex;
|
|
8
|
-
align-items: center;
|
|
9
|
-
justify-content: center;
|
|
10
|
-
opacity: 0;
|
|
11
|
-
transition: opacity 0.3s;
|
|
12
|
-
padding: 1rem;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.kombos-modal--open {
|
|
16
|
-
opacity: 1;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.kombos-modal__panel {
|
|
20
|
-
background: var(--kombos-white);
|
|
21
|
-
border-radius: 6px;
|
|
22
|
-
width: 100%;
|
|
23
|
-
max-height: 90vh;
|
|
24
|
-
display: flex;
|
|
25
|
-
flex-direction: column;
|
|
26
|
-
transform: translateY(1rem);
|
|
27
|
-
transition: transform 0.3s;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.kombos-modal--open .kombos-modal__panel {
|
|
31
|
-
transform: translateY(0);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.kombos-modal__header {
|
|
35
|
-
display: flex;
|
|
36
|
-
justify-content: space-between;
|
|
37
|
-
align-items: center;
|
|
38
|
-
background: var(--kombos-white);
|
|
39
|
-
padding: 1.5rem;
|
|
40
|
-
border-bottom: 1px solid var(--kombos-gray-200);
|
|
41
|
-
border-radius: 6px 6px 0 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.kombos-modal__title {
|
|
45
|
-
color: var(--kombos-gray-900);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.kombos-modal__close {
|
|
49
|
-
background: none;
|
|
50
|
-
border: none;
|
|
51
|
-
cursor: pointer;
|
|
52
|
-
padding: 0;
|
|
53
|
-
color: var(--kombos-gray-700);
|
|
54
|
-
font-size: 1.25rem;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.kombos-modal__body {
|
|
58
|
-
padding: 1.5rem;
|
|
59
|
-
overflow-y: auto;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.kombos-modal__footer {
|
|
63
|
-
background: var(--kombos-white);
|
|
64
|
-
padding: 1.5rem;
|
|
65
|
-
border-top: 1px solid var(--kombos-gray-200);
|
|
66
|
-
border-radius: 0 0 6px 6px;
|
|
67
|
-
display: flex;
|
|
68
|
-
gap: 0.75rem;
|
|
69
|
-
justify-content: flex-end;
|
|
70
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
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, so
|
|
12
|
-
* applying these in the stepper keeps the UI honest instead of letting the
|
|
13
|
-
* quantity snap silently.
|
|
14
|
-
*/
|
|
15
|
-
export function getProductCartLimits(product: IkasProduct): CartQuantityLimits {
|
|
16
|
-
const channel = product.salesChannels?.find(
|
|
17
|
-
(sc) => sc.id === IkasStorefrontConfig.salesChannelId,
|
|
18
|
-
);
|
|
19
|
-
return {
|
|
20
|
-
min: channel?.minQuantityPerCart ?? 1,
|
|
21
|
-
max: channel?.maxQuantityPerCart ?? undefined,
|
|
22
|
-
};
|
|
23
|
-
}
|