@wix/headless-stores 0.0.37 → 0.0.38

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.
@@ -161,3 +161,66 @@ export interface SKURenderProps {
161
161
  * ```
162
162
  */
163
163
  export declare function SKU(props: SKUProps): import("react").ReactNode;
164
+ /**
165
+ * Props for Actions headless component
166
+ */
167
+ export interface ActionsProps {
168
+ /** Render prop function that receives actions data */
169
+ children: (props: ActionsRenderProps) => React.ReactNode;
170
+ }
171
+ /**
172
+ * Render props for Actions component
173
+ */
174
+ export interface ActionsRenderProps {
175
+ /** Function to add product to cart */
176
+ onAddToCart: () => Promise<void>;
177
+ /** Function to buy now (clear cart, add product, proceed to checkout) */
178
+ onBuyNow: () => Promise<void>;
179
+ /** Whether add to cart is available */
180
+ canAddToCart: boolean;
181
+ /** Whether add to cart is currently loading */
182
+ isLoading: boolean;
183
+ /** Whether variant is in stock */
184
+ inStock: boolean;
185
+ /** Whether pre-order is enabled */
186
+ isPreOrderEnabled: boolean;
187
+ /** Pre-order message */
188
+ preOrderMessage: string | null;
189
+ /** Error message if any */
190
+ error: string | null;
191
+ }
192
+ /**
193
+ * Headless component for product actions (add to cart, buy now)
194
+ *
195
+ * @component
196
+ * @example
197
+ * ```tsx
198
+ * import { SelectedVariant } from '@wix/stores/components';
199
+ *
200
+ * function AddToCartButton() {
201
+ * return (
202
+ * <SelectedVariant.Actions>
203
+ * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, inStock, error }) => (
204
+ * <div>
205
+ * {error && <div className="error">{error}</div>}
206
+ * {!inStock && <div>Out of stock</div>}
207
+ * <button
208
+ * onClick={onAddToCart}
209
+ * disabled={!canAddToCart || isLoading}
210
+ * >
211
+ * {isLoading ? 'Adding...' : 'Add to Cart'}
212
+ * </button>
213
+ * <button
214
+ * onClick={onBuyNow}
215
+ * disabled={!canAddToCart || isLoading}
216
+ * >
217
+ * Buy Now
218
+ * </button>
219
+ * </div>
220
+ * )}
221
+ * </SelectedVariant.Actions>
222
+ * );
223
+ * }
224
+ * ```
225
+ */
226
+ export declare function Actions(props: ActionsProps): import("react").ReactNode;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useService, WixServices } from "@wix/services-manager-react";
3
3
  import { SelectedVariantServiceDefinition, SelectedVariantService, } from "../services/selected-variant-service.js";
4
+ import { ProductModifiersServiceDefinition } from "../services/product-modifiers-service.js";
5
+ import { CurrentCartService, CurrentCartServiceDefinition, } from "@wix/headless-ecom/services";
4
6
  import { createServicesMap } from "@wix/services-manager";
5
7
  /**
6
8
  * Root component that provides the SelectedVariant service context to its children.
@@ -36,7 +38,9 @@ import { createServicesMap } from "@wix/services-manager";
36
38
  * ```
37
39
  */
38
40
  export function Root(props) {
39
- return (_jsx(WixServices, { servicesMap: createServicesMap().addService(SelectedVariantServiceDefinition, SelectedVariantService, props.selectedVariantServiceConfig), children: props.children }));
41
+ return (_jsx(WixServices, { servicesMap: createServicesMap()
42
+ .addService(SelectedVariantServiceDefinition, SelectedVariantService, props.selectedVariantServiceConfig)
43
+ .addService(CurrentCartServiceDefinition, CurrentCartService, {}), children: props.children }));
40
44
  }
