@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.
Files changed (94) hide show
  1. package/data/framework.json +208 -78
  2. package/data/migration.json +196 -25
  3. package/data/section-templates/account-info-section/children/AccountFavorites/ikas-config-snippet.json +16 -3
  4. package/data/section-templates/account-info-section/children/AccountOrders/index.tsx +1 -1
  5. package/data/section-templates/account-info-section/ikas-config-snippet.json +24 -5
  6. package/data/section-templates/add-to-cart/ikas-config-snippet.json +17 -0
  7. package/data/section-templates/add-to-cart/index.tsx +16 -2
  8. package/data/section-templates/add-to-cart/sub-components/QuantitySelector/index.tsx +11 -3
  9. package/data/section-templates/add-to-cart/utils/cartLimits.ts +24 -0
  10. package/data/section-templates/blog-home-section/ikas-config-snippet.json +2 -0
  11. package/data/section-templates/blog-home-section/index.tsx +2 -1
  12. package/data/section-templates/blog-post-section/ikas-config-snippet.json +2 -0
  13. package/data/section-templates/blog-post-section/index.tsx +3 -2
  14. package/data/section-templates/bundle-products/hooks/useBundleProducts.ts +7 -6
  15. package/data/section-templates/bundle-products/ikas-config-snippet.json +20 -0
  16. package/data/section-templates/cart-section/components/EmptyState/index.tsx +1 -1
  17. package/data/section-templates/cart-section/sub-components/CartItem/index.tsx +66 -24
  18. package/data/section-templates/cart-section/sub-components/CartItem/styles.css +14 -0
  19. package/data/section-templates/cart-section/sub-components/QuantitySelector/index.tsx +11 -3
  20. package/data/section-templates/cart-section/utils/cartLimits.ts +24 -0
  21. package/data/section-templates/cart-section/utils/cartLineLimits.ts +100 -0
  22. package/data/section-templates/category-images-section/children/CategoryImageItem/ikas-config-snippet.json +4 -0
  23. package/data/section-templates/category-images-section/ikas-config-snippet.json +16 -1
  24. package/data/section-templates/category-list-section/children/CardProductName/ikas-config-snippet.json +2 -1
  25. package/data/section-templates/category-list-section/ikas-config-snippet.json +24 -3
  26. package/data/section-templates/category-list-section/index.tsx +2 -1
  27. package/data/section-templates/component-renderer/additional/ProductDetail/index.tsx +5 -1
  28. package/data/section-templates/component-renderer/ikas-config-snippet.json +16 -3
  29. package/data/section-templates/favorites/ikas-config-snippet.json +18 -1
  30. package/data/section-templates/features-section/ikas-config-snippet.json +14 -1
  31. package/data/section-templates/footer-section/ikas-config-snippet.json +16 -1
  32. package/data/section-templates/forgot-password-section/components/ForgotPasswordForm/index.tsx +1 -1
  33. package/data/section-templates/header-section/children/Announcements/ikas-config-snippet.json +8 -1
  34. package/data/section-templates/header-section/children/CookieBar/ikas-config-snippet.json +3 -0
  35. package/data/section-templates/header-section/children/Navbar/components/CartSidebar/index.tsx +2 -1
  36. package/data/section-templates/header-section/children/Navbar/components/SearchModal/index.tsx +2 -1
  37. package/data/section-templates/header-section/children/Navbar/ikas-config-snippet.json +30 -3
  38. package/data/section-templates/header-section/children/Navbar/index.tsx +6 -5
  39. package/data/section-templates/header-section/ikas-config-snippet.json +16 -3
  40. package/data/section-templates/header-section/sub-components/CartItem/index.tsx +66 -24
  41. package/data/section-templates/header-section/sub-components/CartItem/styles.css +14 -0
  42. package/data/section-templates/header-section/sub-components/QuantitySelector/index.tsx +11 -3
  43. package/data/section-templates/header-section/utils/cartLimits.ts +24 -0
  44. package/data/section-templates/header-section/utils/cartLineLimits.ts +100 -0
  45. package/data/section-templates/hero-slider-section/children/HeroSliderItem/ikas-config-snippet.json +18 -0
  46. package/data/section-templates/hero-slider-section/children/HeroSliderItem/index.tsx +12 -7
  47. package/data/section-templates/hero-slider-section/ikas-config-snippet.json +14 -1
  48. package/data/section-templates/image-handling/ikas-config-snippet.json +66 -13
  49. package/data/section-templates/image-handling/index.tsx +5 -1
  50. package/data/section-templates/login-section/ikas-config-snippet.json +2 -0
  51. package/data/section-templates/navigation/components/CartSidebar/index.tsx +2 -1
  52. package/data/section-templates/navigation/components/SearchModal/index.tsx +2 -1
  53. package/data/section-templates/navigation/ikas-config-snippet.json +30 -3
  54. package/data/section-templates/navigation/index.tsx +6 -5
  55. package/data/section-templates/navigation/sub-components/CartItem/index.tsx +66 -24
  56. package/data/section-templates/navigation/sub-components/CartItem/styles.css +14 -0
  57. package/data/section-templates/navigation/sub-components/QuantitySelector/index.tsx +11 -3
  58. package/data/section-templates/navigation/utils/cartLimits.ts +24 -0
  59. package/data/section-templates/navigation/utils/cartLineLimits.ts +100 -0
  60. package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/ikas-config-snippet.json +17 -0
  61. package/data/section-templates/product-detail-section/children/ProductDetailAddToCart/index.tsx +16 -2
  62. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/components/BackInStockForm/index.tsx +92 -0
  63. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/ikas-config-snippet.json +175 -0
  64. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/index.tsx +141 -0
  65. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/styles.css +47 -0
  66. package/data/section-templates/product-detail-section/children/ProductDetailBackInStock/types.ts +25 -0
  67. package/data/section-templates/product-detail-section/children/ProductDetailBundleFurniture/ikas-config-snippet.json +18 -0
  68. package/data/section-templates/product-detail-section/children/ProductDetailBundleProduct/ikas-config-snippet.json +20 -0
  69. package/data/section-templates/product-detail-section/children/ProductDetailDescription/ikas-config-snippet.json +28 -1
  70. package/data/section-templates/product-detail-section/children/ProductDetailFeatures/ikas-config-snippet.json +30 -1
  71. package/data/section-templates/product-detail-section/children/ProductDetailNameFavorite/ikas-config-snippet.json +18 -1
  72. package/data/section-templates/product-detail-section/children/ProductDetailOffer/ikas-config-snippet.json +20 -0
  73. package/data/section-templates/product-detail-section/children/ProductDetailOptionSet/ikas-config-snippet.json +16 -0
  74. package/data/section-templates/product-detail-section/children/ProductDetailPrices/ikas-config-snippet.json +16 -0
  75. package/data/section-templates/product-detail-section/children/ProductDetailProductGroup/ikas-config-snippet.json +16 -0
  76. package/data/section-templates/product-detail-section/children/ProductDetailSku/ikas-config-snippet.json +16 -0
  77. package/data/section-templates/product-detail-section/children/ProductDetailVariant/ikas-config-snippet.json +17 -0
  78. package/data/section-templates/product-detail-section/hooks/useBundleProducts.ts +7 -6
  79. package/data/section-templates/product-detail-section/ikas-config-snippet.json +66 -13
  80. package/data/section-templates/product-detail-section/index.tsx +5 -1
  81. package/data/section-templates/product-detail-section/sub-components/Modal/index.tsx +120 -0
  82. package/data/section-templates/product-detail-section/sub-components/Modal/styles.css +70 -0
  83. package/data/section-templates/product-detail-section/sub-components/QuantitySelector/index.tsx +11 -3
  84. package/data/section-templates/product-detail-section/utils/cartLimits.ts +24 -0
  85. package/data/section-templates/product-pricing/ikas-config-snippet.json +16 -0
  86. package/data/section-templates/product-slider-section/children/CardProductName/ikas-config-snippet.json +2 -1
  87. package/data/section-templates/product-slider-section/ikas-config-snippet.json +20 -3
  88. package/data/section-templates/register-section/ikas-config-snippet.json +2 -0
  89. package/data/section-templates/variant-selection/ikas-config-snippet.json +17 -0
  90. package/data/storefront-api.json +819 -1443
  91. package/data/storefront-types.json +9644 -6132
  92. package/dist/index.js +2978 -347
  93. package/dist/index.js.map +1 -1
  94. package/package.json +1 -1
