@wix/agent-skills 1.14.0 → 1.15.1
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 +12 -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/DATA_COLLECTION.md +5 -3
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +25 -9
- package/skills/wix-app/references/SITE_PLUGIN.md +1 -1
- package/skills/wix-app/references/STORES_VERSIONING.md +9 -211
- 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/backend-event/COMMON-EVENTS.md +5 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -14
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +151 -0
- 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/COMPONENT-PREVIEW.md +57 -0
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +23 -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 +18 -16
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +25 -53
- package/skills/wix-app/references/stores/CATEGORIES.md +22 -0
- package/skills/wix-app/references/stores/GET_PRODUCT.md +99 -0
- package/skills/wix-app/references/stores/INVENTORY.md +37 -0
- package/skills/wix-app/references/stores/QUERY.md +87 -0
- package/skills/wix-app/references/stores/WRITE.md +55 -0
- 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 +89 -130
- package/skills/wix-headless/entry/bootstrap.mjs +93 -0
- package/skills/wix-headless/entry/skill.md +76 -0
- package/skills/wix-headless/references/AI_FEATURES.md +178 -0
- package/skills/wix-headless/references/CAPABILITIES.md +86 -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 +129 -0
- package/skills/wix-headless/references/IMAGE_GENERATION.md +69 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +99 -0
- package/skills/wix-headless/references/SEED.md +45 -187
- 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 +213 -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 +232 -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 +192 -0
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +155 -0
- package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +229 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +73 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +197 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +136 -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 +79 -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 +15 -0
- package/skills/wix-headless/references/stripe/AUTHENTICATION.md +55 -0
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +71 -0
- package/skills/wix-manage/SKILL.md +29 -3
- package/skills/wix-manage/references/analytics/query-site-analytics.md +298 -0
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +1 -1
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +6 -5
- package/skills/wix-manage/references/bookings/create-course-service.md +30 -5
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +299 -0
- package/skills/wix-manage/references/cms/cms-schema-management.md +6 -0
- package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +30 -7
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +517 -0
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +218 -0
- package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +249 -0
- package/skills/wix-manage/references/sites/site-import.md +270 -0
- package/skills/wix-manage/references/stores/create-product-from-image.md +8 -6
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +57 -0
- package/skills/wix-vibe-headless/SKILL.md +142 -0
- package/skills/wix-vibe-headless/platforms/README.md +22 -0
- package/skills/wix-vibe-headless/platforms/base44.md +142 -0
- package/skills/wix-vibe-headless/platforms/generic.md +87 -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 +157 -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 +217 -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 +136 -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 +223 -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 +164 -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 +146 -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,387 @@
|
|
|
1
|
+
import {
|
|
2
|
+
wixApiRequest,
|
|
3
|
+
WIX_API_BASE,
|
|
4
|
+
WIX_CLIENT_ID,
|
|
5
|
+
setSessionTokens,
|
|
6
|
+
clearSession,
|
|
7
|
+
isMember,
|
|
8
|
+
} from "./wix-client.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom member login for a client-only headless front end — the REST analog of
|
|
12
|
+
* the SDK `OAuthStrategy` (`register`/`login`/`getMemberTokensForDirectLogin`/
|
|
13
|
+
* `getAuthUrl`/`getMemberTokens`/`logout`). **The front owns its login UI**; the
|
|
14
|
+
* member never bounces to a Wix-hosted login page.
|
|
15
|
+
*
|
|
16
|
+
* Two mechanisms — pick by the brief (a page can use both):
|
|
17
|
+
* (A) DIRECT-CREDENTIAL — your own email+password form. `register()` / `login()`
|
|
18
|
+
* → on SUCCESS an INVISIBLE iframe (`web_message`) mints member tokens. No
|
|
19
|
+
* navigation. Supports custom sign-up fields via `profile`.
|
|
20
|
+
* (B) SOCIAL / SSO — your own "Continue with Google/Facebook" (or custom SSO)
|
|
21
|
+
* button. Full-page redirect to the provider → back to YOUR `/callback` →
|
|
22
|
+
* token exchange. `idp` is always a connection id (see `IDP`).
|
|
23
|
+
*
|
|
24
|
+
* All paths converge on ONE end state: member tokens written onto the shared
|
|
25
|
+
* client via `setSessionTokens` (wix-client.js). Member tokens are the SAME shape
|
|
26
|
+
* as visitor tokens, so EVERY later `wixApiRequest` (cart, orders, "my …" reads)
|
|
27
|
+
* now runs as the member — login just swaps the token set on the client you have.
|
|
28
|
+
*
|
|
29
|
+
* State machine — `register()`/`login()`/`verifyEmail()` return `{ state, ... }`:
|
|
30
|
+
* - "SUCCESS" → logged in; `{ state, member }`.
|
|
31
|
+
* - "REQUIRE_EMAIL_VERIFICATION" → a 6-digit code was emailed; `{ state, stateToken }`.
|
|
32
|
+
* Collect the code, call `verifyEmail(code, stateToken)`.
|
|
33
|
+
* - "REQUIRE_OWNER_APPROVAL" → membership pending owner approval; `{ state }`.
|
|
34
|
+
* Hard failures throw a `MemberAuthError` with a `.code` (invalidCredentials,
|
|
35
|
+
* emailAlreadyExists, …) — surface `.message` in your form.
|
|
36
|
+
*
|
|
37
|
+
* `profile` (register, all optional): firstName, lastName, nickname, picture,
|
|
38
|
+
* language, privacyStatus ("PUBLIC"|"PRIVATE"), labels[], phonesV2[], addresses[],
|
|
39
|
+
* company, position, birthdate, and `customFields` (name→value map). Standard
|
|
40
|
+
* fields work as-is; arbitrary `customFields` keys must first be DEFINED in the
|
|
41
|
+
* Members Area app or they're silently dropped. Social (B) can't collect any of
|
|
42
|
+
* these — that's the reason to pick direct-credential.
|
|
43
|
+
*
|
|
44
|
+
* Docs (curl the .md): custom login <https://dev.wix.com/docs/go-headless/self-managed-headless/authentication/members/custom-login-page/custom-login/custom-login-using-the-js-sdk.md>
|
|
45
|
+
* · external/social login <https://dev.wix.com/docs/rest/business-management/headless-authentication/redirects/create-redirect-session>
|
|
46
|
+
* · Retrieve Tokens <https://dev.wix.com/docs/rest/business-management/headless-authentication/authentication/retrieve-tokens>
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const AUTH_BASE = "/_api/iam/authentication/v2";
|
|
50
|
+
const VERIFY_URL = "/_api/iam/verification/v1/auth/verify";
|
|
51
|
+
const RECOVERY_URL = "/_api/iam/recovery/v1/send-email";
|
|
52
|
+
const REDIRECT_SESSION_URL = "/_api/redirects-api/v1/redirect-session";
|
|
53
|
+
const CURRENT_MEMBER_URL = "/members/v1/members/my";
|
|
54
|
+
const OAUTH_STASH_KEY = `wix-oauth-data-${WIX_CLIENT_ID}`;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `idp` connection ids for social login. Google & Facebook are enabled by default
|
|
58
|
+
* on every headless client. For custom SSO / OIDC (Okta, Auth0, …) pass the
|
|
59
|
+
* connection id of a connection you created in the Business Manager instead.
|
|
60
|
+
*/
|
|
61
|
+
export const IDP = {
|
|
62
|
+
GOOGLE: "0e6a50f5-b523-4e29-990d-f37fa2ffdd69",
|
|
63
|
+
FACEBOOK: "3ecad13f-52c3-483d-911f-31dbcf2a6d23",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Auth outcome you can show the user. `.code` is stable; `.message` is human text. */
|
|
67
|
+
export class MemberAuthError extends Error {
|
|
68
|
+
constructor(code, message) {
|
|
69
|
+
super(message || code);
|
|
70
|
+
this.name = "MemberAuthError";
|
|
71
|
+
this.code = code;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─────────────────────────── (A) direct-credential ───────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sign up a new member with email + password (+ optional custom `profile`).
|
|
79
|
+
* On SUCCESS the member is logged in (member tokens written to the shared client).
|
|
80
|
+
*
|
|
81
|
+
* @param {string} email
|
|
82
|
+
* @param {string} password
|
|
83
|
+
* @param {object} [profile] see the `profile` note in the file header
|
|
84
|
+
* @returns {Promise<{ state: string, member?: object|null, stateToken?: string }>}
|
|
85
|
+
*/
|
|
86
|
+
export async function register(email, password, profile) {
|
|
87
|
+
return runCredentialFlow(`${AUTH_BASE}/register`, {
|
|
88
|
+
loginId: { email },
|
|
89
|
+
password,
|
|
90
|
+
...(profile ? { profile } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Log an existing member in with email + password.
|
|
96
|
+
* @param {string} email
|
|
97
|
+
* @param {string} password
|
|
98
|
+
* @returns {Promise<{ state: string, member?: object|null, stateToken?: string }>}
|
|
99
|
+
*/
|
|
100
|
+
export async function login(email, password) {
|
|
101
|
+
return runCredentialFlow(`${AUTH_BASE}/login`, { loginId: { email }, password });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Continue a "REQUIRE_EMAIL_VERIFICATION" flow with the 6-digit code from the email.
|
|
106
|
+
* @param {string} code verification code the member received by email
|
|
107
|
+
* @param {string} stateToken the `stateToken` returned alongside REQUIRE_EMAIL_VERIFICATION
|
|
108
|
+
* @returns {Promise<{ state: string, member?: object|null, stateToken?: string }>}
|
|
109
|
+
*/
|
|
110
|
+
export async function verifyEmail(code, stateToken) {
|
|
111
|
+
let res;
|
|
112
|
+
try {
|
|
113
|
+
res = await wixApiRequest(VERIFY_URL, { body: { code, stateToken } });
|
|
114
|
+
} catch (e) {
|
|
115
|
+
throw mapAuthError(e);
|
|
116
|
+
}
|
|
117
|
+
return resolveState(res);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Email the member a password-reset link. Wix hosts the reset page and returns
|
|
122
|
+
* them to `redirectUri`, which MUST be an allow-listed authorization redirect URI
|
|
123
|
+
* on the OAuth app (see INSTRUCTIONS.md → dashboard links).
|
|
124
|
+
* @param {string} email
|
|
125
|
+
* @param {string} redirectUri full URL to return to after reset
|
|
126
|
+
*/
|
|
127
|
+
export async function sendPasswordResetEmail(email, redirectUri) {
|
|
128
|
+
await wixApiRequest(RECOVERY_URL, {
|
|
129
|
+
body: { email, redirect: { url: redirectUri, clientId: WIX_CLIENT_ID } },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function runCredentialFlow(path, body) {
|
|
134
|
+
let res;
|
|
135
|
+
try {
|
|
136
|
+
res = await wixApiRequest(path, { body });
|
|
137
|
+
} catch (e) {
|
|
138
|
+
throw mapAuthError(e);
|
|
139
|
+
}
|
|
140
|
+
return resolveState(res);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// On SUCCESS, finish login and return the member; otherwise hand the state (and
|
|
144
|
+
// stateToken, if any) back to the UI so it can prompt for verification etc.
|
|
145
|
+
async function resolveState(res) {
|
|
146
|
+
if (res.state === "SUCCESS") {
|
|
147
|
+
const member = await completeDirectLogin(res.sessionToken);
|
|
148
|
+
return { state: res.state, member };
|
|
149
|
+
}
|
|
150
|
+
return { state: res.state, stateToken: res.stateToken };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// The `getMemberTokensForDirectLogin` analog: turn a session token into member
|
|
154
|
+
// tokens via an INVISIBLE iframe (responseMode: web_message, prompt: none). The
|
|
155
|
+
// member never sees a page — this is the silent plumbing behind your own form.
|
|
156
|
+
async function completeDirectLogin(sessionToken) {
|
|
157
|
+
const pkce = await generatePkce();
|
|
158
|
+
const { fullUrl } = await createRedirectSession({
|
|
159
|
+
authRequest: { responseMode: "web_message", sessionToken, pkce },
|
|
160
|
+
});
|
|
161
|
+
const { code } = await authorizeViaHiddenIframe(fullUrl, pkce.state);
|
|
162
|
+
const tokens = await exchangeCode(code, pkce.codeVerifier);
|
|
163
|
+
setSessionTokens(tokens);
|
|
164
|
+
return getCurrentMember();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─────────────────────────── (B) social / SSO ───────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Start a social / SSO login. Redirects the whole page to the provider; returns
|
|
171
|
+
* through YOUR `callbackUri`, where you must call `completeSocialLogin()`.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} idp `IDP.GOOGLE` / `IDP.FACEBOOK`, or a custom SSO connection id
|
|
174
|
+
* @param {string} callbackUri your `/callback` URL — MUST be an allow-listed
|
|
175
|
+
* authorization redirect URI on the OAuth app (see INSTRUCTIONS.md)
|
|
176
|
+
* @param {string} [returnTo="/"] where to send the member after login completes
|
|
177
|
+
*/
|
|
178
|
+
export async function startSocialLogin(idp, callbackUri, returnTo = "/") {
|
|
179
|
+
const pkce = await generatePkce();
|
|
180
|
+
const { fullUrl } = await createRedirectSession({
|
|
181
|
+
authRequest: { responseMode: "fragment", idp, redirectUri: callbackUri, pkce },
|
|
182
|
+
});
|
|
183
|
+
// The callback runs on a separate page load — carry what the exchange needs.
|
|
184
|
+
window.localStorage.setItem(
|
|
185
|
+
OAUTH_STASH_KEY,
|
|
186
|
+
JSON.stringify({ codeVerifier: pkce.codeVerifier, state: pkce.state, redirectUri: callbackUri, returnTo }),
|
|
187
|
+
);
|
|
188
|
+
window.location.href = fullUrl;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Finish a social / SSO login on the `/callback` page: reads `#code`/`#state`
|
|
193
|
+
* from the URL, verifies state, exchanges the code, logs the member in.
|
|
194
|
+
* @returns {Promise<{ member: object|null, returnTo: string }>}
|
|
195
|
+
*/
|
|
196
|
+
export async function completeSocialLogin() {
|
|
197
|
+
const raw = window.localStorage.getItem(OAUTH_STASH_KEY);
|
|
198
|
+
if (!raw) throw new MemberAuthError("missingOAuthData", "No pending login found — start the login again.");
|
|
199
|
+
window.localStorage.removeItem(OAUTH_STASH_KEY);
|
|
200
|
+
const stash = JSON.parse(raw);
|
|
201
|
+
|
|
202
|
+
const params = new URLSearchParams(window.location.hash.replace(/^#/, ""));
|
|
203
|
+
const error = params.get("error");
|
|
204
|
+
if (error) throw new MemberAuthError(error, params.get("error_description") || error);
|
|
205
|
+
const code = params.get("code");
|
|
206
|
+
const state = params.get("state");
|
|
207
|
+
if (!code || !state) throw new MemberAuthError("missingCode", "No authorization code returned.");
|
|
208
|
+
if (state !== stash.state) throw new MemberAuthError("stateMismatch", "OAuth state mismatch — possible CSRF; login aborted.");
|
|
209
|
+
|
|
210
|
+
const tokens = await exchangeCode(code, stash.codeVerifier, stash.redirectUri);
|
|
211
|
+
setSessionTokens(tokens);
|
|
212
|
+
return { member: await getCurrentMember(), returnTo: stash.returnTo || "/" };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ─────────────────────────── session ───────────────────────────
|
|
216
|
+
|
|
217
|
+
/** True once a member is logged in on this client. Gate account UI on it. */
|
|
218
|
+
export function isLoggedIn() {
|
|
219
|
+
return isMember();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Read the logged-in member (name / photo / roles / custom fields). Needs the
|
|
224
|
+
* **Members Area app** installed; returns `null` for an anonymous visitor or when
|
|
225
|
+
* the app isn't installed (that's setup, not a code bug — see INSTRUCTIONS.md).
|
|
226
|
+
* @returns {Promise<object|null>}
|
|
227
|
+
*/
|
|
228
|
+
export async function getCurrentMember() {
|
|
229
|
+
try {
|
|
230
|
+
const res = await wixApiRequest(CURRENT_MEMBER_URL, { method: "GET", query: { fieldSet: "FULL" } });
|
|
231
|
+
return res?.member || null;
|
|
232
|
+
} catch (e) {
|
|
233
|
+
if (e.status === 401 || e.status === 403 || e.status === 404) return null;
|
|
234
|
+
throw e;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Log the member out: clears the local session immediately, then redirects
|
|
240
|
+
* through the Wix logout URL so the server session is dropped too. After this the
|
|
241
|
+
* client is a clean anonymous visitor again.
|
|
242
|
+
* @param {string} [returnTo] where to land after logout (defaults to the origin)
|
|
243
|
+
*/
|
|
244
|
+
export async function logout(returnTo) {
|
|
245
|
+
const postFlowUrl = returnTo || (typeof window !== "undefined" ? window.location.origin : "/");
|
|
246
|
+
let logoutUrl;
|
|
247
|
+
try {
|
|
248
|
+
const { redirectSession } = await wixApiRequest(REDIRECT_SESSION_URL, {
|
|
249
|
+
body: { logout: { clientId: WIX_CLIENT_ID }, callbacks: { postFlowUrl } },
|
|
250
|
+
});
|
|
251
|
+
logoutUrl = redirectSession?.fullUrl;
|
|
252
|
+
} finally {
|
|
253
|
+
clearSession(); // drop local tokens even if building the logout URL failed
|
|
254
|
+
}
|
|
255
|
+
if (logoutUrl && typeof window !== "undefined") window.location.href = logoutUrl;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ─────────────────────────── internals ───────────────────────────
|
|
259
|
+
|
|
260
|
+
async function createRedirectSession({ authRequest }) {
|
|
261
|
+
const { responseMode, sessionToken, idp, redirectUri, pkce } = authRequest;
|
|
262
|
+
// ⚠️ EXACT Wix wire shape — do NOT simplify or inline this. It is NOT the input
|
|
263
|
+
// object: the request needs the `auth.authRequest` wrapper, the PKCE fields FLAT
|
|
264
|
+
// (`codeChallenge` + `codeChallengeMethod`, not a nested `pkce` object; the
|
|
265
|
+
// `codeVerifier` is NOT sent here — it's kept for the token exchange), and
|
|
266
|
+
// `responseType`/`scope` present. Spreading the input straight onto the body →
|
|
267
|
+
// 400 Bad Request.
|
|
268
|
+
const { redirectSession } = await wixApiRequest(REDIRECT_SESSION_URL, {
|
|
269
|
+
body: {
|
|
270
|
+
auth: {
|
|
271
|
+
authRequest: {
|
|
272
|
+
clientId: WIX_CLIENT_ID,
|
|
273
|
+
codeChallenge: pkce.codeChallenge,
|
|
274
|
+
codeChallengeMethod: "S256",
|
|
275
|
+
responseMode,
|
|
276
|
+
responseType: "code",
|
|
277
|
+
scope: "offline_access",
|
|
278
|
+
state: pkce.state,
|
|
279
|
+
...(redirectUri ? { redirectUri } : {}),
|
|
280
|
+
...(sessionToken ? { sessionToken } : {}),
|
|
281
|
+
...(idp ? { idp } : {}),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
if (!redirectSession?.fullUrl) throw new MemberAuthError("noRedirectSession", "Wix did not return an authorization URL.");
|
|
287
|
+
return { fullUrl: redirectSession.fullUrl };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Open the authorize URL in a hidden iframe and wait for the web_message postMessage
|
|
291
|
+
// carrying { code, state }. Matches @wix/sdk iframeUtils.addPostMessageListener.
|
|
292
|
+
function authorizeViaHiddenIframe(authUrl, expectedState) {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
const iframe = document.createElement("iframe");
|
|
295
|
+
iframe.style.display = "none";
|
|
296
|
+
let settled = false;
|
|
297
|
+
const cleanup = () => {
|
|
298
|
+
window.removeEventListener("message", onMessage);
|
|
299
|
+
clearTimeout(timer);
|
|
300
|
+
if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
|
|
301
|
+
};
|
|
302
|
+
const onMessage = (e) => {
|
|
303
|
+
if (!e.data || e.data.state !== expectedState) return; // not our message
|
|
304
|
+
settled = true;
|
|
305
|
+
cleanup();
|
|
306
|
+
if (e.data.error) reject(new MemberAuthError(e.data.error, e.data.error_description || e.data.error));
|
|
307
|
+
else resolve({ code: e.data.code, state: e.data.state });
|
|
308
|
+
};
|
|
309
|
+
window.addEventListener("message", onMessage);
|
|
310
|
+
const timer = setTimeout(() => {
|
|
311
|
+
if (!settled) {
|
|
312
|
+
cleanup();
|
|
313
|
+
// The overwhelmingly common cause: this app's ORIGIN is not an allowed
|
|
314
|
+
// authorization redirect URI on the OAuth app, so the browser silently
|
|
315
|
+
// blocks the iframe's postMessage (check the console for a "target origin
|
|
316
|
+
// … does not match" error). Register the origin — see INSTRUCTIONS.md.
|
|
317
|
+
reject(new MemberAuthError(
|
|
318
|
+
"timeout",
|
|
319
|
+
`Login timed out. Most likely this app's origin (${typeof window !== "undefined" ? window.location.origin : "?"}) ` +
|
|
320
|
+
`is not an allowed authorization redirect URI on the Wix OAuth app — register it in the site's Headless Settings.`,
|
|
321
|
+
));
|
|
322
|
+
}
|
|
323
|
+
}, 120000);
|
|
324
|
+
iframe.src = authUrl;
|
|
325
|
+
document.body.appendChild(iframe);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Exchange an authorization code for member tokens. No Authorization header — this
|
|
330
|
+
// is the token-minting endpoint (mirrors the SDK's fetchTokens). `redirectUri` is
|
|
331
|
+
// sent only for social (it must match the redirect-session's redirectUri).
|
|
332
|
+
async function exchangeCode(code, codeVerifier, redirectUri) {
|
|
333
|
+
if (!code) throw new MemberAuthError("missingCode", "No authorization code to exchange.");
|
|
334
|
+
const res = await fetch(`${WIX_API_BASE}/oauth2/token`, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers: { "Content-Type": "application/json" },
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
clientId: WIX_CLIENT_ID,
|
|
339
|
+
grantType: "authorization_code",
|
|
340
|
+
code,
|
|
341
|
+
codeVerifier,
|
|
342
|
+
...(redirectUri ? { redirectUri } : {}),
|
|
343
|
+
}),
|
|
344
|
+
});
|
|
345
|
+
if (!res.ok) {
|
|
346
|
+
let body;
|
|
347
|
+
try { body = await res.json(); } catch { /* ignore */ }
|
|
348
|
+
throw new MemberAuthError(body?.error || "tokenExchangeFailed", `Token exchange failed: ${res.status}${body ? ` ${JSON.stringify(body)}` : ""}`);
|
|
349
|
+
}
|
|
350
|
+
const data = await res.json();
|
|
351
|
+
return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresIn: data.expires_in };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function mapAuthError(e) {
|
|
355
|
+
const code = e?.body?.details?.applicationError?.code;
|
|
356
|
+
// Email already exists (register) — route them to log in.
|
|
357
|
+
if (code === "-19995" || e?.status === 409) {
|
|
358
|
+
return new MemberAuthError("emailAlreadyExists", "An account with this email already exists — try logging in instead.");
|
|
359
|
+
}
|
|
360
|
+
// Bad credentials: -19999 identity-not-found (404), -19976 wrong-password (401).
|
|
361
|
+
if (code === "-19999" || code === "-19976" || e?.status === 404 || e?.status === 401) {
|
|
362
|
+
return new MemberAuthError("invalidCredentials", "Incorrect email or password.");
|
|
363
|
+
}
|
|
364
|
+
if (e instanceof MemberAuthError) return e;
|
|
365
|
+
return e; // unknown — bubble the raw transport error (fail loudly)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ── PKCE (RFC 7636, S256) — browser Web Crypto, dependency-free ──
|
|
369
|
+
async function generatePkce() {
|
|
370
|
+
const codeVerifier = randomUnreserved(43);
|
|
371
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
|
|
372
|
+
return { codeVerifier, codeChallenge: base64Url(new Uint8Array(digest)), state: randomUnreserved(24) };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function randomUnreserved(length) {
|
|
376
|
+
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
377
|
+
const bytes = crypto.getRandomValues(new Uint8Array(length));
|
|
378
|
+
let out = "";
|
|
379
|
+
for (let i = 0; i < length; i++) out += mask[bytes[i] % mask.length];
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function base64Url(bytes) {
|
|
384
|
+
let binary = "";
|
|
385
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
386
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
387
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
# Wix Portfolio Skill
|
|
3
|
+
|
|
4
|
+
> **Source files (in this skill):** the shared transport `references/shared/wix-client.js` and this vertical's `references/portfolio/wix-portfolio.js`. Copy **both** into your app's `src/rest/` side by side — the helper does `import { wixApiRequest } from "./wix-client.js"`, so they must land in the same folder.
|
|
5
|
+
|
|
6
|
+
Builds a real, client-only Wix portfolio showcase. The browser talks to Wix directly over a
|
|
7
|
+
public `WIX_CLIENT_ID`. Portfolio is **read-only**: never mock projects, never invent media,
|
|
8
|
+
and always render live Wix data (or an honest empty state). The content tree is
|
|
9
|
+
**Collection → Project → Project Item (image/video)**.
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
- User wants a Wix portfolio / showcase site or asks to "connect Wix Portfolio".
|
|
13
|
+
- Replacing placeholder/mock galleries with live Wix Portfolio data.
|
|
14
|
+
- Adding a collections gallery, project grids, or a project detail page (media gallery +
|
|
15
|
+
details/links) over an existing Wix Portfolio.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
1. A Wix site with **Wix Portfolio installed and content already added** — at least one
|
|
19
|
+
collection with projects, and projects with items (this skill does NOT provision; it is
|
|
20
|
+
read-only over the content).
|
|
21
|
+
2. The site's public headless **`WIX_CLIENT_ID`**, provided in the handoff prompt (the Wix
|
|
22
|
+
Business Manager surfaces a copyable prompt with the id filled in — see the router `SKILL.md`).
|
|
23
|
+
Paste it into `src/rest/wix-client.js` in place of the placeholder. It is a visitor-facing
|
|
24
|
+
credential (it only mints anonymous visitor tokens), **not** a secret, so
|
|
25
|
+
hardcoding/committing it is fine.
|
|
26
|
+
3. If the read calls return `403`/`428` before content is published, the Portfolio app or its
|
|
27
|
+
content may not be live yet. That is a **Wix dashboard setup step the owner completes** —
|
|
28
|
+
out of this skill's scope. Flag it and continue; don't fall back to mock data.
|
|
29
|
+
|
|
30
|
+
## The API (copy as-is; do not re-derive it)
|
|
31
|
+
This skill ships only the REST layer — no UI components. Build the portfolio's UI however the
|
|
32
|
+
project wants; wire it to these two snippets. Copy them into the app (e.g. `src/api/`) and only
|
|
33
|
+
adjust import paths:
|
|
34
|
+
- `src/rest/wix-client.js` — visitor-token mint/refresh + transport. Set `WIX_CLIENT_ID` to the
|
|
35
|
+
id from the prompt (replace the `<YOUR-CLIENT-ID>` placeholder). The visitor refresh token is
|
|
36
|
+
persisted to localStorage; do not re-mint anonymously per load.
|
|
37
|
+
- `src/rest/wix-portfolio.js` — exports:
|
|
38
|
+
- **Collections:** `queryCollections`, `getCollectionBySlug`, `countCollections`
|
|
39
|
+
- **Projects:** `queryProjects`, `queryProjectsByCollection`, `getProjectBySlug`, `getProject`
|
|
40
|
+
- **Project items (media):** `listProjectItems`
|
|
41
|
+
|
|
42
|
+
The Collection, Project, and Project Item shapes are documented as JSDoc comments at the top of
|
|
43
|
+
`wix-portfolio.js`. Read them before building the UI — they describe the key fields (cover
|
|
44
|
+
image/video, media resolutions, details rows) and link to the full API reference.
|
|
45
|
+
|
|
46
|
+
## How to wire it (UI is the project's choice)
|
|
47
|
+
- **Collections gallery (home)** — `queryCollections()` for the listing (visible collections
|
|
48
|
+
only, in dashboard order); pass `nextCursor` back as `cursor` to load the next page. Render
|
|
49
|
+
`collection.title`, `collection.description`, and `collection.coverImage.imageInfo.url`. Link
|
|
50
|
+
each card to a collection page keyed on `collection.slug`.
|
|
51
|
+
- **Collection page** — `getCollectionBySlug(slug)` for the header (returns null on miss — show
|
|
52
|
+
a not-found state, never invent one). Then `queryProjectsByCollection(collection.id, { limit?, cursor? })`
|
|
53
|
+
for its project grid; paginate exactly like `queryCollections`.
|
|
54
|
+
- **All-work gallery (optional)** — `queryProjects()` to list every visible project across
|
|
55
|
+
collections (newest-first); paginate the same way.
|
|
56
|
+
- **Project grid card** — render `project.title` and the cover: use `project.coverImage.imageInfo.url`
|
|
57
|
+
when present, else the `project.coverVideo.videoInfo.posters[0]` / first
|
|
58
|
+
`coverVideo.videoInfo.resolutions[]` entry. Link each card to a project page keyed on `project.slug`.
|
|
59
|
+
- **Project detail page** — `getProjectBySlug(slug)` for the header (title, description, and the
|
|
60
|
+
`details[]` rows: each has a `label` plus either `text` or `link: { text, url, target }`).
|
|
61
|
+
Then `listProjectItems(project.id)` for the media gallery — render IMAGE items from
|
|
62
|
+
`image.imageInfo.url` and VIDEO items from the first `video.videoInfo.resolutions[]` entry
|
|
63
|
+
(with `video.videoInfo.posters[0]` as the poster). Honor each item's optional `link`.
|
|
64
|
+
- If you already hold a project id (not a slug), use `getProject(projectId)` instead.
|
|
65
|
+
- **Empty state** — if `countCollections()` is 0, show an empty state telling the user to add
|
|
66
|
+
collections and projects in their Wix dashboard. Never invent projects or media.
|
|
67
|
+
|
|
68
|
+
## Hard rules (do not violate)
|
|
69
|
+
- ✅ Render only live Wix Portfolio data — collections, projects, and items as returned.
|
|
70
|
+
- ❌ Never mock projects, collections, or media — render live data or the empty state.
|
|
71
|
+
- ❌ Never hand-build Wix Media URLs or page permalinks — use the `url`/`imageInfo.url`/
|
|
72
|
+
`videoInfo.resolutions[].url` fields Wix returns.
|
|
73
|
+
- ❌ Never invent project details, captions, dates, or testimonials. Show only what the API returns.
|
|
74
|
+
- ❌ Never show `hidden: true` collections or projects to visitors (the helpers already filter
|
|
75
|
+
`hidden` out — don't add them back).
|
|
76
|
+
- ✅ Set `WIX_CLIENT_ID` from the prompt's value (public client id — safe to hardcode).
|
|
77
|
+
- ✅ Route on `slug` (`getCollectionBySlug` / `getProjectBySlug`); fall back to `getProject(id)`
|
|
78
|
+
only when you hold a GUID.
|
|
79
|
+
- The helpers fail soft on read: single fetches return `null` on a miss so you render a
|
|
80
|
+
not-found/empty state rather than a crash — surface that state, don't paper over it with fake data.
|
|
81
|
+
|
|
82
|
+
## Beyond the snippets
|
|
83
|
+
The snippets cover the common portfolio paths. If you hit a use case they don't cover (e.g.
|
|
84
|
+
collection SEO tags, project watermark settings, portfolio-wide settings, or filtering by a
|
|
85
|
+
field not shown), make the call yourself with `wixApiRequest` — but look up the exact endpoint,
|
|
86
|
+
HTTP method, and request body in the **official Wix API reference** first; never guess:
|
|
87
|
+
- Wix Portfolio API reference: https://dev.wix.com/docs/api-reference/business-solutions/portfolio.md
|
|
88
|
+
- Portfolio Settings (logo, layout, options): https://dev.wix.com/docs/api-reference/business-solutions/portfolio/portfolio-settings/get-portfolio-settings.md
|
|
89
|
+
- API query language (filters, sort, paging): https://dev.wix.com/docs/rest/articles/getting-started/api-query-language.md
|
|
90
|
+
|
|
91
|
+
Keep the snippets as the default for everything they already do; reach for the API reference
|
|
92
|
+
only for the gap.
|
|
93
|
+
|
|
94
|
+
## Point the user to their dashboard
|
|
95
|
+
In some cases, users need to access the Wix dashboard in order to edit the portfolio content for their site. To facilitate this, provide the user with deep links directly to the relevant dashboard pages. For portfolio data those pages are:
|
|
96
|
+
- **Portfolio** — `https://manage.wix.com/dashboard/{metaSiteId}/wix-portfolio/projects` (`Dashboard → Portfolio`). Projects and Collections are tabs on this one page: the **Projects** tab adds projects and their media galleries; the **Collections** tab groups projects into collections.
|
|
97
|
+
|
|
98
|
+
Substitute the site's `metaSiteId` to complete the links (you have it from the handoff / `ListWixSites`). Include the in-dashboard navigation as a fallback.
|
|
99
|
+
|
|
100
|
+
## Verification checklist (before declaring done)
|
|
101
|
+
- [ ] `WIX_CLIENT_ID` set to the prompt's value (not the `<YOUR-CLIENT-ID>` placeholder)
|
|
102
|
+
- [ ] Visitor token persists across reload (no re-mint storm; reads stay fast)
|
|
103
|
+
- [ ] Collections gallery renders live collections with cover images, in dashboard order
|
|
104
|
+
- [ ] Clicking a collection lists its projects via `queryProjectsByCollection`
|
|
105
|
+
- [ ] Project page renders the project's media gallery from `listProjectItems` (images AND videos)
|
|
106
|
+
- [ ] Project `details[]` rows render (text rows and link rows both)
|
|
107
|
+
- [ ] Slug routing works; an unknown slug shows a not-found state (no invented project)
|
|
108
|
+
- [ ] Hidden collections/projects never appear
|
|
109
|
+
- [ ] Empty state shown when `countCollections()` is 0
|
|
110
|
+
- [ ] No mock projects, collections, or media anywhere
|
|
111
|
+
- [ ] Told the user at least once that they can continue setting up their portfolio in the dashboard and provided deep links.
|