41
45
  /**
42
46
  * Headless component for selected variant details display
@@ -141,3 +145,98 @@ export function SKU(props) {
141
145
  sku,
142
146
  });
143
147
  }
148
+ /**
149
+ * Headless component for product actions (add to cart, buy now)
150
+ *
151
+ * @component
152
+ * @example
153
+ * ```tsx
154
+ * import { SelectedVariant } from '@wix/stores/components';
155
+ *
156
+ * function AddToCartButton() {
157
+ * return (
158
+ * <SelectedVariant.Actions>
159
+ * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, inStock, error }) => (
160
+ * <div>
161
+ * {error && <div className="error">{error}</div>}
162
+ * {!inStock && <div>Out of stock</div>}
163
+ * <button
164
+ * onClick={onAddToCart}
165
+ * disabled={!canAddToCart || isLoading}
166
+ * >
167
+ * {isLoading ? 'Adding...' : 'Add to Cart'}
168
+ * </button>
169
+ * <button
170
+ * onClick={onBuyNow}
171
+ * disabled={!canAddToCart || isLoading}
172
+ * >
173
+ * Buy Now
174
+ * </button>
175
+ * </div>
176
+ * )}
177
+ * </SelectedVariant.Actions>
178
+ * );
179
+ * }
180
+ * ```
181
+ */
182
+ export function Actions(props) {
183
+ const variantService = useService(SelectedVariantServiceDefinition);
184
+ const cartService = useService(CurrentCartServiceDefinition);
185
+ // Try to get modifiers service - it may not exist for all products
186
+ let modifiersService = null;
187
+ try {
188
+ modifiersService = useService(ProductModifiersServiceDefinition);
189
+ }
190
+ catch {
191
+ // Modifiers service not available for this product
192
+ modifiersService = null;
193
+ }
194
+ const inStock = variantService.isInStock.get();
195
+ const isPreOrderEnabled = variantService.isPreOrderEnabled.get();
196
+ const preOrderMessage = variantService.preOrderMessage.get();
197
+ const isLoading = variantService.isLoading.get();
198
+ const error = variantService.error.get();
199
+ const quantity = variantService.selectedQuantity.get();
200
+ // Check if all required modifiers are filled
201
+ const areAllRequiredModifiersFilled = modifiersService
202
+ ? modifiersService.areAllRequiredModifiersFilled()
203
+ : true; // If no modifiers service, assume no required modifiers
204
+ const canAddToCart = (inStock || isPreOrderEnabled) &&
205
+ !isLoading &&
206
+ areAllRequiredModifiersFilled;
207
+ const onAddToCart = async () => {
208
+ // Get modifiers data if available
209
+ let modifiersData;
210
+ if (modifiersService) {
211
+ const selectedModifiers = modifiersService.selectedModifiers.get();
212
+ if (Object.keys(selectedModifiers).length > 0) {
213
+ modifiersData = selectedModifiers;
214
+ }
215
+ }
216
+ await variantService.addToCart(quantity, modifiersData);
217
+ };
218
+ const onBuyNow = async () => {
219
+ try {
220
+ // Clear the cart first
221
+ await cartService.clearCart();
222
+ // Add the product to cart
223
+ await onAddToCart();
224
+ // Proceed to checkout
225
+ await cartService.proceedToCheckout();
226
+ }
227
+ catch (error) {
228
+ console.error("Buy now failed:", error);
229
+ throw error;
230
+ }
231
+ };
232
+ return props.children({
233
+ onAddToCart,
234
+ onBuyNow,
235
+ canAddToCart,
236
+ isLoading,
237
+ inStock,
238
+ isPreOrderEnabled,
239
+ preOrderMessage,
240
+ error,
241
+ });
242
+ }
@@ -1,7 +1,6 @@
1
1
  export * as CategoryList from "./CategoryList.js";
2
2
  export * as Category from "./Category.js";
3
3
  export * as Product from "./Product.js";
4
- export * as ProductActions from "./ProductActions.js";
5
4
  export * as ProductModifiers from "./ProductModifiers.js";
6
5
  export * as ProductList from "./ProductList.js";
7
6
  export * as ProductListFilters from "./ProductListFilters.js";
@@ -1,7 +1,6 @@
1
1
  export * as CategoryList from "./CategoryList.js";
2
2
  export * as Category from "./Category.js";
3
3
  export * as Product from "./Product.js";
4
- export * as ProductActions from "./ProductActions.js";
5
4
  export * as ProductModifiers from "./ProductModifiers.js";
6
5
  export * as ProductList from "./ProductList.js";
7
6
  export * as ProductListFilters from "./ProductListFilters.js";
@@ -1,6 +1,6 @@
1
1
  import { defineService, implementService } from "@wix/services-definitions";
2
2
  import { SignalsServiceDefinition, } from "@wix/services-definitions/core-services/signals";
3
- import { productsV3 } from "@wix/stores";
3
+ import { productsV3, readOnlyVariantsV3 } from "@wix/stores";
4
4
  /**
5
5
  * Loads products list service configuration from the Wix Stores API for SSR initialization.
6
6
  * This function is designed to be used during Server-Side Rendering (SSR) to preload
@@ -83,7 +83,7 @@ import { productsV3 } from "@wix/stores";
83
83
  * ```
84
84
  */