@@ -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
  {
@@ -10,13 +10,14 @@ import {
10
10
  IkasBundleSettings,
11
11
  IkasStorefrontConfig,
12
12
  } from "@ikas/bp-storefront";
13
- import { useState } from "preact/hooks";
13
+ import { useEffect, useState } from "preact/hooks";
14
14
  import { Props } from "./types";
15
15
  import Button from "../../sub-components/Button";
16
16
  import QuantitySelector from "../../sub-components/QuantitySelector";
17
17
  import PayWithIkas from "./components/PayWithIkas";
18
18
  import { isBundleOutOfStock } from "../../utils/bundle";
19
19
  import { validateOptionSet } from "../../utils/optionSet";
20
+ import { getProductCartLimits } from "../../utils/cartLimits";
20
21
  import { showToast } from "../../utils/toast";
21
22
 
22
23
  export function ProductDetailAddToCart({
@@ -37,6 +38,12 @@ export function ProductDetailAddToCart({
37
38
  const [quantity, setQuantity] = useState(1);
38
39
  const [isAddingToCart, setIsAddingToCart] = useState(false);
39
40
 
41
+ // Seed the quantity to the product's minimum-per-cart when the product changes.
42
+ useEffect(() => {
43
+ if (!product) return;
44
+ setQuantity(getProductCartLimits(product).min);
45
+ }, [product?.id]);
46
+
40
47
  if (!product) return null;
41
48
 
42
49
  const editLineID =
@@ -59,6 +66,8 @@ export function ProductDetailAddToCart({
59
66
 
60
67
  const isDisabled = isOutOfStock || isAddingToCart;
61
68
 
69
+ const { min: minQuantity, max: maxQuantity } = getProductCartLimits(product);
70
+
62
71
  const payWithIkasUrl = IkasStorefrontConfig.getPayWithIkasUrl();
63
72
  const routing = IkasStorefrontConfig.getCurrentRouting();
64
73
  const showPayWithIkas =
@@ -119,7 +128,12 @@ export function ProductDetailAddToCart({
119
128
  >
120
129
  <div className="kombos-pd-atc__actions">
121
130
  {!hideQuantityInput && !isOutOfStock && (
122
- <QuantitySelector value={quantity} onChange={setQuantity} />
131
+ <QuantitySelector
132
+ value={quantity}
133
+ onChange={setQuantity}
134
+ min={minQuantity}
135
+ max={maxQuantity}
136
+ />
123
137
  )}
124
138
  <Button
125
139
  variant="primary"
@@ -0,0 +1,92 @@
1
+ import {
2
+ IkasProductVariant,
3
+ getProductVariantBackInStockForm,
4
+ initBackInStockNotificationForm,
5
+ setBackInStockNotificationFormEmail,
6
+ submitBackInStockNotificationForm,
7
+ } from "@ikas/bp-storefront";
8
+ import { observer } from "@ikas/component-utils";
9
+ import { useEffect } from "preact/hooks";
10
+ import Button from "../../../../sub-components/Button";
11
+ import Input from "../../../../sub-components/Input";
12
+ import FormItem from "../../../../sub-components/FormItem";
13
+
14
+ interface Props {
15
+ variant: IkasProductVariant;
16
+ description: string;
17
+ emailLabel: string;
18
+ submitButtonText: string;
19
+ submittingText: string;
20
+ errorMessage: string;
21
+ onSaved: () => void;
22
+ }
23
+
24
+ // observer() because it reads the MobX-backed notification form and must
25
+ // re-render as the customer types and as the submit status flags change.
26
+ const BackInStockForm = observer(function BackInStockForm({
27
+ variant,
28
+ description,
29
+ emailLabel,
30
+ submitButtonText,
31
+ submittingText,
32
+ errorMessage,
33
+ onSaved,
34
+ }: Props) {
35
+ const form = getProductVariantBackInStockForm(variant);
36
+
37
+ // Initialize once per variant (resets fields + validation flags).
38
+ useEffect(() => {
39
+ initBackInStockNotificationForm(form, variant);
40
+ }, [form, variant]);
41
+
42
+ const emailHasError = !!form.email?.hasError;
43
+
44
+ const handleSubmit = async (e: Event) => {
45
+ e.preventDefault();
46
+ if (await submitBackInStockNotificationForm(form)) onSaved();
47
+ };
48
+
49
+ const handleEmailInput = (e: Event) =>
50
+ setBackInStockNotificationFormEmail(
51
+ form,
52
+ (e.target as HTMLInputElement).value,
53
+ );
54
+
55
+ return (
56
+ <form className="kombos-pd-bis__form" onSubmit={handleSubmit}>
57
+ {description && <p className="kombos-pd-bis__desc">{description}</p>}
58
+ <FormItem
59
+ label={emailLabel}
60
+ htmlFor="back-in-stock-email"
61
+ status={emailHasError ? "error" : "default"}
62
+ helper={emailHasError ? form.email?.message : undefined}
63
+ >
64
+ <Input
65
+ id="back-in-stock-email"
66
+ type="email"
67
+ value={form.email?.value ?? ""}
68
+ placeholder={form.email?.placeholder}
69
+ status={emailHasError ? "error" : "default"}
70
+ onInput={handleEmailInput}
71
+ />
72
+ </FormItem>
73
+ {form.isFailure && (
74
+ <p className="kombos-pd-bis__error">
75
+ {form.responseMessage || errorMessage}
76
+ </p>
77
+ )}
78
+ <Button
79
+ variant="primary"
80
+ size="s"
81
+ type="submit"
82
+ className="kombos-pd-bis__submit"
83
+ loading={form.isSubmitting}
84
+ disabled={form.isSubmitting}
85
+ >
86
+ {form.isSubmitting ? submittingText : submitButtonText}
87
+ </Button>
88
+ </form>
89
+ );
90
+ });
91
+
92
+ export default BackInStockForm;
@@ -0,0 +1,175 @@
1
+ {
2
+ "id": "{{PROJECT_ID}}-product-detail-back-in-stock",
3
+ "name": "ProductDetailBackInStock",
4
+ "type": "component",
5
+ "entry": "./src/components/ProductDetailBackInStock/index.tsx",
6
+ "styles": "./src/components/ProductDetailBackInStock/styles.css",
7
+ "props": [
8
+ {
9
+ "name": "product",
10
+ "displayName": "Product",
11
+ "type": "PRODUCT",
12
+ "required": false
13
+ },
14
+ {
15
+ "name": "notifyButtonText",
16
+ "displayName": "Notify Button Text",
17
+ "type": "TEXT",
18
+ "required": false,
19
+ "defaultValue": "Notify Me When Available",
20
+ "groupId": "texts"
21
+ },
22
+ {
23
+ "name": "reminderSavedText",
24
+ "displayName": "Reminder Saved Text",
25
+ "type": "TEXT",
26
+ "required": false,
27
+ "defaultValue": "We'll notify you",
28
+ "groupId": "texts"
29
+ },
30
+ {
31
+ "name": "alreadySavedMessage",
32
+ "displayName": "Already Saved Message",
33
+ "type": "TEXT",
34
+ "required": false,
35
+ "defaultValue": "You're already on the list — we'll email you when it's back.",
36
+ "groupId": "texts"
37
+ },
38
+ {
39
+ "name": "savedMessage",
40
+ "displayName": "Saved Success Message",
41
+ "type": "TEXT",
42
+ "required": false,
43
+ "defaultValue": "Great! We'll email you when this product is back in stock.",
44
+ "groupId": "texts"
45
+ },
46
+ {
47
+ "name": "loginRequiredMessage",
48
+ "displayName": "Login Required Message",
49
+ "type": "TEXT",
50
+ "required": false,
51
+ "defaultValue": "Please sign in so we can notify you.",
52
+ "groupId": "texts"
53
+ },
54
+ {
55
+ "name": "errorMessage",
56
+ "displayName": "Error Message",
57
+ "type": "TEXT",
58
+ "required": false,
59
+ "defaultValue": "Something went wrong. Please try again.",
60
+ "groupId": "texts"
61
+ },
62
+ {
63
+ "name": "modalTitle",
64
+ "displayName": "Email Modal Title",
65
+ "type": "TEXT",
66
+ "required": false,
67
+ "defaultValue": "Notify me when available",
68
+ "groupId": "texts"
69
+ },
70
+ {
71
+ "name": "modalDescription",
72
+ "displayName": "Email Modal Description",
73
+ "type": "TEXT",
74
+ "required": false,
75
+ "defaultValue": "Enter your email and we'll let you know the moment this product is back in stock.",
76
+ "groupId": "texts"
77
+ },
78
+ {
79
+ "name": "emailLabel",
80
+ "displayName": "Email Field Label",
81
+ "type": "TEXT",
82
+ "required": false,
83
+ "defaultValue": "Email",
84
+ "groupId": "texts"
85
+ },
86
+ {
87
+ "name": "submitButtonText",
88
+ "displayName": "Submit Button Text",
89
+ "type": "TEXT",
90
+ "required": false,
91
+ "defaultValue": "Notify Me",
92
+ "groupId": "texts"
93
+ },
94
+ {
95
+ "name": "submittingText",
96
+ "displayName": "Submitting Text",
97
+ "type": "TEXT",
98
+ "required": false,
99
+ "defaultValue": "Saving...",
100
+ "groupId": "texts"
101
+ },
102
+ {
103
+ "name": "mobileMarginTop",
104
+ "displayName": "Top Spacing",
105
+ "type": "TYPE",
106
+ "required": false,
107
+ "defaultValue": {
108
+ "value": 0,
109
+ "unit": "px"
110
+ },
111
+ "groupId": "mobile",
112
+ "typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
113
+ },
114
+ {
115
+ "name": "mobileMarginBottom",
116
+ "displayName": "Bottom Spacing",
117
+ "type": "TYPE",
118
+ "required": false,
119
+ "defaultValue": {
120
+ "value": 0,
121
+ "unit": "px"
122
+ },
123
+ "groupId": "mobile",
124
+ "typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
125
+ },
126
+ {
127
+ "name": "desktopMarginTop",
128
+ "displayName": "Top Spacing",
129
+ "type": "TYPE",
130
+ "required": false,
131
+ "defaultValue": {
132
+ "value": 0,
133
+ "unit": "px"
134
+ },
135
+ "groupId": "desktop",
136
+ "typeId": "@ikas/bp-storefront-models-MarginTopStyleType"
137
+ },
138
+ {
139
+ "name": "desktopMarginBottom",
140
+ "displayName": "Bottom Spacing",
141
+ "type": "TYPE",
142
+ "required": false,
143
+ "defaultValue": {
144
+ "value": 0,
145
+ "unit": "px"
146
+ },
147
+ "groupId": "desktop",
148
+ "typeId": "@ikas/bp-storefront-models-MarginBottomStyleType"
149
+ }
150
+ ],
151
+ "propGroups": [
152
+ {
153
+ "id": "appearance",
154
+ "name": "Appearance Settings",
155
+ "children": [
156
+ {
157
+ "id": "mobile",
158
+ "name": "Mobile View",
159
+ "description": "Margin settings for mobile devices"
160
+ },
161
+ {
162
+ "id": "desktop",
163
+ "name": "Desktop View",
164
+ "description": "Margin settings for desktop devices"
165
+ }
166
+ ],
167
+ "description": "Component mobile and desktop view settings"
168
+ },
169
+ {
170
+ "id": "texts",
171
+ "name": "Texts",
172
+ "description": "Button, status and modal texts"
173
+ }
174
+ ]
175
+ }
@@ -0,0 +1,141 @@
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;
@@ -0,0 +1,47 @@
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
+ }
@@ -0,0 +1,25 @@
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
+ }
@@ -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
  }