@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.
- package/cjs/dist/react/SelectedVariant.d.ts +63 -0
- package/cjs/dist/react/SelectedVariant.js +100 -1
- package/cjs/dist/react/index.d.ts +0 -1
- package/cjs/dist/react/index.js +0 -1
- package/cjs/dist/services/products-list-service.js +87 -3
- package/dist/react/SelectedVariant.d.ts +63 -0
- package/dist/react/SelectedVariant.js +100 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -1
- package/dist/services/products-list-service.js +87 -3
- package/package.json +1 -1
- package/cjs/dist/react/ProductActions.d.ts +0 -70
- package/cjs/dist/react/ProductActions.js +0 -104
- package/dist/react/ProductActions.d.ts +0 -70
- package/dist/react/ProductActions.js +0 -104
|
@@ -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()
|
|
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";
|
package/cjs/dist/react/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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()
|
|
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
|
+
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/react/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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,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
|
-
}
|