85
85
  export async function loadProductsListServiceConfig(searchOptions) {
86
- const result = await productsV3.searchProducts(searchOptions);
86
+ const result = await fetchProducts(searchOptions);
87
87
  return {
88
88
  products: result.products ?? [],
89
89
  searchOptions,
@@ -91,6 +91,90 @@ export async function loadProductsListServiceConfig(searchOptions) {
91
91
  aggregations: result.aggregationData,
92
92
  };
93
93
  }
94
+ /**
95
+ * Fetches products and their missing variants in one optimized request.
96
+ * This function wraps the standard searchProducts call and automatically
97
+ * fetches missing variant data for all products that need it.
98
+ *
99
+ * @param searchOptions - The search options for querying products
100
+ * @returns Promise that resolves to the search result with complete variant data
101
+ */
102
+ const fetchProducts = async (searchOptions) => {
103
+ const result = await productsV3.searchProducts(searchOptions);
104
+ // Fetch missing variants for all products in one batch request
105
+ if (result.products) {
106
+ result.products = await fetchMissingVariants(result.products);
107
+ }
108
+ return result;
109
+ };
110
+ /**
111
+ * Fetches missing variants for all products in one batch request.
112
+ * This function identifies products that need variant data and fetches
113
+ * all variants efficiently using the readOnlyVariantsV3 API.
114
+ *
115
+ * @param products - Array of products that may need variant data
116
+ * @returns Promise that resolves to products with complete variant information
117
+ */
118
+ const fetchMissingVariants = async (products) => {
119
+ // Find products that need variants (both single and multi-variant products)
120
+ const productsNeedingVariants = products.filter((product) => !product.variantsInfo?.variants &&
121
+ product.variantSummary?.variantCount &&
122
+ product.variantSummary.variantCount > 0);
123
+ if (productsNeedingVariants.length === 0) {
124
+ return products;
125
+ }
126
+ try {
127
+ const productIds = productsNeedingVariants
128
+ .map((p) => p._id)
129
+ .filter(Boolean);
130
+ if (productIds.length === 0) {
131
+ return products;
132
+ }
133
+ const items = [];
134
+ const res = await readOnlyVariantsV3
135
+ .queryVariants({})
136
+ .in("productData.productId", productIds)
137
+ .limit(100)
138
+ .find();
139
+ items.push(...res.items);
140
+ let nextRes = res;
141
+ while (nextRes.hasNext()) {
142
+ nextRes = await nextRes.next();
143
+ items.push(...nextRes.items);
144
+ }
145
+ const variantsByProductId = new Map();
146
+ items.forEach((item) => {
147
+ const productId = item.productData?.productId;
148
+ if (productId) {
149
+ if (!variantsByProductId.has(productId)) {
150
+ variantsByProductId.set(productId, []);
151
+ }
152
+ variantsByProductId.get(productId).push({
153
+ ...item,
154
+ choices: item.optionChoices,
155
+ });
156
+ }
157
+ });
158
+ // Update products with their variants
159
+ return products.map((product) => {
160
+ const variants = variantsByProductId.get(product._id || "");
161
+ if (variants && variants.length > 0) {
162
+ return {
163
+ ...product,
164
+ variantsInfo: {
165
+ ...product.variantsInfo,
166
+ variants,
167
+ },
168
+ };
169
+ }
170
+ return product;
171
+ });
172
+ }
173
+ catch (error) {
174
+ console.error("Failed to fetch missing variants:", error);
175
+ return products;
176
+ }
177
+ };
94
178
  /**
95
179
  * Service definition for the Products List service.
96
180
  * This defines the reactive API contract for managing a list of products with search, pagination, and filtering capabilities.
@@ -179,7 +263,7 @@ export const ProductListService = implementService.withConfig()(ProductsListServ
179
263
  },
180
264
  }
181
265
  : searchOptions;
182
- const result = await productsV3.searchProducts(affectiveSearchOptions);
266
+ const result = await fetchProducts(affectiveSearchOptions);
183
267
  productsSignal.set(result.products ?? []);
184
268
  pagingMetadataSignal.set(result.pagingMetadata);
185
269
  }
@@ -161,3 +161,66 @@ export interface SKURenderProps {
161
161
  * ```
162
162
  */
163
163
  export declare function SKU(props: SKUProps): import("react").ReactNode;
164
+ /**
165
+ * Props for Actions headless component
166
+ */
167
+ export interface ActionsProps {
168
+ /** Render prop function that receives actions data */
169
+ children: (props: ActionsRenderProps) => React.ReactNode;
170
+ }
171
+ /**
172
+ * Render props for Actions component
173
+ */
174
+ export interface ActionsRenderProps {
175
+ /** Function to add product to cart */
176
+ onAddToCart: () => Promise<void>;
177
+ /** Function to buy now (clear cart, add product, proceed to checkout) */
178
+ onBuyNow: () => Promise<void>;
179
+ /** Whether add to cart is available */
180
+ canAddToCart: boolean;
181
+ /** Whether add to cart is currently loading */
182
+ isLoading: boolean;
183
+ /** Whether variant is in stock */
184
+ inStock: boolean;
185
+ /** Whether pre-order is enabled */
186
+ isPreOrderEnabled: boolean;
187
+ /** Pre-order message */
188
+ preOrderMessage: string | null;
189
+ /** Error message if any */
190
+ error: string | null;
191
+ }
192
+ /**
193
+ * Headless component for product actions (add to cart, buy now)
194
+ *
195
+ * @component
196
+ * @example
197
+ * ```tsx
198
+ * import { SelectedVariant } from '@wix/stores/components';
199
+ *
200
+ * function AddToCartButton() {
201
+ * return (
202
+ * <SelectedVariant.Actions>
203
+ * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, inStock, error }) => (
204
+ * <div>
205
+ * {error && <div className="error">{error}</div>}
206
+ * {!inStock && <div>Out of stock</div>}
207
+ * <button
208
+ * onClick={onAddToCart}
209
+ * disabled={!canAddToCart || isLoading}
210
+ * >
211
+ * {isLoading ? 'Adding...' : 'Add to Cart'}
212
+ * </button>
213
+ * <button
214
+ * onClick={onBuyNow}
215
+ * disabled={!canAddToCart || isLoading}
216
+ * >
217
+ * Buy Now
218
+ * </button>
219
+ * </div>
220
+ * )}
221
+ * </SelectedVariant.Actions>
222
+ * );
223
+ * }
224
+ * ```
225
+ */
226
+ export declare function Actions(props: ActionsProps): import("react").ReactNode;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useService, WixServices } from "@wix/services-manager-react";
3
3
  import { SelectedVariantServiceDefinition, SelectedVariantService, } from "../services/selected-variant-service.js";
