@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.
Files changed (28) hide show
  1. package/data/framework.json +2 -32
  2. package/data/section-templates/add-to-cart/index.tsx +2 -16
  3. package/data/section-templates/add-to-cart/sub-components/QuantitySelector/index.tsx +3 -11
  4. package/data/section-templates/cart-section/sub-components/CartItem/index.tsx +24 -57
  5. package/data/section-templates/cart-section/sub-components/CartItem/styles.css +0 -14
  6. package/data/section-templates/cart-section/sub-components/QuantitySelector/index.tsx +3 -11
  7. package/data/section-templates/header-section/sub-components/CartItem/index.tsx +24 -57
  8. package/data/section-templates/header-section/sub-components/CartItem/styles.css +0 -14
  9. package/data/section-templates/header-section/sub-components/QuantitySelector/index.tsx +3 -11
  10. package/data/section-templates/image-handling/ikas-config-snippet.json +0 -1
  11. package/data/section-templates/navigation/sub-components/CartItem/index.tsx +24 -57
  12. package/data/section-templates/navigation/sub-components/CartItem/styles.css +0 -14
  13. package/data/section-templates/navigation/sub-components/QuantitySelector/index.tsx +3 -11
  14. package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/index.tsx +2 -16
  15. package/data/section-templates/product-detail-section/ikas-config-snippet.json +0 -1
  16. package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx +3 -11
  17. package/data/storefront-api.json +1 -1
  18. package/data/storefront-types.json +1 -1
  19. package/package.json +1 -1
  20. package/data/section-templates/add-to-cart/utils/cartLimits.ts +0 -23
  21. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/components/BackInStockForm/index.tsx +0 -92
  22. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +0 -159
  23. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx +0 -141
  24. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css +0 -47
  25. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts +0 -25
  26. package/data/section-templates/product-detail-section/sub-components/Modal/index.tsx +0 -120
  27. package/data/section-templates/product-detail-section/sub-components/Modal/styles.css +0 -70
  28. package/data/section-templates/product-detail-section/utils/cartLimits.ts +0 -23
@@ -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;
@@ -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
- }
@@ -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
- }