@wix/agent-skills 1.14.0 → 1.15.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/README.md +4 -1
- package/package.json +1 -1
- package/skills/README.md +23 -0
- package/skills/wix-app/SKILL.md +10 -2
- package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +326 -0
- package/skills/wix-app/references/CODE_QUALITY.md +1 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +124 -115
- package/skills/wix-app/references/DASHBOARD_PAGE.md +3 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +23 -9
- package/skills/wix-app/references/SITE_PLUGIN.md +1 -1
- package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +115 -0
- package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +264 -0
- package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +37 -0
- package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +86 -0
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +119 -0
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +103 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +76 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +82 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +89 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +124 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +91 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +71 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +70 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +73 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +98 -0
- package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +278 -0
- package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +94 -0
- package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +43 -0
- package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +66 -0
- package/skills/wix-app/references/auto-patterns-dashboard/views.md +76 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -14
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +54 -29
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +2 -2
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +17 -105
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +127 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +2 -1
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +15 -15
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +25 -53
- package/skills/wix-app/scripts/generate-auto-patterns.js +510 -0
- package/skills/wix-auth/SKILL.md +12 -0
- package/skills/wix-auth/references/device-flow.md +140 -0
- package/skills/wix-docs/SKILL.md +188 -0
- package/skills/wix-docs/references/API_SPEC_SEARCH.md +213 -0
- package/skills/wix-docs/references/EXTRACTING.md +120 -0
- package/skills/wix-headless/SKILL.md +84 -130
- package/skills/wix-headless/entry/bootstrap.mjs +93 -0
- package/skills/wix-headless/entry/skill.md +78 -0
- package/skills/wix-headless/references/AI_FEATURES.md +178 -0
- package/skills/wix-headless/references/CAPABILITIES.md +84 -0
- package/skills/wix-headless/references/DISCOVERY.md +34 -56
- package/skills/wix-headless/references/DOC_DISCOVERY.md +37 -0
- package/skills/wix-headless/references/FEEDBACK.md +118 -0
- package/skills/wix-headless/references/IMAGE_GENERATION.md +69 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +97 -0
- package/skills/wix-headless/references/SEED.md +44 -188
- package/skills/wix-headless/references/SETUP.md +32 -103
- package/skills/wix-headless/references/astro.md +98 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +196 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +236 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +279 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +163 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +203 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +222 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +124 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +160 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +118 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +132 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +124 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +188 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +173 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +161 -0
- package/skills/wix-headless/references/inline-recipes/setup-blog.md +175 -0
- package/skills/wix-headless/references/inline-recipes/setup-bookings.md +228 -0
- package/skills/wix-headless/references/inline-recipes/setup-cms.md +200 -0
- package/skills/wix-headless/references/inline-recipes/setup-events.md +196 -0
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +174 -0
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +176 -0
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +153 -0
- package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +229 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +161 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +117 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +152 -0
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +57 -0
- package/skills/wix-headless/references/managed/CONNECT.md +74 -0
- package/skills/wix-headless/references/managed/CREATE.md +130 -0
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +57 -0
- package/skills/wix-headless/references/non-astro.md +59 -0
- package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +22 -0
- package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +7 -0
- package/skills/wix-headless/references/stripe/AUTHENTICATION.md +55 -0
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +63 -0
- package/skills/wix-manage/SKILL.md +24 -1
- package/skills/wix-manage/references/analytics/query-site-analytics.md +241 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +1 -1
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +265 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +436 -0
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +209 -0
- package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +249 -0
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +51 -0
- package/skills/wix-vibe-headless/SKILL.md +136 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +128 -0
- package/skills/wix-vibe-headless/references/blog/wix-blog.js +217 -0
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +153 -0
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-checkout.js +147 -0
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-services.js +215 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +154 -0
- package/skills/wix-vibe-headless/references/cms/wix-cms.js +186 -0
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +142 -0
- package/skills/wix-vibe-headless/references/events/wix-events-browse.js +135 -0
- package/skills/wix-vibe-headless/references/events/wix-events-registration.js +187 -0
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +211 -0
- package/skills/wix-vibe-headless/references/members/wix-members-auth.js +387 -0
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +111 -0
- package/skills/wix-vibe-headless/references/portfolio/wix-portfolio.js +194 -0
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +127 -0
- package/skills/wix-vibe-headless/references/pricing-plans/wix-pricing-plans.js +157 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +138 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-menu.js +224 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-ordering.js +156 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-reservations.js +107 -0
- package/skills/wix-vibe-headless/references/shared/wix-client.js +186 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +155 -0
- package/skills/wix-vibe-headless/references/storefront/wix-store-cart.js +147 -0
- package/skills/wix-vibe-headless/references/storefront/wix-store-catalog.js +142 -0
- package/skills/wix-headless/references/BUILD-astro.md +0 -253
- package/skills/wix-headless/references/BUILD-own-build.md +0 -167
- package/skills/wix-headless/references/BUILD.md +0 -105
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +0 -165
- package/skills/wix-headless/references/DISCOVERY-connect.md +0 -55
- package/skills/wix-headless/references/DISCOVERY-create.md +0 -238
- package/skills/wix-headless/references/PLAN-connect.md +0 -19
- package/skills/wix-headless/references/PLAN-create.md +0 -17
- package/skills/wix-headless/references/PLAN.md +0 -92
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +0 -300
- package/skills/wix-headless/references/astro/blog/BLOG_SETUP.md +0 -218
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +0 -45
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +0 -77
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +0 -214
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +0 -231
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +0 -453
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +0 -354
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +0 -299
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +0 -351
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +0 -119
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +0 -103
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +0 -132
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +0 -463
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +0 -119
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +0 -129
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +0 -178
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +0 -85
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +0 -129
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +0 -229
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +0 -215
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +0 -165
- package/skills/wix-headless/references/astro/templates/Layout.astro +0 -132
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +0 -41
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +0 -374
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +0 -136
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +0 -25
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +0 -71
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +0 -54
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +0 -49
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +0 -245
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +0 -424
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +0 -176
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +0 -181
- package/skills/wix-headless/references/astro/templates/ecom/CartBadge.tsx +0 -84
- package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +0 -543
- package/skills/wix-headless/references/astro/templates/ecom/cart.astro +0 -13
- package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +0 -302
- package/skills/wix-headless/references/astro/templates/ecom/discounts.ts +0 -71
- package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +0 -24
- package/skills/wix-headless/references/astro/templates/gift-cards/GiftCardPurchase.tsx +0 -172
- package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +0 -42
- package/skills/wix-headless/references/astro/templates/gift-cards/_nav-snippet.astro +0 -11
- package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +0 -220
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +0 -51
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.ts +0 -75
- package/skills/wix-headless/references/astro/templates/global.css +0 -288
- package/skills/wix-headless/references/astro/templates/stores/AddToCartButton.tsx +0 -77
- package/skills/wix-headless/references/astro/templates/stores/BackInStockForm.tsx +0 -176
- package/skills/wix-headless/references/astro/templates/stores/CategoryRail.astro +0 -69
- package/skills/wix-headless/references/astro/templates/stores/ProductCard.astro +0 -79
- package/skills/wix-headless/references/astro/templates/stores/ProductPurchase.tsx +0 -397
- package/skills/wix-headless/references/astro/templates/stores/SeoTags.astro +0 -21
- package/skills/wix-headless/references/astro/templates/stores/back-in-stock.ts +0 -67
- package/skills/wix-headless/references/astro/templates/stores/categories.ts +0 -155
- package/skills/wix-headless/references/astro/templates/stores/category/[slug].astro +0 -130
- package/skills/wix-headless/references/astro/templates/stores/components-stores.css +0 -400
- package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +0 -128
- package/skills/wix-headless/references/astro/templates/stores/products/index.astro +0 -101
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +0 -161
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +0 -32
- package/skills/wix-headless/references/bookings/FLOW.md +0 -246
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +0 -95
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +0 -355
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +0 -176
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +0 -71
- package/skills/wix-headless/references/commands/known-apps.json +0 -6
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +0 -142
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +0 -88
- package/skills/wix-headless/references/custom/blog/WIRING.md +0 -52
- package/skills/wix-headless/references/custom/bookings/WIRING.md +0 -197
- package/skills/wix-headless/references/custom/cms/WIRING.md +0 -97
- package/skills/wix-headless/references/custom/ecom/WIRING.md +0 -67
- package/skills/wix-headless/references/custom/forms/WIRING.md +0 -101
- package/skills/wix-headless/references/custom/stores/WIRING.md +0 -80
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +0 -59
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +0 -43
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +0 -51
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +0 -357
- package/skills/wix-headless/references/seed-recipes.md +0 -49
- package/skills/wix-headless/references/shared/AGENTS_MD.md +0 -47
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +0 -108
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +0 -8
- package/skills/wix-headless/references/shared/DESIGN_MD.md +0 -84
- package/skills/wix-headless/references/shared/DOCS_SEARCH.md +0 -41
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +0 -186
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +0 -206
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +0 -97
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +0 -143
- package/skills/wix-headless/references/shared/STYLING.md +0 -140
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +0 -126
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +0 -252
- package/skills/wix-headless/references/verticals/_schema.json +0 -74
- package/skills/wix-headless/references/verticals/_schema.md +0 -95
- package/skills/wix-headless/references/verticals/blog.md +0 -31
- package/skills/wix-headless/references/verticals/bookings.md +0 -41
- package/skills/wix-headless/references/verticals/cms.md +0 -30
- package/skills/wix-headless/references/verticals/ecom.md +0 -25
- package/skills/wix-headless/references/verticals/forms.md +0 -26
- package/skills/wix-headless/references/verticals/gift-cards.md +0 -24
- package/skills/wix-headless/references/verticals/stores.md +0 -33
- package/skills/wix-headless/scripts/check-manifest.mjs +0 -335
- package/skills/wix-headless/scripts/compose.mjs +0 -520
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -157
- package/skills/wix-headless/scripts/flatten.sh +0 -61
- package/skills/wix-headless/scripts/generate-images.mjs +0 -242
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +0 -154
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +0 -190
- package/skills/wix-headless/scripts/scaffold.sh +0 -139
- package/skills/wix-headless/scripts/seed-utilities.sh +0 -93
- package/skills/wix-headless/scripts/write-decorative-json.mjs +0 -62
- package/skills/wix-headless/shared-utilities/analytics.ts +0 -40
- package/skills/wix-headless/shared-utilities/ricos.ts +0 -179
- package/skills/wix-headless/shared-utilities/wix-image.ts +0 -72
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST version of the Wix headless integration — the direct analog of
|
|
3
|
+
* `storefrontApiRequest` in src/lib/shopify.ts. Plain fetch, JSON in / JSON out.
|
|
4
|
+
* No SDK, no query builder, no Velo.
|
|
5
|
+
*
|
|
6
|
+
* WIX_CLIENT_ID is the one thing to set. The client exchanges it for a visitor
|
|
7
|
+
* access token via the OAuth2 token endpoint, then sends that token on every API call.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The public headless OAuth client id. It is a buyer-facing credential (it only mints
|
|
12
|
+
* anonymous visitor tokens), NOT a secret — so it is safe to hardcode here. Paste the
|
|
13
|
+
* value from the Wix Business Manager prompt in place of the placeholder.
|
|
14
|
+
*/
|
|
15
|
+
export const WIX_CLIENT_ID = "<YOUR-CLIENT-ID>";
|
|
16
|
+
|
|
17
|
+
export const WIX_API_BASE = "https://www.wixapis.com";
|
|
18
|
+
const OAUTH_TOKEN_URL = `${WIX_API_BASE}/oauth2/token`;
|
|
19
|
+
|
|
20
|
+
// Scope the storage key by client id so two headless sites served from the same
|
|
21
|
+
// origin (e.g. localhost:4321 across projects) don't share one visitor token —
|
|
22
|
+
// which would load site A's token for site B and mix up carts/identity.
|
|
23
|
+
const TOKEN_STORAGE_KEY = `wix-visitor-token-${WIX_CLIENT_ID}`;
|
|
24
|
+
let tokenCache = null;
|
|
25
|
+
|
|
26
|
+
function loadToken() {
|
|
27
|
+
if (tokenCache) return tokenCache;
|
|
28
|
+
if (typeof window === "undefined") return null;
|
|
29
|
+
try {
|
|
30
|
+
const raw = window.localStorage.getItem(TOKEN_STORAGE_KEY);
|
|
31
|
+
if (raw) tokenCache = JSON.parse(raw);
|
|
32
|
+
} catch {
|
|
33
|
+
/* ignore disabled/full storage */
|
|
34
|
+
}
|
|
35
|
+
return tokenCache;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveToken(t) {
|
|
39
|
+
tokenCache = t;
|
|
40
|
+
if (typeof window === "undefined") return;
|
|
41
|
+
try {
|
|
42
|
+
window.localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify(t));
|
|
43
|
+
} catch {
|
|
44
|
+
/* ignore */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function mintToken(body) {
|
|
49
|
+
const res = await fetch(OAUTH_TOKEN_URL, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
|
+
body: JSON.stringify(body),
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) throw new Error(`Wix OAuth failed: ${res.status}`);
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
return {
|
|
57
|
+
accessToken: data.access_token,
|
|
58
|
+
refreshToken: data.refresh_token,
|
|
59
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Log a member in on THIS client by swapping the persisted token set for the
|
|
65
|
+
* member's tokens (see references/members/). Member tokens are the SAME shape as
|
|
66
|
+
* visitor tokens and refresh via the same `refresh_token` grant — logging in just
|
|
67
|
+
* replaces the set the client already carries, so EVERY subsequent `wixApiRequest`
|
|
68
|
+
* (cart, orders, bookings, "my …" reads) now runs as the member. Called by the
|
|
69
|
+
* members helper after a successful credential / social / SSO login.
|
|
70
|
+
*
|
|
71
|
+
* @param {{ accessToken: string, refreshToken: string, expiresIn: number }} tokens
|
|
72
|
+
*/
|
|
73
|
+
export function setSessionTokens({ accessToken, refreshToken, expiresIn }) {
|
|
74
|
+
saveToken({
|
|
75
|
+
accessToken,
|
|
76
|
+
refreshToken,
|
|
77
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
78
|
+
role: "member",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Drop the persisted session so the next call mints a FRESH anonymous visitor.
|
|
84
|
+
* Call on logout — otherwise the dead member token lingers and reads fail.
|
|
85
|
+
*/
|
|
86
|
+
export function clearSession() {
|
|
87
|
+
tokenCache = null;
|
|
88
|
+
if (typeof window === "undefined") return;
|
|
89
|
+
try {
|
|
90
|
+
window.localStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
91
|
+
} catch {
|
|
92
|
+
/* ignore */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* True once a member is logged in on this client (vs. an anonymous visitor).
|
|
98
|
+
* Gate account UI on this, or on the members helper's `isLoggedIn()`.
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
export function isMember() {
|
|
102
|
+
return loadToken()?.role === "member";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a visitor access token from the client id.
|
|
107
|
+
*
|
|
108
|
+
* The visitor token IS the identity of the Wix "current cart", so we persist the
|
|
109
|
+
* refresh token to localStorage and REFRESH it on expiry — re-minting a fresh
|
|
110
|
+
* `anonymous` token would create a NEW visitor and silently empty the cart on
|
|
111
|
+
* every reload / after the 4h token lifetime. Anonymous mint happens only once
|
|
112
|
+
* (or when no refresh token is stored).
|
|
113
|
+
*/
|
|
114
|
+
async function getAccessToken() {
|
|
115
|
+
const cached = loadToken();
|
|
116
|
+
if (cached && cached.expiresAt > Date.now() + 60_000) return cached.accessToken;
|
|
117
|
+
|
|
118
|
+
if (cached?.refreshToken) {
|
|
119
|
+
try {
|
|
120
|
+
const refreshed = await mintToken({ clientId: WIX_CLIENT_ID, grantType: "refresh_token", refreshToken: cached.refreshToken });
|
|
121
|
+
// Refreshing preserves identity: a member refresh token yields member tokens.
|
|
122
|
+
refreshed.role = cached.role || "visitor";
|
|
123
|
+
saveToken(refreshed);
|
|
124
|
+
return refreshed.accessToken;
|
|
125
|
+
} catch {
|
|
126
|
+
/* refresh failed — fall through to a fresh anonymous visitor */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const fresh = await mintToken({ clientId: WIX_CLIENT_ID, grantType: "anonymous" });
|
|
130
|
+
fresh.role = "visitor";
|
|
131
|
+
saveToken(fresh);
|
|
132
|
+
return fresh.accessToken;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Core transport — mirrors storefrontApiRequest. Adds the Authorization header,
|
|
137
|
+
* resolves the path against the Wix API base, parses JSON, surfaces errors.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} path
|
|
140
|
+
* @param {{ method?: "GET"|"POST"|"PUT"|"DELETE", body?: unknown, query?: Record<string, string | undefined> }} [options]
|
|
141
|
+
*/
|
|
142
|
+
export async function wixApiRequest(path, options = {}) {
|
|
143
|
+
const { method = "POST", body, query } = options;
|
|
144
|
+
const token = await getAccessToken();
|
|
145
|
+
|
|
146
|
+
const url = new URL(path.startsWith("http") ? path : `${WIX_API_BASE}${path}`);
|
|
147
|
+
if (query) {
|
|
148
|
+
for (const [k, v] of Object.entries(query)) {
|
|
149
|
+
if (v === undefined) continue;
|
|
150
|
+
if (Array.isArray(v)) {
|
|
151
|
+
for (const item of v) url.searchParams.append(k, item);
|
|
152
|
+
} else {
|
|
153
|
+
url.searchParams.set(k, v);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const res = await fetch(url.toString(), {
|
|
159
|
+
method,
|
|
160
|
+
headers: {
|
|
161
|
+
"Content-Type": "application/json",
|
|
162
|
+
Authorization: token, // Wix expects the raw access token (no "Bearer " prefix)
|
|
163
|
+
},
|
|
164
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (res.status === 402) {
|
|
168
|
+
// This API requires an active plan / premium feature on the site.
|
|
169
|
+
console.warn("Wix: Payment required (402) — this API needs an active plan/premium feature.");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!res.ok) {
|
|
173
|
+
const text = await res.text().catch(() => "");
|
|
174
|
+
let parsed;
|
|
175
|
+
try { parsed = JSON.parse(text); } catch { parsed = text; }
|
|
176
|
+
// Attach status + parsed body so callers (e.g. the members helper) can map a
|
|
177
|
+
// meaningful auth outcome (404 = bad credentials, 409 = email exists) to a
|
|
178
|
+
// friendly message instead of parsing the message string.
|
|
179
|
+
const err = new Error(`Wix API error ${res.status}: ${text}`);
|
|
180
|
+
err.status = res.status;
|
|
181
|
+
err.body = parsed;
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
if (res.status === 204) return undefined;
|
|
185
|
+
return await res.json();
|
|
186
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
|
|
2
|
+
# Wix Storefront Skill
|
|
3
|
+
|
|
4
|
+
> **Source files (in this skill):** the shared transport `references/shared/wix-client.js` and both storefront helpers from `references/storefront/`. All helpers import from `"./wix-client.js"`, so copy them into the same folder (e.g. `src/rest/`). Copy **both** for a full storefront:
|
|
5
|
+
>
|
|
6
|
+
> | File | What it covers |
|
|
7
|
+
> |---|---|
|
|
8
|
+
> | `wix-store-catalog.js` | Products, categories, product detail, search |
|
|
9
|
+
> | `wix-store-cart.js` | Add to cart, cart management, checkout |
|
|
10
|
+
|
|
11
|
+
Builds a real, client-only Wix storefront. The browser talks to Wix directly over a
|
|
12
|
+
public `WIX_CLIENT_ID`. Never mock products; never hand-build `/checkout` URLs — always
|
|
13
|
+
go through the eCom cart + redirect-session.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
- User wants a Wix eCommerce store or asks to "connect Wix".
|
|
17
|
+
- Replacing placeholder/mock products with live Wix data.
|
|
18
|
+
- Adding cart, checkout, categories, or product detail pages over an existing Wix Stores catalog.
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
1. A Wix site with **Wix Stores installed and products already added** (this skill does
|
|
22
|
+
NOT provision — it's read-only over the catalog).
|
|
23
|
+
2. The site's public headless **`WIX_CLIENT_ID`**, provided in the handoff prompt (the
|
|
24
|
+
Wix Business Manager surfaces a copyable prompt with the id filled in — see
|
|
25
|
+
the router `SKILL.md`). Paste it into `src/rest/wix-client.js` in place of the placeholder. It is a
|
|
26
|
+
buyer-facing credential (it only mints anonymous visitor tokens), **not** a secret, so
|
|
27
|
+
hardcoding/committing it is fine.
|
|
28
|
+
3. The deployed app domain must be allow-listed on the OAuth client for Wix-hosted
|
|
29
|
+
checkout to return. This is a **separate Wix setup flow the user completes later** —
|
|
30
|
+
out of this skill's scope. If checkout return fails before that setup is done, that's
|
|
31
|
+
expected; flag it and continue.
|
|
32
|
+
|
|
33
|
+
## The API (copy as-is; do not re-derive it)
|
|
34
|
+
This skill ships only the REST layer — no UI components. Build the storefront's UI
|
|
35
|
+
however the project wants; wire it to these two snippets. Copy them into the app (e.g.
|
|
36
|
+
`src/api/`) and only adjust import paths:
|
|
37
|
+
- `src/rest/wix-client.js` — visitor-token mint/refresh + transport. Set `WIX_CLIENT_ID` to
|
|
38
|
+
the id from the prompt (replace the `<YOUR-CLIENT-ID>` placeholder). The visitor refresh
|
|
39
|
+
token IS the cart identity; it is persisted to localStorage. Do not re-mint anonymously
|
|
40
|
+
per load or the cart silently empties.
|
|
41
|
+
- `src/rest/wix-store-catalog.js` — **Catalog:**
|
|
42
|
+
`queryProducts`, `queryProductsByCategory`, `getProductBySlug`, `countProducts`,
|
|
43
|
+
`queryCategories`, `getCategoryBySlug`
|
|
44
|
+
- `src/rest/wix-store-cart.js` — **Cart & checkout:**
|
|
45
|
+
`addToCart`, `getCurrentCart`, `updateCartItemQuantity`, `removeFromCart`, `checkout`
|
|
46
|
+
|
|
47
|
+
The Product and Cart shapes are documented as JSDoc comments at the top of each helper file.
|
|
48
|
+
Read them before building the UI — they describe the key fields and link to the full API
|
|
49
|
+
reference for anything not shown.
|
|
50
|
+
|
|
51
|
+
## How to wire it (UI is the project's choice)
|
|
52
|
+
- **Product grid** — `queryProducts()` for the listing (visible products only); pass
|
|
53
|
+
`nextCursor` back as `cursor` to load the next page. Render fields directly from the Wix
|
|
54
|
+
product object (see the `Product` typedef in `wix-store.js` for key fields). For price, use
|
|
55
|
+
`actualPriceRange.minValue.formattedAmount` (already includes the currency symbol) — no
|
|
56
|
+
manual formatting needed.
|
|
57
|
+
- **PDP** — `getProductBySlug(slug)` keyed off the URL slug; returns null on miss — show
|
|
58
|
+
a not-found state, never invent a product. **Drive the whole PDP from the returned product
|
|
59
|
+
object at runtime — build it generically, not around the one product you happened to inspect.**
|
|
60
|
+
A catalog is heterogeneous: some products have `options`, some have `modifiers` (mandatory or
|
|
61
|
+
optional), some track inventory, some none of these. Render a selector for **every** entry the
|
|
62
|
+
product actually carries: one control per `product.options` (variant choices) **and** one per
|
|
63
|
+
`product.modifiers` (TEXT_CHOICES → choice buttons/select; FREE_TEXT → a text input); render
|
|
64
|
+
neither when the arrays are empty. Skipping modifiers is a common miss — a product with a
|
|
65
|
+
**mandatory** modifier (e.g. "gift wrap?") whose control isn't rendered can never be added: the
|
|
66
|
+
buyer can't satisfy the requirement, so `add-to-cart` returns 200 with an **empty** `lineItems`
|
|
67
|
+
and the add silently no-ops.
|
|
68
|
+
- **Gate the Add-to-cart button** — disable it only until the requirements the product *actually
|
|
69
|
+
has* are met, computed from the product object (never assume every product has options or
|
|
70
|
+
modifiers): if `product.options` is non-empty, a variant must resolve from the selections; every
|
|
71
|
+
`modifier.mandatory === true` must have a value. A product with no options and no mandatory
|
|
72
|
+
modifiers is immediately addable — don't leave the button stuck. Optional modifiers never block.
|
|
73
|
+
Then pass the selections to `addToCart` (see Cart below); never call it with a required selection missing.
|
|
74
|
+
- **Reflect stock in the UI** — the product object already carries availability at three levels; surface
|
|
75
|
+
it rather than letting the buyer discover it only on click. Read it from the data at runtime (never
|
|
76
|
+
hardcode):
|
|
77
|
+
- **Grid / card:** `product.inventory.availabilityStatus` (`IN_STOCK` / `OUT_OF_STOCK` /
|
|
78
|
+
`PARTIALLY_OUT_OF_STOCK`) — badge an out-of-stock product as sold out.
|
|
79
|
+
- **Option choice:** `product.options[].choicesSettings.choices[].inStock` — disable/strike a choice
|
|
80
|
+
(e.g. size L) that has no in-stock variant, before a full variant is even resolved.
|
|
81
|
+
- **Variant:** `variantsInfo.variants[].inventoryStatus.inStock` — once selections resolve to a
|
|
82
|
+
variant, disable Add-to-cart (label "Out of stock") when that variant is `inStock: false`.
|
|
83
|
+
A product/variant with inventory tracking **off** reports `availabilityStatus: IN_STOCK` /
|
|
84
|
+
`inStock: true` and stays freely addable — tracking-off is not "no data", it's "always available".
|
|
85
|
+
`addToCart` still throws on a sold-out line as a backstop, but the UI should prevent reaching it.
|
|
86
|
+
- **Categories** — `queryCategories()` for a category menu; `getCategoryBySlug(slug)` for
|
|
87
|
+
a category landing page. Pass `category.id` to `queryProductsByCategory(categoryId, { limit?, cursor? })`
|
|
88
|
+
to list only the products in that category; paginate exactly like `queryProducts`.
|
|
89
|
+
- **Cart** — `addToCart(catalogItemId, variantId?, qty?, { modifierChoices?, customTextFields? }?)`,
|
|
90
|
+
`updateCartItemQuantity(lineItemId, qty)`, `removeFromCart(lineItemId)`.
|
|
91
|
+
- `variantId` (`variantsInfo.variants[].id` from `getProductBySlug`) — required for products with
|
|
92
|
+
options; resolve it by matching the buyer's selections to `variant.choices[].optionChoiceIds`.
|
|
93
|
+
- `modifierChoices` — `{ [modifier.key]: choiceKey }` for `TEXT_CHOICES` modifiers.
|
|
94
|
+
- `customTextFields` — `{ [modifier.freeTextSettings.key]: userInput }` for `FREE_TEXT` modifiers.
|
|
95
|
+
Mandatory modifiers must be included. See the eCommerce integration guide:
|
|
96
|
+
https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md
|
|
97
|
+
- Use `cart.lineItems[].id` as `lineItemId` (not `catalogItemId`) for mutations.
|
|
98
|
+
- Read the cart back with `getCurrentCart()` rather than mirroring it locally.
|
|
99
|
+
- **Checkout** — `window.location.href = await checkout()`. After the buyer returns from
|
|
100
|
+
hosted checkout the order is placed and the cart is empty — re-fetch with
|
|
101
|
+
`getCurrentCart()` on return (e.g. on mount + `visibilitychange`) to clear the UI.
|
|
102
|
+
- **Empty state** — if `countProducts()` is 0, show an empty state telling the user to
|
|
103
|
+
add products in their Wix dashboard. Never invent products.
|
|
104
|
+
|
|
105
|
+
## Hard rules (do not violate)
|
|
106
|
+
- ✅ Checkout ONLY via `checkout()` (`create-checkout` → `/headless/v1/redirect-session`
|
|
107
|
+
`fullUrl`), then redirect.
|
|
108
|
+
- ❌ Never hand-build `/checkout`, cart-add, or product permalinks for purchase.
|
|
109
|
+
- ❌ Never mock products — render live Wix data or the empty state.
|
|
110
|
+
- ❌ Never generate fake reviews, ratings, or testimonials. Empty review UI only.
|
|
111
|
+
- ✅ Set `WIX_CLIENT_ID` from the prompt's value (public client id — safe to hardcode).
|
|
112
|
+
- ✅ `lineItemId` for cart mutations is `cart.lineItems[].id`, not `catalogItemId`.
|
|
113
|
+
- ✅ On the PDP, render a control for **every** `product.options` entry **and** every `product.modifiers`
|
|
114
|
+
entry — never only variants. Keep Add-to-cart disabled until a variant resolves and every
|
|
115
|
+
`modifier.mandatory === true` has a value; a mandatory modifier with no rendered control makes the
|
|
116
|
+
product unbuyable (add-to-cart returns 200 with empty `lineItems`).
|
|
117
|
+
- ✅ Pass `addToCart`'s `variantId` (`variantsInfo.variants[].id`) for products with variants; omit for products without.
|
|
118
|
+
- ✅ Pass `modifierChoices` (`{ [modifier.key]: choiceKey }`) for TEXT_CHOICES modifiers; pass `customTextFields`
|
|
119
|
+
(`{ [modifier.freeTextSettings.key]: userInput }`) for FREE_TEXT modifiers. Include mandatory modifiers.
|
|
120
|
+
- The engine fails loudly on purpose: `addToCart`/`checkout` throw on out-of-stock or
|
|
121
|
+
empty carts. A green path means it is really buyable — don't swallow these.
|
|
122
|
+
|
|
123
|
+
## Beyond the snippets
|
|
124
|
+
The snippets cover the common storefront paths. If you hit a use case they don't cover
|
|
125
|
+
(e.g. coupons, members/auth, a product field not shown in the typedef), make the call
|
|
126
|
+
yourself with `wixApiRequest` — but look up the exact endpoint, HTTP method, and request
|
|
127
|
+
body in the **official Wix API reference** first; never guess:
|
|
128
|
+
- Official Wix API reference: https://dev.wix.com/docs/api-reference.md
|
|
129
|
+
- eCommerce integration guide (modifiers, custom text, variants): https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md
|
|
130
|
+
- Member login + a "my orders" account view → the **members** vertical (`references/members/INSTRUCTIONS.md`): custom login on your own UI so buyers can sign in and see their account.
|
|
131
|
+
|
|
132
|
+
Keep the snippets as the default for everything they already do; reach for the API
|
|
133
|
+
reference only for the gap.
|
|
134
|
+
|
|
135
|
+
## Point the user to their dashboard
|
|
136
|
+
In some cases, users need to access the Wix dashboard in order to edit the store content for their site. To facilitate this, provide the user with deep links directly to the relevant dashboard pages. For store data those pages are:
|
|
137
|
+
- **Products** — `https://manage.wix.com/dashboard/{metaSiteId}/wix-stores/products` (`Dashboard → Store → Products`; add/edit products, variants, inventory)
|
|
138
|
+
- **Categories** — `https://manage.wix.com/dashboard/{metaSiteId}/wix-stores/categories/list` (`Dashboard → Store → Categories`; organize products into the category menu)
|
|
139
|
+
|
|
140
|
+
Substitute the site's `metaSiteId` to complete the links (you have it from the handoff / `ListWixSites`). Include the in-dashboard navigation as a fallback.
|
|
141
|
+
|
|
142
|
+
## Verification checklist (before declaring done)
|
|
143
|
+
- [ ] `WIX_CLIENT_ID` set to the prompt's value (not the `<YOUR-CLIENT-ID>` placeholder)
|
|
144
|
+
- [ ] Visitor token persists across reload (cart survives reload, same visitor)
|
|
145
|
+
- [ ] Every product choice renders on the PDP — variant options **and** modifiers (mandatory ones included)
|
|
146
|
+
- [ ] Add-to-cart button stays disabled until all required choices are made (variant + mandatory modifiers)
|
|
147
|
+
- [ ] A product with a mandatory modifier adds successfully (its selection is sent, cart line appears)
|
|
148
|
+
- [ ] Stock reflected in the UI — sold-out product badged (grid), out-of-stock option choices and variants disabled/labelled (PDP)
|
|
149
|
+
- [ ] Add to cart works; out-of-stock items throw rather than add a dead line
|
|
150
|
+
- [ ] Quantity update / remove reflect in `getCurrentCart()`
|
|
151
|
+
- [ ] Checkout redirects via redirect-session `fullUrl` (no hand-built URL)
|
|
152
|
+
- [ ] Cart re-fetched on return from checkout (clears once the order is placed)
|
|
153
|
+
- [ ] Empty state shown when `countProducts()` is 0
|
|
154
|
+
- [ ] No mock products anywhere
|
|
155
|
+
- [ ] Told the user at least once that they can continue setting up their store in the dashboard and provided deep links.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { wixApiRequest } from "./wix-client.js";
|
|
2
|
+
|
|
3
|
+
// Data model reference: see INSTRUCTIONS.md
|
|
4
|
+
// Product shape (for addToCart): see wix-store-catalog.js
|
|
5
|
+
|
|
6
|
+
// Stores app id — required inside catalogReference for store products.
|
|
7
|
+
const STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wix eCom Cart — key fields for building a cart UI.
|
|
11
|
+
* Full model: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/get-cart.md
|
|
12
|
+
*
|
|
13
|
+
* id {string}, currency {string},
|
|
14
|
+
* lineItems[].id {string} — lineItemId for update/remove (NOT catalogItemId),
|
|
15
|
+
* lineItems[].quantity {number},
|
|
16
|
+
* lineItems[].catalogReference.catalogItemId {string},
|
|
17
|
+
* lineItems[].productName.original {string},
|
|
18
|
+
* lineItems[].price.formattedAmount {string} — after discounts with currency symbol,
|
|
19
|
+
* lineItems[].fullPrice.formattedAmount {string} — before discount (strikethrough),
|
|
20
|
+
* lineItems[].descriptionLines {array} — human-readable option/modifier labels:
|
|
21
|
+
* [{ name: { original }, plainText: { original } OR colorInfo: { original, code } }],
|
|
22
|
+
* lineItems[].image.url {string},
|
|
23
|
+
* lineItems[].availability.status {string} — "AVAILABLE"|"NOT_AVAILABLE"|"PARTIALLY_AVAILABLE"|"NOT_FOUND"
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Add a product to the visitor's current cart.
|
|
28
|
+
*
|
|
29
|
+
* For products with options (variants), pass the chosen variantId — resolve it from
|
|
30
|
+
* product.variantsInfo.variants (from getProductBySlug) by matching the buyer's selected
|
|
31
|
+
* option choices to variant.choices[].optionChoiceIds.
|
|
32
|
+
*
|
|
33
|
+
* For products with modifiers:
|
|
34
|
+
* - TEXT_CHOICES: pass modifierChoices: { [modifier.key]: choiceKey }
|
|
35
|
+
* - FREE_TEXT: pass customTextFields: { [modifier.freeTextSettings.key]: userInput }
|
|
36
|
+
* Mandatory modifiers (modifier.mandatory === true) MUST be included.
|
|
37
|
+
*
|
|
38
|
+
* Throws on out-of-stock so the buyer can't reach checkout with an unbuyable line.
|
|
39
|
+
* Full catalogReference reference: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md
|
|
40
|
+
*
|
|
41
|
+
* @param {string} catalogItemId Product GUID (product.id).
|
|
42
|
+
* @param {string} [variantId] variantsInfo.variants[].id — required for products with variants.
|
|
43
|
+
* @param {number} [quantity]
|
|
44
|
+
* @param {{ modifierChoices?: Record<string,string>, customTextFields?: Record<string,string> }} [extras]
|
|
45
|
+
* @returns {Promise<object>} Updated cart.
|
|
46
|
+
*/
|
|
47
|
+
export async function addToCart(catalogItemId, variantId, quantity = 1, { modifierChoices, customTextFields } = {}) {
|
|
48
|
+
const catalogReferenceOptions = {};
|
|
49
|
+
if (variantId) catalogReferenceOptions.variantId = variantId;
|
|
50
|
+
if (modifierChoices && Object.keys(modifierChoices).length) catalogReferenceOptions.options = modifierChoices;
|
|
51
|
+
if (customTextFields && Object.keys(customTextFields).length) catalogReferenceOptions.customTextFields = customTextFields;
|
|
52
|
+
|
|
53
|
+
const catalogReference = { appId: STORES_APP_ID, catalogItemId };
|
|
54
|
+
if (Object.keys(catalogReferenceOptions).length) catalogReference.options = catalogReferenceOptions;
|
|
55
|
+
const res = await wixApiRequest("/ecom/v1/carts/current/add-to-cart", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: { lineItems: [{ catalogReference, quantity }] },
|
|
58
|
+
});
|
|
59
|
+
const line = (res?.cart?.lineItems ?? []).find(
|
|
60
|
+
(l) => l.catalogReference?.catalogItemId === catalogItemId && (!variantId || l.catalogReference?.options?.variantId === variantId),
|
|
61
|
+
);
|
|
62
|
+
// Wix returns 200 even when the line is silently rejected — guard both signals:
|
|
63
|
+
// 1. availability.status set to something other than AVAILABLE
|
|
64
|
+
// 2. no matching line at all (quantity 0 / line absent)
|
|
65
|
+
if (line?.availability?.status && line.availability.status !== "AVAILABLE") {
|
|
66
|
+
throw new Error(`Item not available for sale (status: ${line.availability.status}). Is it in stock?`);
|
|
67
|
+
}
|
|
68
|
+
if (!line || line.quantity === 0) {
|
|
69
|
+
// A missing line usually means a required selection wasn't sent — a mandatory modifier
|
|
70
|
+
// (pass modifierChoices/customTextFields) or the variantId for a product with options —
|
|
71
|
+
// not necessarily out of stock. Verify every required choice is included in this call.
|
|
72
|
+
throw new Error(
|
|
73
|
+
"Item could not be added to the cart. Check that every required selection was sent: " +
|
|
74
|
+
"the variantId for a product with options, and all mandatory modifiers " +
|
|
75
|
+
"(modifierChoices for TEXT_CHOICES, customTextFields for FREE_TEXT). It may also be out of stock.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return res?.cart;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Read the visitor's current cart. Returns null if no cart exists yet. */
|
|
82
|
+
export async function getCurrentCart() {
|
|
83
|
+
try {
|
|
84
|
+
const res = await wixApiRequest("/ecom/v1/carts/current", { method: "GET" });
|
|
85
|
+
return res?.cart ?? null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a checkout from the current cart and return the hosted checkout URL.
|
|
93
|
+
* Throws on empty cart, unavailable lines, or a missing redirect URL.
|
|
94
|
+
* Usage: window.location.href = await checkout()
|
|
95
|
+
* @returns {Promise<string>}
|
|
96
|
+
*/
|
|
97
|
+
export async function checkout() {
|
|
98
|
+
const cart = await getCurrentCart();
|
|
99
|
+
const lines = cart?.lineItems ?? [];
|
|
100
|
+
if (!lines.length) throw new Error("Cannot check out: the cart is empty.");
|
|
101
|
+
const unavailable = lines.filter((l) => l.availability?.status && l.availability.status !== "AVAILABLE");
|
|
102
|
+
if (unavailable.length) {
|
|
103
|
+
const names = unavailable.map((l) => l.productName?.original ?? l.catalogReference?.catalogItemId).join(", ");
|
|
104
|
+
throw new Error(`Cannot check out: ${unavailable.length} item(s) not available — ${names}.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const checkoutRes = await wixApiRequest("/ecom/v1/carts/current/create-checkout", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: { channelType: "WEB" },
|
|
110
|
+
});
|
|
111
|
+
const checkoutId = checkoutRes?.checkoutId;
|
|
112
|
+
if (!checkoutId) throw new Error("Failed to create checkout from the current cart.");
|
|
113
|
+
|
|
114
|
+
const redirect = await wixApiRequest("/headless/v1/redirect-session", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: { ecomCheckout: { checkoutId }, callbacks: { postFlowUrl: window.location.href } },
|
|
117
|
+
});
|
|
118
|
+
const url = redirect?.redirectSession?.fullUrl;
|
|
119
|
+
if (!url) throw new Error("Failed to create the checkout redirect session.");
|
|
120
|
+
return url;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Update the quantity of a cart line. lineItemId is cart.lineItems[].id, not catalogItemId.
|
|
125
|
+
* Wix caps the result at remaining stock — returned quantity may be lower than requested.
|
|
126
|
+
* @returns {Promise<object>} Updated cart.
|
|
127
|
+
*/
|
|
128
|
+
export async function updateCartItemQuantity(lineItemId, quantity) {
|
|
129
|
+
const res = await wixApiRequest("/ecom/v1/carts/current/update-line-items-quantity", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: { lineItems: [{ id: lineItemId, quantity }] },
|
|
132
|
+
});
|
|
133
|
+
return res?.cart;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Remove a line from the current cart by its cart.lineItems[].id.
|
|
138
|
+
* @param {string} lineItemId
|
|
139
|
+
* @returns {Promise<object>} Updated cart.
|
|
140
|
+
*/
|
|
141
|
+
export async function removeFromCart(lineItemId) {
|
|
142
|
+
const res = await wixApiRequest("/ecom/v1/carts/current/remove-line-items", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
body: { lineItemIds: [lineItemId] },
|
|
145
|
+
});
|
|
146
|
+
return res?.cart;
|
|
147
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { wixApiRequest } from "./wix-client.js";
|
|
2
|
+
|
|
3
|
+
// Data model reference: see INSTRUCTIONS.md
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wix Stores V3 Product — key fields for the storefront catalog.
|
|
7
|
+
* Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/query-products.md
|
|
8
|
+
*
|
|
9
|
+
* id {string}, name {string}, slug {string}, visible {boolean}, productType "PHYSICAL"|"DIGITAL",
|
|
10
|
+
* mainCategoryId {string},
|
|
11
|
+
* media.main.image {object} — { id, url, height, width, altText },
|
|
12
|
+
* media.itemsInfo.items {array} — gallery: [{ id, altText, image: { id, url, height, width, altText }, mediaType }],
|
|
13
|
+
* actualPriceRange.minValue.formattedAmount {string} — lowest price with currency symbol,
|
|
14
|
+
* actualPriceRange.maxValue.formattedAmount {string} — highest price with currency symbol,
|
|
15
|
+
* compareAtPriceRange.minValue.formattedAmount {string} — strikethrough price (present when on sale),
|
|
16
|
+
* inventory.availabilityStatus {string} — "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK",
|
|
17
|
+
* options {array} — product options e.g. Size, Color:
|
|
18
|
+
* [{ id, name, optionRenderType "TEXT_CHOICES"|"COLOR_CHOICES"|"SWATCH_CHOICES",
|
|
19
|
+
* choicesSettings.choices [{ choiceId, key, name, inStock, visible, linkedMedia }] }],
|
|
20
|
+
* modifiers {array} — non-variant customizations (engraving, gift wrap):
|
|
21
|
+
* [{ id, name, mandatory, modifierRenderType "TEXT_CHOICES"|"FREE_TEXT",
|
|
22
|
+
* key, choicesSettings.choices, freeTextSettings.key }],
|
|
23
|
+
* plainDescription {string} — HTML product description,
|
|
24
|
+
* variantsInfo.variants {array} — returned only by getProductBySlug:
|
|
25
|
+
* [{ id, visible, choices [{ optionChoiceIds: { optionId, choiceId } }],
|
|
26
|
+
* price: { actualPrice, compareAtPrice }, media, inventoryStatus: { inStock } }]
|
|
27
|
+
* To resolve a buyer's option selections to a variantId: find the variant whose choices
|
|
28
|
+
* match all selected { optionId, choiceId } pairs, then pass variant.id to addToCart.
|
|
29
|
+
*
|
|
30
|
+
* Category: { id, name, slug, visible, description, image, itemCounter, parentCategory.id }
|
|
31
|
+
* Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Query visible products (one page). Pass nextCursor back as cursor to load the next page.
|
|
36
|
+
* @param {{ limit?: number, cursor?: string }} [options]
|
|
37
|
+
* @returns {Promise<{ products: object[], nextCursor: string|null }>}
|
|
38
|
+
*/
|
|
39
|
+
export async function queryProducts({ limit = 100, cursor } = {}) {
|
|
40
|
+
const res = await wixApiRequest("/stores/v3/products/query", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: {
|
|
43
|
+
fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
|
|
44
|
+
query: {
|
|
45
|
+
...(cursor ? {} : { filter: { visible: true } }),
|
|
46
|
+
cursorPaging: cursor ? { limit, cursor } : { limit },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
products: res?.products ?? [],
|
|
52
|
+
nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch a product by its URL slug. Returns null if not found.
|
|
58
|
+
* Returns the full product including variantsInfo.variants (with per-variant media and choices).
|
|
59
|
+
* @param {string} slug
|
|
60
|
+
* @returns {Promise<object|null>}
|
|
61
|
+
*/
|
|
62
|
+
export async function getProductBySlug(slug) {
|
|
63
|
+
const res = await wixApiRequest(`/stores/v3/products/slug/${encodeURIComponent(slug)}`, {
|
|
64
|
+
method: "GET",
|
|
65
|
+
query: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"] },
|
|
66
|
+
});
|
|
67
|
+
return res?.product ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Query visible products belonging to a category (one page).
|
|
72
|
+
* @param {string} categoryId Category GUID from queryCategories / getCategoryBySlug.
|
|
73
|
+
* @param {{ limit?: number, cursor?: string }} [options]
|
|
74
|
+
* @returns {Promise<{ products: object[], nextCursor: string|null }>}
|
|
75
|
+
*/
|
|
76
|
+
export async function queryProductsByCategory(categoryId, { limit = 100, cursor } = {}) {
|
|
77
|
+
const res = await wixApiRequest("/stores/v3/products/search", {
|
|
78
|
+
method: "POST",
|
|
79
|
+
body: {
|
|
80
|
+
fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
|
|
81
|
+
search: {
|
|
82
|
+
...(cursor
|
|
83
|
+
? { cursorPaging: { limit, cursor } }
|
|
84
|
+
: {
|
|
85
|
+
cursorPaging: { limit },
|
|
86
|
+
filter: {
|
|
87
|
+
visible: true,
|
|
88
|
+
"allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] },
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
products: res?.products ?? [],
|
|
96
|
+
nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Total number of visible products. Used for empty-state logic (0 → prompt user to add products).
|
|
102
|
+
* @returns {Promise<number>}
|
|
103
|
+
*/
|
|
104
|
+
export async function countProducts() {
|
|
105
|
+
const res = await wixApiRequest("/stores/v3/products/count", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
body: { filter: { visible: true } },
|
|
108
|
+
});
|
|
109
|
+
return res?.count ?? 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Query Wix Stores categories (one page).
|
|
114
|
+
* @param {{ limit?: number, cursor?: string }} [options]
|
|
115
|
+
* @returns {Promise<{ categories: object[], nextCursor: string|null }>}
|
|
116
|
+
*/
|
|
117
|
+
export async function queryCategories({ limit = 100, cursor } = {}) {
|
|
118
|
+
const res = await wixApiRequest("/categories/v1/categories/query", {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: {
|
|
121
|
+
treeReference: { appNamespace: "@wix/stores", treeKey: null },
|
|
122
|
+
query: { cursorPaging: cursor ? { limit, cursor } : { limit } },
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
categories: res?.categories ?? [],
|
|
127
|
+
nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get a single category by its URL slug. Returns null if not found.
|
|
133
|
+
* @param {string} slug
|
|
134
|
+
* @returns {Promise<object|null>}
|
|
135
|
+
*/
|
|
136
|
+
export async function getCategoryBySlug(slug) {
|
|
137
|
+
const res = await wixApiRequest(`/categories/v1/categories/slug/${encodeURIComponent(slug)}`, {
|
|
138
|
+
method: "GET",
|
|
139
|
+
query: { "treeReference.appNamespace": "@wix/stores" },
|
|
140
|
+
});
|
|
141
|
+
return res?.category ?? null;
|
|
142
|
+
}
|