4
+ import { ProductModifiersServiceDefinition } from "../services/product-modifiers-service.js";
5
+ import { CurrentCartService, CurrentCartServiceDefinition, } from "@wix/headless-ecom/services";
4
6
  import { createServicesMap } from "@wix/services-manager";
5
7
  /**
6
8
  * Root component that provides the SelectedVariant service context to its children.
@@ -36,7 +38,9 @@ import { createServicesMap } from "@wix/services-manager";
36
38
  * ```
37
39
  */
38
40
  export function Root(props) {
39
- return (_jsx(WixServices, { servicesMap: createServicesMap().addService(SelectedVariantServiceDefinition, SelectedVariantService, props.selectedVariantServiceConfig), children: props.children }));
41
+ return (_jsx(WixServices, { servicesMap: createServicesMap()
42
+ .addService(SelectedVariantServiceDefinition, SelectedVariantService, props.selectedVariantServiceConfig)
43
+ .addService(CurrentCartServiceDefinition, CurrentCartService, {}), children: props.children }));
40
44
  }
41
45
  /**
42
46
  * Headless component for selected variant details display
@@ -141,3 +145,98 @@ export function SKU(props) {
141
145
  sku,
142
146
  });
143
147
  }
148
+ /**
149
+ * Headless component for product actions (add to cart, buy now)
150
+ *
151
+ * @component
152
+ * @example
153
+ * ```tsx
154
+ * import { SelectedVariant } from '@wix/stores/components';
155
+ *
156
+ * function AddToCartButton() {
157
+ * return (
158
+ * <SelectedVariant.Actions>
159
+ * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, inStock, error }) => (
160
+ * <div>
161
+ * {error && <div className="error">{error}</div>}
162
+ * {!inStock && <div>Out of stock</div>}
163
+ * <button
164
+ * onClick={onAddToCart}
165
+ * disabled={!canAddToCart || isLoading}
166
+ * >
167
+ * {isLoading ? 'Adding...' : 'Add to Cart'}
168
+ * </button>
169
+ * <button
170
+ * onClick={onBuyNow}
171
+ * disabled={!canAddToCart || isLoading}
172
+ * >
173
+ * Buy Now
174
+ * </button>
175
+ * </div>
176
+ * )}
177
+ * </SelectedVariant.Actions>
178
+ * );
179
+ * }
180
+ * ```
181
+ */
182
+ export function Actions(props) {
183
+ const variantService = useService(SelectedVariantServiceDefinition);
184
+ const cartService = useService(CurrentCartServiceDefinition);
185
+ // Try to get modifiers service - it may not exist for all products
186
+ let modifiersService = null;
187
+ try {
188
+ modifiersService = useService(ProductModifiersServiceDefinition);
189
+ }
190
+ catch {
191
+ // Modifiers service not available for this product
192
+ modifiersService = null;
193
+ }
194
+ const inStock = variantService.isInStock.get();
195
+ const isPreOrderEnabled = variantService.isPreOrderEnabled.get();
196
+ const preOrderMessage = variantService.preOrderMessage.get();
197
+ const isLoading = variantService.isLoading.get();
198
+ const error = variantService.error.get();
199
+ const quantity = variantService.selectedQuantity.get();
200
+ // Check if all required modifiers are filled
201
+ const areAllRequiredModifiersFilled = modifiersService
202
+ ? modifiersService.areAllRequiredModifiersFilled()
203
+ : true; // If no modifiers service, assume no required modifiers
204
+ const canAddToCart = (inStock || isPreOrderEnabled) &&
205
+ !isLoading &&
206
+ areAllRequiredModifiersFilled;
207
+ const onAddToCart = async () => {
208
+ // Get modifiers data if available
209
+ let modifiersData;
210
+ if (modifiersService) {
211
+ const selectedModifiers = modifiersService.selectedModifiers.get();
212
+ if (Object.keys(selectedModifiers).length > 0) {
213
+ modifiersData = selectedModifiers;
214
+ }
215
+ }
216
+ await variantService.addToCart(quantity, modifiersData);
217
+ };
218
+ const onBuyNow = async () => {
219
+ try {
220
+ // Clear the cart first
221
+ await cartService.clearCart();
222
+ // Add the product to cart
223
+ await onAddToCart();
224
+ // Proceed to checkout
225
+ await cartService.proceedToCheckout();
226
+ }
227
+ catch (error) {
228
+ console.error("Buy now failed:", error);
229
+ throw error;
230
+ }
231
+ };
232
+ return props.children({
233
+ onAddToCart,
234
+ onBuyNow,
235
+ canAddToCart,
236
+ isLoading,
237
+ inStock,
238
+ isPreOrderEnabled,
239
+ preOrderMessage,
240
+ error,
241
+ });
242
+ }
@@ -1,7 +1,6 @@
1
1
  export * as CategoryList from "./CategoryList.js";
