@wix/agent-skills 1.18.0 → 1.18.2
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/SKILL.md +4 -4
- package/skills/wix-app/references/APP_VALIDATION.md +7 -33
- package/skills/wix-app/references/DASHBOARD_PAGE.md +18 -14
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +75 -92
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +23 -26
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +7 -6
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +71 -4
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +67 -7
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -1
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +9 -6
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +59 -89
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +23 -4
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +29 -14
- package/skills/wix-base44-connector/SKILL.md +168 -48
- package/skills/wix-base44-connector/scripts/{utils.js → utils.cjs} +69 -48
- package/skills/wix-docs/SKILL.md +109 -111
- package/skills/wix-docs/references/API_SPEC_SEARCH.md +35 -23
- package/skills/wix-docs/references/CALLING.md +91 -0
- package/skills/wix-docs/references/EXTRACTING.md +5 -3
- package/skills/wix-headless/references/DOC_DISCOVERY.md +9 -3
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +57 -10
- package/skills/wix-manage/SKILL.md +106 -92
- package/skills/wix-manage/references/dashboard-navigation/routes.json +12 -52
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-api.md +0 -2
- package/skills/wix-manage/references/forms/create-form.md +272 -184
- package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +13 -10
- package/skills/wix-manage/references/forms/update-form.md +122 -0
- package/skills/wix-manage/references/media/generate-image-with-ai.md +86 -0
- package/skills/wix-manage/references/seo/manage-content-plan.md +247 -0
- package/skills/wix-manage/references/seo/manage-seo-tags.md +59 -6
- package/skills/wix-manage/references/stores/change-store-currency.md +10 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +16 -1
- package/skills/wix-manage/references/stores/update-product-with-options.md +9 -0
- package/skills/wix-vibe-headless/SKILL.md +17 -18
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +118 -0
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +55 -62
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +31 -29
- package/skills/wix-vibe-headless/platforms/base44.md +12 -5
- package/skills/wix-vibe-headless/platforms/generic.md +13 -12
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/blog/seed/SEED.md +4 -8
- package/skills/wix-vibe-headless/references/blog/seed/{seed-blog.js → seed-blog.cjs} +2 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +5 -14
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +5 -9
- package/skills/wix-vibe-headless/references/bookings/seed/{seed-bookings.js → seed-bookings.cjs} +2 -2
- package/skills/wix-vibe-headless/references/bookings/seed/{seed-rentals.js → seed-rentals.cjs} +3 -3
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +1 -1
- package/skills/wix-vibe-headless/references/cms/seed/SEED.md +2 -2
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/events/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/events/seed/{seed-events.js → seed-events.cjs} +2 -2
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +24 -7
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +91 -17
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/members/seed/SEED.md +2 -3
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +4 -13
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/portfolio/seed/{seed-portfolio.js → seed-portfolio.cjs} +2 -2
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +5 -14
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/pricing-plans/seed/{seed-pricing-plans.js → seed-pricing-plans.cjs} +2 -2
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +5 -15
- package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +3 -7
- package/skills/wix-vibe-headless/references/restaurants/seed/{seed-restaurants.js → seed-restaurants.cjs} +2 -2
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +359 -327
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +1 -0
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +2 -2
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +63 -78
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +59 -43
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +46 -10
- package/skills/wix-vibe-headless/references/storefront/seed/{seed-store.js → seed-store.cjs} +163 -25
- package/skills/wix-vibe-headless/references/storefront/app/pages/Shop.jsx +0 -75
|
@@ -40,6 +40,7 @@ export function CartProvider({ children }) {
|
|
|
40
40
|
return await fn();
|
|
41
41
|
} catch (e) {
|
|
42
42
|
setError(e?.message || "Something went wrong. Please try again.");
|
|
43
|
+
setIsOpen(true); // Surface refusals even when adding from a card or PDP with the drawer closed.
|
|
43
44
|
if (reread) await refreshCart().catch(() => {});
|
|
44
45
|
return null;
|
|
45
46
|
} finally {
|
|
@@ -95,10 +95,10 @@ export function useProductDetail(slug) {
|
|
|
95
95
|
setAdding(true);
|
|
96
96
|
try {
|
|
97
97
|
// addToCart reports its own failures through the cart context (it opens the drawer with the
|
|
98
|
-
// reason)
|
|
98
|
+
// reason). Preserve its undefined-on-success / null-on-failure result for chained actions.
|
|
99
99
|
// Coerce the quantity: the shipped stepper lets the field sit empty mid-edit, and a keyboard
|
|
100
100
|
// submit from that state would otherwise post "" as the quantity.
|
|
101
|
-
await addToCart(product.id, variant?.id, Math.max(1, Number(quantity) || 1), {
|
|
101
|
+
return await addToCart(product.id, variant?.id, Math.max(1, Number(quantity) || 1), {
|
|
102
102
|
modifierChoices: Object.keys(modifierChoices).length ? modifierChoices : undefined,
|
|
103
103
|
customTextFields: Object.keys(customTextFields).length ? customTextFields : undefined,
|
|
104
104
|
});
|
|
@@ -1,98 +1,83 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
// Load-bearing:
|
|
6
|
-
// • paging is CURSOR-based. Keep the cursor from the previous response and pass it back; a cursor
|
|
7
|
-
// follow-up reuses the original filter, so the category is fixed for the life of that cursor.
|
|
8
|
-
// Changing category starts a fresh query, never a cursor continuation.
|
|
9
|
-
// • queryCategories returns the auto-created "all-products" system category alongside real ones, and
|
|
10
|
-
// `visible` does not flag it — filter it by slug or the menu shows a duplicate of everything.
|
|
11
|
-
// • sorting is done here on the loaded page, not by the API: with cursor paging the server would
|
|
12
|
-
// re-sort per page, so a sort applied across pages already fetched is the honest option. Say so in
|
|
13
|
-
// the UI if the catalog is larger than one page.
|
|
14
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
15
|
-
import { queryProducts, queryProductsByCategory, queryCategories } from "@/rest/wix-store-catalog";
|
|
1
|
+
// Catalog queries run on Wix before pagination. A changed selection starts a fresh cursor chain.
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { searchProducts, queryCategories, CATALOG_SORTS } from "@/rest/wix-store-catalog";
|
|
16
4
|
|
|
5
|
+
export const SORTS = CATALOG_SORTS;
|
|
17
6
|
const SYSTEM_CATEGORY_SLUG = "all-products";
|
|
18
|
-
export const SORTS = {
|
|
19
|
-
featured: { label: "Featured" },
|
|
20
|
-
priceAsc: { label: "Price: low to high" },
|
|
21
|
-
priceHigh: { label: "Price: high to low" },
|
|
22
|
-
name: { label: "Name: A–Z" },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Prices arrive formatted for display ("$12.00"), so read the numeric value for sorting from the
|
|
26
|
-
// amount when it's there and fall back to digits in the formatted string.
|
|
27
|
-
function priceOf(product) {
|
|
28
|
-
const min = product?.actualPriceRange?.minValue;
|
|
29
|
-
const n = Number(min?.amount ?? String(min?.formattedAmount || "").replace(/[^\d.]/g, ""));
|
|
30
|
-
return Number.isFinite(n) ? n : 0;
|
|
31
|
-
}
|
|
32
7
|
|
|
33
8
|
export function useShop({ pageSize = 24 } = {}) {
|
|
34
9
|
const [categories, setCategories] = useState([]);
|
|
35
|
-
const [activeCategory, setActiveCategory] = useState(null);
|
|
36
|
-
const [products, setProducts] = useState(null); // null = first load
|
|
37
|
-
const [cursor, setCursor] = useState(null);
|
|
38
|
-
const [loadingMore, setLoadingMore] = useState(false);
|
|
39
|
-
const [error, setError] = useState(null);
|
|
10
|
+
const [activeCategory, setActiveCategory] = useState(null);
|
|
40
11
|
const [sort, setSort] = useState("featured");
|
|
12
|
+
const [filters, setFilters] = useState({});
|
|
13
|
+
const [attempt, setAttempt] = useState(0);
|
|
14
|
+
const [page, setPage] = useState({ key: null, products: null, cursor: null, error: null, loadingMore: false });
|
|
15
|
+
const generation = useRef(0);
|
|
16
|
+
const pendingMore = useRef(null);
|
|
17
|
+
const key = JSON.stringify([pageSize, activeCategory?.id ?? null, sort, filters.minPrice ?? null,
|
|
18
|
+
filters.maxPrice ?? null, !!filters.inStockOnly, filters.search ?? "", attempt]);
|
|
19
|
+
const currentKey = useRef(key);
|
|
20
|
+
currentKey.current = key;
|
|
41
21
|
|
|
42
22
|
useEffect(() => {
|
|
23
|
+
let alive = true;
|
|
43
24
|
queryCategories({ limit: 100 })
|
|
44
|
-
.then(({ categories: list }) =>
|
|
45
|
-
setCategories((list || []).filter(
|
|
46
|
-
|
|
25
|
+
.then(({ categories: list }) => {
|
|
26
|
+
if (alive) setCategories((list || []).filter(c => c.visible !== false && c.slug !== SYSTEM_CATEGORY_SLUG));
|
|
27
|
+
})
|
|
28
|
+
.catch(() => { if (alive) setCategories([]); });
|
|
29
|
+
return () => { alive = false; };
|
|
47
30
|
}, []);
|
|
48
31
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const id = ++generation.current;
|
|
34
|
+
let alive = true;
|
|
35
|
+
pendingMore.current = null;
|
|
36
|
+
setPage({ key, products: null, cursor: null, error: null, loadingMore: false });
|
|
37
|
+
const [limit, categoryId, selectedSort, minPrice, maxPrice, inStockOnly, search] = JSON.parse(key);
|
|
38
|
+
searchProducts({ limit, categoryId, sort: selectedSort, minPrice, maxPrice, inStockOnly, search })
|
|
39
|
+
.then(res => {
|
|
40
|
+
if (alive && generation.current === id && currentKey.current === key) {
|
|
41
|
+
setPage({ key, products: res.products, cursor: res.nextCursor, error: null, loadingMore: false });
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
.catch(e => {
|
|
45
|
+
if (alive && generation.current === id && currentKey.current === key) {
|
|
46
|
+
setPage({ key, products: [], cursor: null, error: e?.message || "Couldn't load products.", loadingMore: false });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return () => { alive = false; generation.current++; };
|
|
50
|
+
}, [key]);
|
|
66
51
|
|
|
67
|
-
const loadMore = async () => {
|
|
68
|
-
if (!cursor ||
|
|
69
|
-
|
|
52
|
+
const loadMore = useCallback(async () => {
|
|
53
|
+
if (page.key !== key || !page.cursor || pendingMore.current) return;
|
|
54
|
+
const request = { generation: generation.current, key };
|
|
55
|
+
pendingMore.current = request; // blocks repeated clicks before React rerenders
|
|
56
|
+
const isCurrent = () => generation.current === request.generation && currentKey.current === key;
|
|
57
|
+
setPage(p => ({ ...p, loadingMore: true, error: null }));
|
|
70
58
|
try {
|
|
71
|
-
const res =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
const res = await searchProducts({ limit: pageSize, cursor: page.cursor });
|
|
60
|
+
if (isCurrent()) setPage(p => {
|
|
61
|
+
const seen = new Set((p.products || []).map(product => product.id));
|
|
62
|
+
return { ...p, products: [...(p.products || []), ...res.products.filter(product => {
|
|
63
|
+
if (seen.has(product.id)) return false;
|
|
64
|
+
seen.add(product.id); return true;
|
|
65
|
+
})], cursor: res.nextCursor };
|
|
66
|
+
});
|
|
76
67
|
} catch (e) {
|
|
77
|
-
|
|
68
|
+
if (isCurrent()) setPage(p => ({ ...p, error: e?.message || "Couldn't load more products." }));
|
|
78
69
|
} finally {
|
|
79
|
-
|
|
70
|
+
if (pendingMore.current === request) pendingMore.current = null;
|
|
71
|
+
if (isCurrent()) setPage(p => ({ ...p, loadingMore: false }));
|
|
80
72
|
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const sorted = useMemo(() => {
|
|
84
|
-
if (!products) return null;
|
|
85
|
-
const copy = [...products];
|
|
86
|
-
if (sort === "priceAsc") copy.sort((a, b) => priceOf(a) - priceOf(b));
|
|
87
|
-
else if (sort === "priceHigh") copy.sort((a, b) => priceOf(b) - priceOf(a));
|
|
88
|
-
else if (sort === "name") copy.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
|
|
89
|
-
return copy;
|
|
90
|
-
}, [products, sort]);
|
|
73
|
+
}, [key, page.key, page.cursor, pageSize]);
|
|
91
74
|
|
|
75
|
+
const retry = useCallback(() => setAttempt(n => n + 1), []);
|
|
76
|
+
const current = page.key === key;
|
|
92
77
|
return {
|
|
93
|
-
categories, activeCategory, setActiveCategory,
|
|
94
|
-
products:
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
categories, activeCategory, setActiveCategory, sort, setSort, filters, setFilters,
|
|
79
|
+
products: current ? page.products : null, loading: !current || page.products === null,
|
|
80
|
+
error: current ? page.error : null, retry,
|
|
81
|
+
hasMore: current && !!page.cursor, loadMore, loadingMore: current && page.loadingMore,
|
|
97
82
|
};
|
|
98
83
|
}
|
|
@@ -55,26 +55,67 @@ import { wixApiRequest } from "./wix-client.js";
|
|
|
55
55
|
* Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories
|
|
56
56
|
*/
|
|
57
57
|
|
|
58
|
+
// Search Products supports server-side sort/filter before cursor paging:
|
|
59
|
+
// https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
|
|
60
|
+
export const CATALOG_SORTS = {
|
|
61
|
+
featured: { label: "Default order" }, // legacy key; no promise of merchant-curated ranking
|
|
62
|
+
priceAsc: { label: "Price: low to high" },
|
|
63
|
+
priceHigh: { label: "Price: high to low" },
|
|
64
|
+
name: { label: "Name: A–Z" },
|
|
65
|
+
newest: { label: "Newest" },
|
|
66
|
+
};
|
|
67
|
+
const SORT_FIELDS = {
|
|
68
|
+
priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
|
|
69
|
+
priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
70
|
+
name: [{ fieldName: "name", order: "ASC" }],
|
|
71
|
+
newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function priceBound(value, name) {
|
|
75
|
+
if (value == null || value === "") return undefined;
|
|
76
|
+
if (!["number", "string"].includes(typeof value) || !String(value).trim() || !Number.isFinite(Number(value)) || Number(value) < 0) {
|
|
77
|
+
throw new Error(`${name} must be a non-negative number.`);
|
|
78
|
+
}
|
|
79
|
+
return Number(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
58
82
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
83
|
+
* Search visible catalog products, sorted/filtered across the whole catalog.
|
|
84
|
+
* Price bounds and ordering use the product's minimum actual variant price in site currency.
|
|
85
|
+
* A cursor continues the original query; start without one when any selection changes.
|
|
86
|
+
* @param {{ limit?: number, cursor?: string, categoryId?: string, sort?: string,
|
|
87
|
+
* minPrice?: number|string, maxPrice?: number|string, inStockOnly?: boolean, search?: string }} [options]
|
|
61
88
|
* @returns {Promise<{ products: object[], nextCursor: string|null }>}
|
|
62
89
|
*/
|
|
63
|
-
export async function
|
|
64
|
-
|
|
90
|
+
export async function searchProducts({ limit = 100, cursor, categoryId, sort = "featured", minPrice, maxPrice, inStockOnly = false, search = "" } = {}) {
|
|
91
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("limit must be between 1 and 100.");
|
|
92
|
+
let query = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
|
|
93
|
+
if (!cursor) {
|
|
94
|
+
if (!Object.hasOwn(CATALOG_SORTS, sort)) throw new Error("Unsupported catalog sort.");
|
|
95
|
+
const min = priceBound(minPrice, "minPrice"), max = priceBound(maxPrice, "maxPrice");
|
|
96
|
+
if (min !== undefined && max !== undefined && min > max) throw new Error("minPrice must not exceed maxPrice.");
|
|
97
|
+
if (typeof search !== "string" || search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
|
|
98
|
+
const conditions = [{ visible: true }];
|
|
99
|
+
if (categoryId) conditions.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
|
|
100
|
+
// Search rejects two operators in the same field object. Join separate bounds with $and.
|
|
101
|
+
if (min !== undefined) conditions.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
|
|
102
|
+
if (max !== undefined) conditions.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
|
|
103
|
+
if (inStockOnly) conditions.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
|
|
104
|
+
query = { ...query, filter: { $and: conditions },
|
|
105
|
+
...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
|
|
106
|
+
...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const res = await wixApiRequest("/stores/v3/products/search", {
|
|
65
110
|
method: "POST",
|
|
66
|
-
body: {
|
|
67
|
-
fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
|
|
68
|
-
query: {
|
|
69
|
-
...(cursor ? {} : { filter: { visible: true } }),
|
|
70
|
-
cursorPaging: cursor ? { limit, cursor } : { limit },
|
|
71
|
-
},
|
|
72
|
-
},
|
|
111
|
+
body: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"], search: query },
|
|
73
112
|
});
|
|
74
|
-
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
return { products: res?.products ?? [], nextCursor: res?.pagingMetadata?.cursors?.next ?? null };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Visible products; accepts the same sort/filter options as searchProducts. */
|
|
117
|
+
export function queryProducts(options = {}) {
|
|
118
|
+
return searchProducts(options);
|
|
78
119
|
}
|
|
79
120
|
|
|
80
121
|
/**
|
|
@@ -102,34 +143,9 @@ export async function getProductBySlug(slug) {
|
|
|
102
143
|
return res?.product ?? null;
|
|
103
144
|
}
|
|
104
145
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
* @param {{ limit?: number, cursor?: string }} [options]
|
|
109
|
-
* @returns {Promise<{ products: object[], nextCursor: string|null }>}
|
|
110
|
-
*/
|
|
111
|
-
export async function queryProductsByCategory(categoryId, { limit = 100, cursor } = {}) {
|
|
112
|
-
const res = await wixApiRequest("/stores/v3/products/search", {
|
|
113
|
-
method: "POST",
|
|
114
|
-
body: {
|
|
115
|
-
fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
|
|
116
|
-
search: {
|
|
117
|
-
...(cursor
|
|
118
|
-
? { cursorPaging: { limit, cursor } }
|
|
119
|
-
: {
|
|
120
|
-
cursorPaging: { limit },
|
|
121
|
-
filter: {
|
|
122
|
-
visible: true,
|
|
123
|
-
"allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] },
|
|
124
|
-
},
|
|
125
|
-
}),
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
return {
|
|
130
|
-
products: res?.products ?? [],
|
|
131
|
-
nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
|
|
132
|
-
};
|
|
146
|
+
/** Category products; accepts the same sort/filter options as searchProducts. */
|
|
147
|
+
export function queryProductsByCategory(categoryId, options = {}) {
|
|
148
|
+
return searchProducts({ ...options, categoryId });
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
/**
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Storefront — seeding
|
|
2
2
|
|
|
3
|
-
Seed a Wix Stores catalog by **calling `seed-store.
|
|
3
|
+
Seed a Wix Stores catalog by **calling `seed-store.cjs`** — don't hand-write the REST calls. It's
|
|
4
4
|
a build-time module (run via `exec_tool`, not shipped in the app) that abstracts every Wix Stores
|
|
5
5
|
seed operation. Load it and call **`setupStore` — the one-call path** — with plain data.
|
|
6
|
+
Pass only the connector token and catalog data. The module handles site configuration internally;
|
|
7
|
+
do not read the config or supply site/client IDs.
|
|
6
8
|
|
|
7
9
|
**Match each product's type to what the buyer receives** (the example shows both). *Access* — a
|
|
8
10
|
membership, or an online course/program the buyer enrolls in — isn't a store product at all; that's
|
|
@@ -11,12 +13,8 @@ the `pricing-plans` vertical, not here.
|
|
|
11
13
|
```js
|
|
12
14
|
// build-time exec_tool
|
|
13
15
|
const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const seed = (() => { const m = { exports: {} };
|
|
17
|
-
new Function("module", "exports", "require", fs.readFileSync("/app/.agents/skills/wix-vibe-headless/references/storefront/seed/seed-store.js", "utf8"))(m, m.exports, require);
|
|
18
|
-
return m.exports; })();
|
|
19
|
-
const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
|
|
16
|
+
const seed = require(require("path").resolve(".agents/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs"));
|
|
17
|
+
const ctx = { token: accessToken };
|
|
20
18
|
|
|
21
19
|
// ONE call: install (+ wait for V3) → create products → categories → attach images, ids kept
|
|
22
20
|
// in memory (no hand-threading). Categories map name -> product NAMES. Pass an imageUrl per product
|
|
@@ -25,6 +23,7 @@ const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
|
|
|
25
23
|
// result — not a still-generating /__generating__/<id>.png placeholder (Wix can't fetch that).
|
|
26
24
|
// generate_image runs in the background while you build, so the urls are ready by seed time.
|
|
27
25
|
const result = await seed.setupStore(ctx, {
|
|
26
|
+
currency: "EUR", // Pass only if the user asked for a currency or it's obvious for the store; else omit this line.
|
|
28
27
|
products: [
|
|
29
28
|
// physical — a shipped item: carries `quantity` (the default type)
|
|
30
29
|
{ name: "The Glam Rocker", description: "Sequin-studded velvet legend…", price: 49.99, quantity: 12, imageUrl: imageUrls[0] },
|
|
@@ -38,12 +37,40 @@ const result = await seed.setupStore(ctx, {
|
|
|
38
37
|
],
|
|
39
38
|
categories: { "Legends": ["The Glam Rocker"], "Rising Stars": [] }, // omit if the brief names none
|
|
40
39
|
});
|
|
41
|
-
// result: { products:[{id,slug,revision,name}], categories:[{id,name}], imagesAttached
|
|
40
|
+
// result: { products:[{id,slug,revision,name}], categories:[{id,name}], imagesAttached,
|
|
41
|
+
// currency: { requested, actual, status, warnings } }
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
The optional `currency` sets the site's payment currency before product creation. Pass it only when
|
|
45
|
+
the user explicitly asked for a currency, or when it's obvious for the store — otherwise omit it. Do
|
|
46
|
+
not infer a currency from the builder's country/region or the brief's language; when in doubt, leave
|
|
47
|
+
it out and the current site currency is preserved. Product prices are numbers in that currency;
|
|
48
|
+
changing currency does not convert existing amounts.
|
|
49
|
+
Currency update or verification failures do not stop seeding: inspect `result.currency.status`
|
|
50
|
+
and `warnings`, report the unresolved setting, and use the connector skill to resolve it. An
|
|
51
|
+
unknown actual currency is `null`; do not replace currency symbols to simulate a successful update.
|
|
52
|
+
Currency changes may take time to appear in existing product responses, even after carts and
|
|
53
|
+
checkout use the new currency. If `result.currency.status` confirms the update succeeded, continue
|
|
54
|
+
without waiting for or verifying the change in product responses or the preview.
|
|
55
|
+
|
|
56
|
+
|
|
44
57
|
**Seeding is additive — never delete or overwrite existing content.** Don't clean up, don't remove
|
|
45
58
|
"sample" data, don't reset. Just add.
|
|
46
59
|
|
|
60
|
+
## Stock
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
{ name: "Limited Print", price: 25, quantity: 12 } // count stock: integer 0–99999
|
|
64
|
+
{ name: "Made-to-order Print", price: 25, inStock: true } // available without a quantity counter
|
|
65
|
+
{ name: "Unavailable Print", price: 25, inStock: false } // unavailable without a quantity counter
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Supply `quantity` **or** `inStock`, never both. Use `inStock: true` for unlimited stock, not a
|
|
69
|
+
large invented quantity. All expanded variants inherit the same setting. Omitting both defaults
|
|
70
|
+
to quantity 0 for physical products and in-stock for downloads. Stock mode doesn't change the
|
|
71
|
+
product type or remove the downloadable-file requirement.
|
|
72
|
+
[Wix inventory tracking](https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/inventory-items-v3/inventory-item-object).
|
|
73
|
+
|
|
47
74
|
## How many, and exercising the UI
|
|
48
75
|
|
|
49
76
|
**Default to 3 products** unless the brief asks for a specific catalog — the seed shows the shape,
|
|
@@ -63,8 +90,12 @@ options: [
|
|
|
63
90
|
|
|
64
91
|
- `type: "color"` → `SWATCH_CHOICES` with each choice's `colorCode`, which the PDP draws as a swatch.
|
|
65
92
|
Any other `type` → text pills. Give every colour choice a `colorCode`.
|
|
93
|
+
Within a batch, reuse the same color code for the same option/choice name across products;
|
|
94
|
+
give different shades distinct names (for example, Forest Green and Light Green).
|
|
95
|
+
- Choice names must be unique within each product option, for both text and color choices.
|
|
96
|
+
Reusing a choice name across different products is fine; color choices must follow the consistency rule above.
|
|
66
97
|
- Variants are expanded for you: the full cross-product of the options, each carrying the product's
|
|
67
|
-
`price`, `compareAtPrice` and `quantity
|
|
98
|
+
`price`, `compareAtPrice` and stock setting (`quantity` or `inStock`). Two options with 2 and 3 choices means 6 variants — keep
|
|
68
99
|
option counts small.
|
|
69
100
|
- `compareAtPrice` (> `price`) is the "was" price: strikethrough on the PDP and a `−N%` badge on the
|
|
70
101
|
tile, computed from the two amounts. It works with or without options.
|
|
@@ -92,6 +123,11 @@ Two things this module does **not** seed, so don't try:
|
|
|
92
123
|
follows automatically — `choiceImage()` reads it back at `media.items[].mediaId`.
|
|
93
124
|
|
|
94
125
|
## Escape hatch — individual functions
|
|
126
|
+
|
|
127
|
+
If seeding reports a partial failure, keep the reported successful product IDs and correct the
|
|
128
|
+
failed inputs. Do not rerun the whole seed or wrap it in a retry loop: creation may already have
|
|
129
|
+
succeeded for some products. Missing results mean unknown creation status; inspect before creating again.
|
|
130
|
+
|
|
95
131
|
Reach for the functions below only when the one-call `setupStore` doesn't fit (partial re-seed, custom
|
|
96
132
|
ordering, mid-flow checks). `setupStore` is built from them, in this order:
|
|
97
133
|
|
|
@@ -118,7 +154,7 @@ await seed.attachProductImages(ctx, products.map((p, i) => ({ id: p.id, url: ima
|
|
|
118
154
|
|
|
119
155
|
## Reference
|
|
120
156
|
If a call returns a shape you didn't expect, or you need an operation this module doesn't cover,
|
|
121
|
-
use the
|
|
157
|
+
use the documentation skill available in your environment to search + read the live Wix API reference — never guess. The
|
|
122
158
|
authoritative source recipe is `wix-headless/references/inline-recipes/setup-online-store.md`.
|
|
123
159
|
|
|
124
160
|
Read a method's page before writing its call: it carries the exact body shape, the required
|