@wix/agent-skills 1.19.1 → 1.20.0
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/package.json +1 -1
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +1 -1
- package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +7 -2
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +53 -28
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +17 -4
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +71 -31
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +3 -4
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
- package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
- package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
- package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
- package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
- package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
- package/skills/wix-base44-connector/SKILL.md +46 -24
- package/skills/wix-headless/SKILL.md +1 -0
- package/skills/wix-headless/references/CAPABILITIES.md +7 -2
- package/skills/wix-headless/references/SDK_HANDOFF.md +2 -1
- package/skills/wix-headless/references/inline-recipes/experience-store.md +49 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +431 -115
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +1 -1
- package/skills/wix-headless/references/managed/CREATE.md +5 -0
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
- package/skills/wix-headless-fast/install/fast-path.mjs +3 -1
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +13 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +227 -56
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +165 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +48 -15
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +129 -11
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +36 -6
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +190 -24
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +5 -1
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +59 -6
- package/skills/wix-manage/SKILL.md +8 -5
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +4 -1
- package/skills/wix-manage/references/bookings/create-appointment-service.md +3 -1
- package/skills/wix-manage/references/bookings/create-class-service.md +3 -1
- package/skills/wix-manage/references/bookings/create-course-service.md +4 -2
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +68 -3
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md +1 -1
- package/skills/wix-manage/references/events/create-wix-event.md +46 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
- package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +108 -14
- package/skills/wix-manage/references/seo/manage-content-plan.md +1 -1
- package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
- package/skills/wix-manage/references/sites/upload-static-site.md +264 -34
- package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44.md +19 -4
- package/skills/wix-vibe-headless/platforms/generic.md +6 -0
- package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
- package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
- package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +110 -27
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +32 -14
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +14 -3
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +47 -35
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +77 -30
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +31 -2
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +31 -6
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// useProductDetail — all PDP logic, no markup: load product by slug,
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// renders what this returns.
|
|
1
|
+
// useProductDetail — all PDP logic, no markup: load product by slug, resolve the buyer's selections
|
|
2
|
+
// to a Wix variant, gate add-to-cart on availability + mandatory modifiers, and build the addToCart
|
|
3
|
+
// payload. The data paths here (variant resolution, price precedence, modifier keys, stock gating)
|
|
4
|
+
// are the bug-prone part — keep them verbatim; the PDP page only renders what this returns.
|
|
6
5
|
import { useState, useEffect, useMemo } from "react";
|
|
7
|
-
import { getProductBySlug } from "@/rest/wix-store-catalog";
|
|
6
|
+
import { getProductBySlug, sellingPrice } from "@/rest/wix-store-catalog";
|
|
8
7
|
import { choiceImage, variantImage } from "@/lib/storeImage";
|
|
9
8
|
import { useCart } from "@/context/CartContext";
|
|
10
9
|
|
|
10
|
+
// A variant is identified by the SET of its choice ids — sorted, so option order can't matter.
|
|
11
|
+
const choiceKey = (choiceIds) => [...choiceIds].sort().join("_");
|
|
12
|
+
|
|
11
13
|
export function useProductDetail(slug) {
|
|
12
14
|
const { addToCart } = useCart();
|
|
13
15
|
const [product, setProduct] = useState(null);
|
|
@@ -16,6 +18,8 @@ export function useProductDetail(slug) {
|
|
|
16
18
|
// link, the other is worth retrying. Without the split, a network blip reads as "product deleted".
|
|
17
19
|
const [error, setError] = useState(null);
|
|
18
20
|
const [adding, setAdding] = useState(false);
|
|
21
|
+
// Selections start EMPTY. Pre-picking the first choice lets a buyer who never noticed the default
|
|
22
|
+
// buy the wrong colour; the PDP shows a neutral "choose a size" until every option is picked.
|
|
19
23
|
const [selectedOptions, setSelectedOptions] = useState({});
|
|
20
24
|
const [modifierValues, setModifierValues] = useState({});
|
|
21
25
|
const [quantity, setQuantity] = useState(1);
|
|
@@ -26,17 +30,13 @@ export function useProductDetail(slug) {
|
|
|
26
30
|
setProduct(null);
|
|
27
31
|
setNotFound(false);
|
|
28
32
|
setError(null);
|
|
33
|
+
setSelectedOptions({});
|
|
34
|
+
setQuantity(1);
|
|
29
35
|
getProductBySlug(slug)
|
|
30
36
|
.then((p) => {
|
|
31
37
|
if (cancelled) return;
|
|
32
38
|
if (!p) return setNotFound(true);
|
|
33
39
|
setProduct(p);
|
|
34
|
-
const initial = {};
|
|
35
|
-
(p.options || []).forEach((o) => {
|
|
36
|
-
const first = o.choicesSettings?.choices?.find((c) => c.inStock !== false);
|
|
37
|
-
if (first) initial[o.id] = first.choiceId;
|
|
38
|
-
});
|
|
39
|
-
setSelectedOptions(initial);
|
|
40
40
|
})
|
|
41
41
|
.catch((e) => { if (!cancelled) setError(e?.message || "Couldn't load this product."); });
|
|
42
42
|
return () => { cancelled = true; };
|
|
@@ -44,29 +44,70 @@ export function useProductDetail(slug) {
|
|
|
44
44
|
|
|
45
45
|
const options = product?.options || [];
|
|
46
46
|
const modifiers = product?.modifiers || [];
|
|
47
|
-
const variants = product?.variantsInfo?.variants || [];
|
|
48
47
|
|
|
48
|
+
// Visible variants keyed by their sorted choice ids. Two variants with the same choices is a
|
|
49
|
+
// catalog defect — report it instead of silently letting one overwrite the other.
|
|
50
|
+
const { variantByKey, duplicateKey } = useMemo(() => {
|
|
51
|
+
const map = new Map();
|
|
52
|
+
let dup = null;
|
|
53
|
+
for (const v of product?.variantsInfo?.variants || []) {
|
|
54
|
+
if (v.visible === false) continue;
|
|
55
|
+
const k = choiceKey((v.choices || []).map((c) => c.optionChoiceIds?.choiceId));
|
|
56
|
+
if (map.has(k) && !dup) dup = k;
|
|
57
|
+
map.set(k, v);
|
|
58
|
+
}
|
|
59
|
+
return { variantByKey: map, duplicateKey: dup };
|
|
60
|
+
}, [product]);
|
|
61
|
+
|
|
62
|
+
const selectionComplete = options.length > 0 && options.every((o) => selectedOptions[o.id]);
|
|
49
63
|
const variant = useMemo(() => {
|
|
50
|
-
if (options.length === 0) return
|
|
51
|
-
if (!
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
}, [options, variants, selectedOptions]);
|
|
64
|
+
if (options.length === 0) return variantByKey.values().next().value || null; // the single variant
|
|
65
|
+
if (!selectionComplete || duplicateKey) return null;
|
|
66
|
+
return variantByKey.get(choiceKey(options.map((o) => selectedOptions[o.id]))) || null;
|
|
67
|
+
}, [options, variantByKey, duplicateKey, selectedOptions, selectionComplete]);
|
|
55
68
|
|
|
69
|
+
// In stock OR pre-orderable counts as buyable; a preorder add carries preOrderRequested.
|
|
56
70
|
const inStock = variant ? variant.inventoryStatus?.inStock !== false : true;
|
|
71
|
+
const isPreorder = !!variant && !inStock && variant.inventoryStatus?.preorderEnabled === true;
|
|
72
|
+
const available = inStock || isPreorder;
|
|
73
|
+
|
|
74
|
+
const missingModifier = modifiers.find((m) => m.mandatory && !(
|
|
75
|
+
m.modifierRenderType === "FREE_TEXT" ? modifierValues[m.freeTextSettings?.key] : modifierValues[m.key]));
|
|
76
|
+
|
|
57
77
|
const canAdd = useMemo(() => {
|
|
78
|
+
if (!product) return false;
|
|
58
79
|
if (options.length > 0 && !variant) return false;
|
|
59
|
-
if (variant && !
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
if (variant && !available) return false;
|
|
81
|
+
return !missingModifier;
|
|
82
|
+
}, [product, options, variant, available, missingModifier]);
|
|
83
|
+
|
|
84
|
+
// Why the buy button is disabled — neutral guidance to render next to the action ("Choose a
|
|
85
|
+
// size"), not an error; null when the buyer can add. Promote to error styling only after they try.
|
|
86
|
+
const blockedReason = useMemo(() => {
|
|
87
|
+
if (!product) return null;
|
|
88
|
+
if (duplicateKey) return "This product's variants can't be resolved right now.";
|
|
89
|
+
if (options.length > 0 && !selectionComplete) {
|
|
90
|
+
const missing = options.filter((o) => !selectedOptions[o.id]).map((o) => o.name);
|
|
91
|
+
return `Choose ${missing.join(" and ")}`;
|
|
92
|
+
}
|
|
93
|
+
if (options.length > 0 && !variant) return "This combination isn't available";
|
|
94
|
+
if (variant && !available) return "Out of stock";
|
|
95
|
+
if (missingModifier) return `Add ${missingModifier.name}`;
|
|
96
|
+
return null;
|
|
97
|
+
}, [product, duplicateKey, options, selectionComplete, selectedOptions, variant, available, missingModifier]);
|
|
63
98
|
|
|
64
|
-
// Everything the buyer sees follows the resolved `variant` (also returned below
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
const
|
|
99
|
+
// Everything the buyer sees follows the resolved `variant` (also returned below). The price obeys
|
|
100
|
+
// the exact precedence in sellingPrice(): an automatic discount beats the regular price and strikes
|
|
101
|
+
// it; else the regular price with the merchant's compare-at as the "was". Before every option is
|
|
102
|
+
// picked, the product's min–max range (one value when equal) — never an empty price.
|
|
103
|
+
const min = product?.actualPriceRange?.minValue, max = product?.actualPriceRange?.maxValue;
|
|
104
|
+
const rangeDisplay = min?.amount && max?.amount && min.amount !== max.amount
|
|
105
|
+
? `${min.formattedAmount} – ${max.formattedAmount}` : (min?.formattedAmount || "");
|
|
106
|
+
const resolved = variant ? sellingPrice(variant.price) : null;
|
|
107
|
+
const price = resolved?.current?.formattedAmount || rangeDisplay;
|
|
108
|
+
const compareAtPrice = resolved && resolved.original?.formattedAmount &&
|
|
109
|
+
Number(resolved.original.amount) > Number(resolved.current?.amount) ? resolved.original.formattedAmount : "";
|
|
110
|
+
const discountNames = product?.discountInfo?.discountRuleNames || []; // automatic discounts, product level
|
|
70
111
|
|
|
71
112
|
// The gallery follows the selection. Prefer the selected choice's own image (the per-swatch photo,
|
|
72
113
|
// choice.media.items[].mediaId) — Wix's per-variant media is often NOT differentiated (it defaults to
|
|
@@ -82,7 +123,11 @@ export function useProductDetail(slug) {
|
|
|
82
123
|
return variantImage(variant);
|
|
83
124
|
}, [variant, options, selectedOptions]);
|
|
84
125
|
|
|
85
|
-
|
|
126
|
+
// Changing an option resets the quantity — the ceiling belongs to the newly resolved variant.
|
|
127
|
+
const selectOption = (optionId, choiceId) => {
|
|
128
|
+
setSelectedOptions((s) => ({ ...s, [optionId]: choiceId }));
|
|
129
|
+
setQuantity(1);
|
|
130
|
+
};
|
|
86
131
|
const setModifier = (key, value) => setModifierValues((s) => ({ ...s, [key]: value }));
|
|
87
132
|
|
|
88
133
|
async function submit() {
|
|
@@ -101,6 +146,7 @@ export function useProductDetail(slug) {
|
|
|
101
146
|
return await addToCart(product.id, variant?.id, Math.max(1, Number(quantity) || 1), {
|
|
102
147
|
modifierChoices: Object.keys(modifierChoices).length ? modifierChoices : undefined,
|
|
103
148
|
customTextFields: Object.keys(customTextFields).length ? customTextFields : undefined,
|
|
149
|
+
preorder: isPreorder,
|
|
104
150
|
});
|
|
105
151
|
} finally {
|
|
106
152
|
setAdding(false);
|
|
@@ -111,6 +157,7 @@ export function useProductDetail(slug) {
|
|
|
111
157
|
product, notFound, error, retry: () => setReloadKey((k) => k + 1),
|
|
112
158
|
options, modifiers,
|
|
113
159
|
selectedOptions, selectOption, modifierValues, setModifier,
|
|
114
|
-
quantity, setQuantity, variant, inStock,
|
|
160
|
+
quantity, setQuantity, variant, inStock, isPreorder, canAdd, blockedReason, adding,
|
|
161
|
+
price, compareAtPrice, discountNames, submit, focusMediaUrl,
|
|
115
162
|
};
|
|
116
163
|
}
|
|
@@ -40,17 +40,24 @@ const STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
|
|
|
40
40
|
* Throws on out-of-stock so the buyer can't reach checkout with an unbuyable line.
|
|
41
41
|
* Full catalogReference reference: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md
|
|
42
42
|
*
|
|
43
|
+
* A pre-order variant (inventoryStatus.preorderEnabled, out of stock) is added with `preorder: true`;
|
|
44
|
+
* a recurring subscription plan with its `subscriptionOptionId` (omit it for a one-time purchase).
|
|
45
|
+
* The two may coexist.
|
|
46
|
+
*
|
|
43
47
|
* @param {string} catalogItemId Product GUID (product.id).
|
|
44
48
|
* @param {string} [variantId] variantsInfo.variants[].id — required for products with variants.
|
|
45
49
|
* @param {number} [quantity]
|
|
46
|
-
* @param {{ modifierChoices?: Record<string,string>, customTextFields?: Record<string,string
|
|
50
|
+
* @param {{ modifierChoices?: Record<string,string>, customTextFields?: Record<string,string>,
|
|
51
|
+
* subscriptionOptionId?: string, preorder?: boolean }} [extras]
|
|
47
52
|
* @returns {Promise<object>} Updated cart.
|
|
48
53
|
*/
|
|
49
|
-
export async function addToCart(catalogItemId, variantId, quantity = 1, { modifierChoices, customTextFields } = {}) {
|
|
54
|
+
export async function addToCart(catalogItemId, variantId, quantity = 1, { modifierChoices, customTextFields, subscriptionOptionId, preorder } = {}) {
|
|
50
55
|
const catalogReferenceOptions = {};
|
|
51
56
|
if (variantId) catalogReferenceOptions.variantId = variantId;
|
|
52
57
|
if (modifierChoices && Object.keys(modifierChoices).length) catalogReferenceOptions.options = modifierChoices;
|
|
53
58
|
if (customTextFields && Object.keys(customTextFields).length) catalogReferenceOptions.customTextFields = customTextFields;
|
|
59
|
+
if (subscriptionOptionId) catalogReferenceOptions.subscriptionOptionId = subscriptionOptionId;
|
|
60
|
+
if (preorder) catalogReferenceOptions.preOrderRequested = true;
|
|
54
61
|
|
|
55
62
|
const catalogReference = { appId: STORES_APP_ID, catalogItemId };
|
|
56
63
|
if (Object.keys(catalogReferenceOptions).length) catalogReference.options = catalogReferenceOptions;
|
|
@@ -92,8 +99,30 @@ export async function getCurrentCart() {
|
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Estimate the current cart's totals — the ONLY source of the after-discount subtotal and the
|
|
104
|
+
* cart-level discount. Line prices already carry item-level discounts; delivery and tax resolve at
|
|
105
|
+
* checkout unless asked for, so never invent them. Returns null when there is no cart or the
|
|
106
|
+
* estimate fails (a display nicety — the cart itself stays valid).
|
|
107
|
+
* https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/estimate-current-cart.md
|
|
108
|
+
* @returns {Promise<{ priceSummary: { subtotal: object, discount: object, total: object } }|null>}
|
|
109
|
+
* priceSummary amounts are ConvertedMoney { amount, convertedAmount } — format like line prices.
|
|
110
|
+
*/
|
|
111
|
+
export async function estimateCurrentCart() {
|
|
112
|
+
try {
|
|
113
|
+
const res = await wixApiRequest("/ecom/v2/carts/current/estimate", { method: "POST", body: {} });
|
|
114
|
+
return res?.summary ?? null;
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
95
120
|
/**
|
|
96
121
|
* Start the hosted checkout for the current cart and return its URL.
|
|
122
|
+
* This is the shopper's CURRENT cart. A "Buy now" that skips the cart needs a standalone cart
|
|
123
|
+
* (POST /ecom/v2/carts) handed to the redirect session — it must never append to or replace the
|
|
124
|
+
* current cart. Not shipped here; verify the redirect session accepts a standalone cart id under
|
|
125
|
+
* the visitor token before adding it.
|
|
97
126
|
* In Cart V2 the cart id IS the checkout id — there is no separate checkout-creation call; the
|
|
98
127
|
* redirect session is created straight from the current cart's id.
|
|
99
128
|
* Throws on empty cart, unavailable lines, or a missing redirect URL.
|
|
@@ -13,8 +13,13 @@ import { wixApiRequest } from "./wix-client.js";
|
|
|
13
13
|
* actualPriceRange.minValue.formattedAmount {string} — lowest price with currency symbol,
|
|
14
14
|
* actualPriceRange.maxValue.formattedAmount {string} — highest price with currency symbol,
|
|
15
15
|
* compareAtPriceRange.minValue.formattedAmount {string} — strikethrough price (present when on sale),
|
|
16
|
-
* actualPriceRange/compareAtPriceRange .minValue.amount {string} — the same figures unformatted
|
|
17
|
-
*
|
|
16
|
+
* actualPriceRange/compareAtPriceRange .minValue.amount {string} — the same figures unformatted.
|
|
17
|
+
* Never compute a percent-off or "you save" claim from them: a range's minimum says nothing about
|
|
18
|
+
* the variant the buyer picks. The price the buyer pays comes from sellingPrice() below.
|
|
19
|
+
* variantSummary.minPriceVariant {object} — the lowest-priced visible variant (MIN_PRICE_VARIANT):
|
|
20
|
+
* { id, price: { actualPrice, compareAtPrice?, priceAfterDiscount? } } — a card's price, and the
|
|
21
|
+
* variantId for a direct add on a product with no options,
|
|
22
|
+
* discountInfo.discountRuleNames {string[]} — automatic-discount names (DISCOUNT_INFO), product level,
|
|
18
23
|
* inventory.availabilityStatus {string} — "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK",
|
|
19
24
|
* inventory.preorderStatus {string} — "ENABLED"|"DISABLED". ENABLED + OUT_OF_STOCK is a pre-order,
|
|
20
25
|
* which is buyable — label it "Pre-order" rather than "Sold out",
|
|
@@ -23,8 +28,9 @@ import { wixApiRequest } from "./wix-client.js";
|
|
|
23
28
|
* getProductBySlug call per product,
|
|
24
29
|
* ribbon {object} — { id, name } merchant-set badge: "New", "Sale", "Best Seller". This is the
|
|
25
30
|
* catalogue's own label, so prefer it over anything derived — a "new" badge computed from
|
|
26
|
-
* createdDate flags every product at once right after seeding
|
|
27
|
-
*
|
|
31
|
+
* createdDate flags every product at once right after seeding. A ribbon is a LABEL: a "Sale"
|
|
32
|
+
* ribbon is not proof of a lower price, and a compareAtPrice does not earn a ribbon,
|
|
33
|
+
* additionalRibbons {array} — up to four further { id, name } badges. Render ALL of them,
|
|
28
34
|
* createdDate / updatedDate {string} — ISO timestamps,
|
|
29
35
|
* variantSummary.variantCount {number} — how many variants exist, without fetching them,
|
|
30
36
|
* options {array} — product options e.g. Size, Color:
|
|
@@ -45,9 +51,11 @@ import { wixApiRequest } from "./wix-client.js";
|
|
|
45
51
|
* dangerouslySetInnerHTML; strip tags only for plain-text contexts (meta description, teaser),
|
|
46
52
|
* variantsInfo.variants {array} — returned only by getProductBySlug:
|
|
47
53
|
* [{ id, visible, choices [{ optionChoiceIds: { optionId, choiceId } }],
|
|
48
|
-
* price: { actualPrice, compareAtPrice }, media,
|
|
54
|
+
* price: { actualPrice, compareAtPrice?, priceAfterDiscount? }, media,
|
|
55
|
+
* inventoryStatus: { inStock, preorderEnabled } }]
|
|
49
56
|
* To resolve a buyer's option selections to a variantId: find the variant whose choices
|
|
50
57
|
* match all selected { optionId, choiceId } pairs, then pass variant.id to addToCart.
|
|
58
|
+
* A variant is buyable when inStock OR preorderEnabled (a preorder add carries preOrderRequested).
|
|
51
59
|
*
|
|
52
60
|
* Category: { id, name, slug, visible, description, image, itemCounter, parentCategory.id }
|
|
53
61
|
* NB: queryCategories includes the auto-created system category { slug: "all-products" } —
|
|
@@ -55,6 +63,20 @@ import { wixApiRequest } from "./wix-client.js";
|
|
|
55
63
|
* Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories
|
|
56
64
|
*/
|
|
57
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The price the buyer pays, and the price to strike — exact precedence, shared by cards and the PDP.
|
|
68
|
+
* An automatic discount (priceAfterDiscount, present only when DISCOUNT_INFO is requested) wins and
|
|
69
|
+
* strikes the regular actualPrice; otherwise actualPrice with the merchant's compareAtPrice as the
|
|
70
|
+
* "was". `!== undefined` on purpose: a discounted price of 0 is a real price.
|
|
71
|
+
* @param {{ actualPrice?: object, compareAtPrice?: object, priceAfterDiscount?: object }} [price]
|
|
72
|
+
* variant.price, or variantSummary.minPriceVariant.price. Each is { amount, formattedAmount }.
|
|
73
|
+
* @returns {{ current: object|undefined, original: object|undefined }} render .formattedAmount
|
|
74
|
+
*/
|
|
75
|
+
export function sellingPrice(price) {
|
|
76
|
+
if (price?.priceAfterDiscount !== undefined) return { current: price.priceAfterDiscount, original: price.actualPrice };
|
|
77
|
+
return { current: price?.actualPrice, original: price?.compareAtPrice };
|
|
78
|
+
}
|
|
79
|
+
|
|
58
80
|
// Search Products supports server-side sort/filter before cursor paging:
|
|
59
81
|
// https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
|
|
60
82
|
export const CATALOG_SORTS = {
|
|
@@ -108,7 +130,9 @@ export async function searchProducts({ limit = 100, cursor, categoryId, sort = "
|
|
|
108
130
|
}
|
|
109
131
|
const res = await wixApiRequest("/stores/v3/products/search", {
|
|
110
132
|
method: "POST",
|
|
111
|
-
|
|
133
|
+
// MIN_PRICE_VARIANT + DISCOUNT_INFO → variantSummary.minPriceVariant with its discounted price:
|
|
134
|
+
// the card's price and the direct-add variantId, without a per-product fetch.
|
|
135
|
+
body: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO", "MIN_PRICE_VARIANT", "DISCOUNT_INFO"], search: query },
|
|
112
136
|
});
|
|
113
137
|
return { products: res?.products ?? [], nextCursor: res?.pagingMetadata?.cursors?.next ?? null };
|
|
114
138
|
}
|
|
@@ -137,6 +161,7 @@ export async function getProductBySlug(slug) {
|
|
|
137
161
|
"MEDIA_ITEMS_INFO",
|
|
138
162
|
"PRODUCT_CHOICES_MEDIA_REFERENCES",
|
|
139
163
|
"VARIANT_OPTION_CHOICE_NAMES",
|
|
164
|
+
"DISCOUNT_INFO", // variant price.priceAfterDiscount + discountInfo.discountRuleNames
|
|
140
165
|
],
|
|
141
166
|
},
|
|
142
167
|
});
|