2
2
  export * as Category from "./Category.js";
3
3
  export * as Product from "./Product.js";
4
- export * as ProductActions from "./ProductActions.js";
5
4
  export * as ProductModifiers from "./ProductModifiers.js";
6
5
  export * as ProductList from "./ProductList.js";
7
6
  export * as ProductListFilters from "./ProductListFilters.js";
@@ -1,7 +1,6 @@
1
1
  export * as CategoryList from "./CategoryList.js";
2
2
  export * as Category from "./Category.js";
3
3
  export * as Product from "./Product.js";
4
- export * as ProductActions from "./ProductActions.js";
5
4
  export * as ProductModifiers from "./ProductModifiers.js";
6
5
  export * as ProductList from "./ProductList.js";
7
6
  export * as ProductListFilters from "./ProductListFilters.js";
@@ -1,6 +1,6 @@
1
1
  import { defineService, implementService } from "@wix/services-definitions";
2
2
  import { SignalsServiceDefinition, } from "@wix/services-definitions/core-services/signals";
3
- import { productsV3 } from "@wix/stores";
3
+ import { productsV3, readOnlyVariantsV3 } from "@wix/stores";
4
4
  /**
5
5
  * Loads products list service configuration from the Wix Stores API for SSR initialization.
6
6
  * This function is designed to be used during Server-Side Rendering (SSR) to preload
@@ -83,7 +83,7 @@ import { productsV3 } from "@wix/stores";
83
83
  * ```
84
84
  */
85
85
  export async function loadProductsListServiceConfig(searchOptions) {
86
- const result = await productsV3.searchProducts(searchOptions);
86
+ const result = await fetchProducts(searchOptions);
87
87
  return {
88
88
  products: result.products ?? [],
89
89
  searchOptions,
@@ -91,6 +91,90 @@ export async function loadProductsListServiceConfig(searchOptions) {
91
91
  aggregations: result.aggregationData,
92
92
  };
93
93
  }
94
+ /**
95
+ * Fetches products and their missing variants in one optimized request.
96
+ * This function wraps the standard searchProducts call and automatically
97
+ * fetches missing variant data for all products that need it.
98
+ *
99
+ * @param searchOptions - The search options for querying products
100
+ * @returns Promise that resolves to the search result with complete variant data
101
+ */
102
+ const fetchProducts = async (searchOptions) => {
103
+ const result = await productsV3.searchProducts(searchOptions);
104
+ // Fetch missing variants for all products in one batch request
105
+ if (result.products) {
106
+ result.products = await fetchMissingVariants(result.products);
107
+ }
108
+ return result;
109
+ };
110
+ /**
111
+ * Fetches missing variants for all products in one batch request.
112
+ * This function identifies products that need variant data and fetches
113
+ * all variants efficiently using the readOnlyVariantsV3 API.
114
+ *
115
+ * @param products - Array of products that may need variant data
116
+ * @returns Promise that resolves to products with complete variant information
117
+ */
118
+ const fetchMissingVariants = async (products) => {
119
+ // Find products that need variants (both single and multi-variant products)
120
+ const productsNeedingVariants = products.filter((product) => !product.variantsInfo?.variants &&
121
+ product.variantSummary?.variantCount &&
122
+ product.variantSummary.variantCount > 0);
123
+ if (productsNeedingVariants.length === 0) {
124
+ return products;
125
+ }
126
+ try {
127
+ const productIds = productsNeedingVariants
128
+ .map((p) => p._id)
129
+ .filter(Boolean);
130
+ if (productIds.length === 0) {
131
+ return products;
132
+ }
133
+ const items = [];
134
+ const res = await readOnlyVariantsV3
135
+ .queryVariants({})
136
+ .in("productData.productId", productIds)
137
+ .limit(100)
138
+ .find();
139
+ items.push(...res.items);
140
+ let nextRes = res;
141
+ while (nextRes.hasNext()) {
142
+ nextRes = await nextRes.next();
143
+ items.push(...nextRes.items);
144
+ }
145
+ const variantsByProductId = new Map();
146
+ items.forEach((item) => {
147
+ const productId = item.productData?.productId;
148
+ if (productId) {
149
+ if (!variantsByProductId.has(productId)) {
150
+ variantsByProductId.set(productId, []);
151
+ }
152
+ variantsByProductId.get(productId).push({
153
+ ...item,
154
+ choices: item.optionChoices,
155
+ });
156
+ }
157
+ });
158
+ // Update products with their variants
159
+ return products.map((product) => {
160
+ const variants = variantsByProductId.get(product._id || "");
161
+ if (variants && variants.length > 0) {
162
+ return {
163
+ ...product,
164
+ variantsInfo: {
165
+ ...product.variantsInfo,
166
+ variants,
167
+ },
168
+ };
169
+ }
170
+ return product;
171
+ });
172
+ }
173
+ catch (error) {
174
+ console.error("Failed to fetch missing variants:", error);
175
+ return products;
176
+ }
177
+ };
94
178
  /**
95
179
  * Service definition for the Products List service.
96
180
  * This defines the reactive API contract for managing a list of products with search, pagination, and filtering capabilities.
@@ -179,7 +263,7 @@ export const ProductListService = implementService.withConfig()(ProductsListServ
179
263
  },
180
264
  }
181
265
  : searchOptions;
182
- const result = await productsV3.searchProducts(affectiveSearchOptions);
266
+ const result = await fetchProducts(affectiveSearchOptions);
183
267
  productsSignal.set(result.products ?? []);
184
268
  pagingMetadataSignal.set(result.pagingMetadata);
185
269
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/headless-stores",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "prebuild": "cd ../media && yarn build && cd ../ecom && yarn build",
@@ -1,70 +0,0 @@
1
- /**
2
- * Props for Actions headless component
3
- */
4
- export interface ActionsProps {
5
- /** Quantity to add (optional) */
6
- quantity?: number;
7
- /** Render prop function that receives actions data */
8
- children: (props: ActionsRenderProps) => React.ReactNode;
9
- }
10
- /**
11
- * Render props for Actions component
12
- */
13
- export interface ActionsRenderProps {
14
- /** Function to add product to cart */
15
- onAddToCart: () => Promise<void>;
16
- /** Function to buy now (clear cart, add product, proceed to checkout) */
17
- onBuyNow: () => Promise<void>;
18
- /** Whether add to cart is available */
19
- canAddToCart: boolean;
20
- /** Whether add to cart is currently loading */
21
- isLoading: boolean;
22
- /** Current variant price */
23
- price: string;
24
- /** Whether variant is in stock */
25
- inStock: boolean;
26
- /** Whether pre-order is enabled */
27
- isPreOrderEnabled: boolean;
28
- /** Pre-order message */
29
- preOrderMessage: string | null;
30
- /** Error message if any */
31
- error: string | null;
32
- /** Available quantity */
33
- availableQuantity: number | null;
34
- }
35
- /**
36
- * Headless component for product actions (add to cart, buy now)
37
- *
38
- * @component
39
- * @example
40
- * ```tsx
41
- * import { ProductActions } from '@wix/stores/components';
42
- *
43
- * function AddToCartButton() {
44
- * return (
45
- * <ProductActions.Actions quantity={1}>
46
- * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, price, inStock, error }) => (
47
- * <div>
48
- * <div>Price: {price}</div>
49
- * {error && <div className="error">{error}</div>}
50
- * {!inStock && <div>Out of stock</div>}
51
- * <button
52
- * onClick={onAddToCart}
53
- * disabled={!canAddToCart || isLoading}
54
- * >
55
- * {isLoading ? 'Adding...' : 'Add to Cart'}
56
- * </button>
57
- * <button
58
- * onClick={onBuyNow}
59
- * disabled={!canAddToCart || isLoading}
60
- * >
61
- * Buy Now
62
- * </button>
63
- * </div>
64
- * )}
65
- * </ProductActions.Actions>
66
- * );
67
- * }
68
- * ```
69
- */
70
- export declare function Actions(props: ActionsProps): import("react").ReactNode;
@@ -1,104 +0,0 @@
1
- import { useService } from "@wix/services-manager-react";
2
- import { SelectedVariantServiceDefinition } from "../services/selected-variant-service.js";
3
- import { ProductModifiersServiceDefinition } from "../services/product-modifiers-service.js";
4
- import { CurrentCartServiceDefinition } from "@wix/headless-ecom/services";
5
- /**
6
- * Headless component for product actions (add to cart, buy now)
7
- *
8
- * @component
9
- * @example
10
- * ```tsx
11
- * import { ProductActions } from '@wix/stores/components';
12
- *
13
- * function AddToCartButton() {
14
- * return (
15
- * <ProductActions.Actions quantity={1}>
16
- * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, price, inStock, error }) => (
17
- * <div>
18
- * <div>Price: {price}</div>
19
- * {error && <div className="error">{error}</div>}
20
- * {!inStock && <div>Out of stock</div>}
21
- * <button
22
- * onClick={onAddToCart}
23
- * disabled={!canAddToCart || isLoading}
24
- * >
25
- * {isLoading ? 'Adding...' : 'Add to Cart'}
26
- * </button>
27
- * <button
28
- * onClick={onBuyNow}
29
- * disabled={!canAddToCart || isLoading}
30
- * >
31
- * Buy Now
32
- * </button>
33
- * </div>
34
- * )}
35
- * </ProductActions.Actions>
36
- * );
37
- * }
38
- * ```
39
- */
40
- export function Actions(props) {
41
- const variantService = useService(SelectedVariantServiceDefinition);
42
- const cartService = useService(CurrentCartServiceDefinition);
43
- // Try to get modifiers service - it may not exist for all products
44
- let modifiersService = null;
45
- try {
46
- modifiersService = useService(ProductModifiersServiceDefinition);
47
- }
48
- catch {
49
- // Modifiers service not available for this product
50
- modifiersService = null;
51
- }
52
- const price = variantService.currentPrice.get();
53
- const inStock = variantService.isInStock.get();
54
- const isPreOrderEnabled = variantService.isPreOrderEnabled.get();
55
- const preOrderMessage = variantService.preOrderMessage.get();
56
- const isLoading = variantService.isLoading.get();
57
- const error = variantService.error.get();
58
- const availableQuantity = variantService.quantityAvailable.get();
59
- const quantity = variantService.selectedQuantity.get();
60
- // Check if all required modifiers are filled
61
- const areAllRequiredModifiersFilled = modifiersService
62
- ? modifiersService.areAllRequiredModifiersFilled()
63
- : true; // If no modifiers service, assume no required modifiers
64
- const canAddToCart = (inStock || isPreOrderEnabled) &&
65
- !isLoading &&
66
- areAllRequiredModifiersFilled;
67
- const onAddToCart = async () => {
68
- // Get modifiers data if available
69
- let modifiersData;
70
- if (modifiersService) {
71
- const selectedModifiers = modifiersService.selectedModifiers.get();
72
- if (Object.keys(selectedModifiers).length > 0) {
73
- modifiersData = selectedModifiers;
74
- }
75
- }
76
- await variantService.addToCart(quantity, modifiersData);
77
- };
78
- const onBuyNow = async () => {
79
- try {
80
- // Clear the cart first
81
- await cartService.clearCart();
82
- // Add the product to cart
83
- await onAddToCart();
84
- // Proceed to checkout
85
- await cartService.proceedToCheckout();
86
- }
87
- catch (error) {
88
- console.error("Buy now failed:", error);
89
- throw error;
90
- }
91
- };
92
- return props.children({
93
- onAddToCart,
94
- onBuyNow,
95
- canAddToCart,
96
- isLoading,
97
- price,
98
- inStock,
99
- isPreOrderEnabled,
100
- preOrderMessage,
101
- error,
102
- availableQuantity,
103
- });
104
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * Props for Actions headless component
3
- */
4
- export interface ActionsProps {
5
- /** Quantity to add (optional) */
6
- quantity?: number;
7
- /** Render prop function that receives actions data */
8
- children: (props: ActionsRenderProps) => React.ReactNode;
9
- }
10
- /**
11
- * Render props for Actions component
12
- */
13
- export interface ActionsRenderProps {
14
- /** Function to add product to cart */
15
- onAddToCart: () => Promise<void>;
16
- /** Function to buy now (clear cart, add product, proceed to checkout) */
17
- onBuyNow: () => Promise<void>;
18
- /** Whether add to cart is available */
19
- canAddToCart: boolean;
20
- /** Whether add to cart is currently loading */
21
- isLoading: boolean;
22
- /** Current variant price */
23
- price: string;
24
- /** Whether variant is in stock */
25
- inStock: boolean;
26
- /** Whether pre-order is enabled */
27
- isPreOrderEnabled: boolean;
28
- /** Pre-order message */
29
- preOrderMessage: string | null;
30
- /** Error message if any */
31
- error: string | null;
32
- /** Available quantity */
33
- availableQuantity: number | null;
34
- }
35
- /**
36
- * Headless component for product actions (add to cart, buy now)
37
- *
38
- * @component
39
- * @example
40
- * ```tsx
41
- * import { ProductActions } from '@wix/stores/components';
42
- *
43
- * function AddToCartButton() {
44
- * return (
45
- * <ProductActions.Actions quantity={1}>
46
- * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, price, inStock, error }) => (
47
- * <div>
48
- * <div>Price: {price}</div>
49
- * {error && <div className="error">{error}</div>}
50
- * {!inStock && <div>Out of stock</div>}
51
- * <button
52
- * onClick={onAddToCart}
53
- * disabled={!canAddToCart || isLoading}
54
- * >
55
- * {isLoading ? 'Adding...' : 'Add to Cart'}
56
- * </button>
57
- * <button
58
- * onClick={onBuyNow}
59
- * disabled={!canAddToCart || isLoading}
60
- * >
61
- * Buy Now
62
- * </button>
63
- * </div>
64
- * )}
65
- * </ProductActions.Actions>
66
- * );
67
- * }
68
- * ```
69
- */
70
- export declare function Actions(props: ActionsProps): import("react").ReactNode;
@@ -1,104 +0,0 @@
1
- import { useService } from "@wix/services-manager-react";
2
- import { SelectedVariantServiceDefinition } from "../services/selected-variant-service.js";
3
- import { ProductModifiersServiceDefinition } from "../services/product-modifiers-service.js";
4
- import { CurrentCartServiceDefinition } from "@wix/headless-ecom/services";
5
- /**
6
- * Headless component for product actions (add to cart, buy now)
7
- *
8
- * @component
9
- * @example
10
- * ```tsx
11
- * import { ProductActions } from '@wix/stores/components';
12
- *
13
- * function AddToCartButton() {
14
- * return (
15
- * <ProductActions.Actions quantity={1}>
16
- * {({ onAddToCart, onBuyNow, canAddToCart, isLoading, price, inStock, error }) => (
17
- * <div>
18
- * <div>Price: {price}</div>
19
- * {error && <div className="error">{error}</div>}
20
- * {!inStock && <div>Out of stock</div>}
21
- * <button
22
- * onClick={onAddToCart}
23
- * disabled={!canAddToCart || isLoading}
24
- * >
25
- * {isLoading ? 'Adding...' : 'Add to Cart'}
26
- * </button>
27
- * <button
28
- * onClick={onBuyNow}
29
- * disabled={!canAddToCart || isLoading}
30
- * >
31
- * Buy Now
32
- * </button>
33
- * </div>
34
- * )}
35
- * </ProductActions.Actions>
36
- * );
37
- * }
38
- * ```
39
- */
40
- export function Actions(props) {
41
- const variantService = useService(SelectedVariantServiceDefinition);
42
- const cartService = useService(CurrentCartServiceDefinition);
43
- // Try to get modifiers service - it may not exist for all products
44
- let modifiersService = null;
45
- try {
46
- modifiersService = useService(ProductModifiersServiceDefinition);
47
- }
48
- catch {
49
- // Modifiers service not available for this product
50
- modifiersService = null;
51
- }
52
- const price = variantService.currentPrice.get();
53
- const inStock = variantService.isInStock.get();
54
- const isPreOrderEnabled = variantService.isPreOrderEnabled.get();
55
- const preOrderMessage = variantService.preOrderMessage.get();
56
- const isLoading = variantService.isLoading.get();
57
- const error = variantService.error.get();
58
- const availableQuantity = variantService.quantityAvailable.get();
59
- const quantity = variantService.selectedQuantity.get();
60
- // Check if all required modifiers are filled
61
- const areAllRequiredModifiersFilled = modifiersService
62
- ? modifiersService.areAllRequiredModifiersFilled()
63
- : true; // If no modifiers service, assume no required modifiers
64
- const canAddToCart = (inStock || isPreOrderEnabled) &&
65
- !isLoading &&
66
- areAllRequiredModifiersFilled;
67
- const onAddToCart = async () => {
68
- // Get modifiers data if available
69
- let modifiersData;
70
- if (modifiersService) {
71
- const selectedModifiers = modifiersService.selectedModifiers.get();
72
- if (Object.keys(selectedModifiers).length > 0) {
73
- modifiersData = selectedModifiers;
74
- }
75
- }
76
- await variantService.addToCart(quantity, modifiersData);
77
- };
78
- const onBuyNow = async () => {
79
- try {
80
- // Clear the cart first
81
- await cartService.clearCart();
82
- // Add the product to cart
83
- await onAddToCart();
84
- // Proceed to checkout
85
- await cartService.proceedToCheckout();
86
- }
87
- catch (error) {
88
- console.error("Buy now failed:", error);
89
- throw error;
90
- }
91
- };
92
- return props.children({
93
- onAddToCart,
94
- onBuyNow,
95
- canAddToCart,
96
- isLoading,
97
- price,
98
- inStock,
99
- isPreOrderEnabled,
100
- preOrderMessage,
101
- error,
102
- availableQuantity,
103
- });
104
- }