@wix/agent-skills 1.16.3 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +115 -12
- package/skills/wix-app/references/APP_MARKET_REVIEW.md +1 -1
- package/skills/wix-app/references/BACKEND_API.md +18 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +6 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +10 -2
- package/skills/wix-app/references/DASHBOARD_PAGE.md +32 -70
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +145 -127
- package/skills/wix-app/references/SITE_PLUGIN.md +14 -2
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +2 -2
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +79 -0
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +73 -0
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +52 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +184 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +182 -99
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +94 -49
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +16 -6
- package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +275 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +35 -40
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +167 -445
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +56 -29
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +53 -158
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +78 -0
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +42 -134
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +11 -3
- package/skills/wix-app/references/editor-react-component/PARTS.md +46 -24
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +63 -179
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +79 -163
- package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +36 -21
- package/skills/wix-app/references/editor-react-component/SSR.md +30 -99
- package/skills/wix-base44-connector/SKILL.md +156 -50
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
- package/skills/wix-base44-connector/scripts/utils.js +239 -45
- package/skills/wix-headless/entry/bootstrap.mjs +9 -2
- package/skills/wix-headless/entry/skill.md +1 -1
- package/skills/wix-headless/references/CAPABILITIES.md +9 -1
- package/skills/wix-headless/references/DESIGN.md +1 -0
- package/skills/wix-headless/references/DISCOVERY.md +2 -1
- package/skills/wix-headless/references/IMAGE_GENERATION.md +1 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +5 -2
- package/skills/wix-headless/references/SEED.md +2 -1
- package/skills/wix-headless/references/SETUP.md +1 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +2 -2
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +68 -31
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +26 -10
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +8 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +10 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +254 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +27 -24
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +13 -0
- package/skills/wix-headless/references/inline-recipes/setup-rentals.md +254 -0
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +5 -0
- package/skills/wix-headless/references/non-astro.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +166 -0
- package/skills/wix-headless-fast/entry/skill.md +99 -0
- package/skills/wix-headless-fast/install/deploy.mjs +433 -0
- package/skills/wix-headless-fast/install/fast-path.mjs +141 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +113 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +42 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +123 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +70 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +99 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +76 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +62 -0
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +99 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +24 -0
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +16156 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +72 -0
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +287 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +129 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +143 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +119 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +138 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +54 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +206 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +96 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +70 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +76 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +22 -0
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +11962 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +61 -0
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +302 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +179 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +83 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +57 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +151 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +75 -0
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +11262 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +103 -0
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +324 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +66 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +176 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +118 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +117 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +59 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +136 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +128 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +61 -0
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +12 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +80 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +21 -0
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +11884 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +86 -0
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +276 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +150 -0
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +317 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +269 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +231 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +135 -0
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +10593 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +108 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +320 -0
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +63 -0
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +60 -0
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +155 -0
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +78 -0
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +47 -0
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +66 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +88 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +53 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +122 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +50 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +21 -0
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +42 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +10 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +11 -0
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +10329 -0
- package/skills/wix-headless-fast/references/members/seed/SEED.md +39 -0
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +94 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +134 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +110 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +90 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +115 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +59 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +39 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +165 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +55 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +30 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +21 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +29 -0
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +10197 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +78 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +258 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +133 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +58 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +126 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +37 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +39 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +75 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +45 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +30 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +40 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +21 -0
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +11226 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +71 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +279 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +142 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +55 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +43 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +177 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +103 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +254 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +146 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +168 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +47 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +22 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +12 -0
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +12560 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +81 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +479 -0
- package/skills/wix-headless-fast/references/shared/CONTENT.md +22 -0
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +73 -0
- package/skills/wix-headless-fast/references/shared/DESIGN.md +26 -0
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +52 -0
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +15 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +27 -0
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +24 -0
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +76 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +46 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +46 -0
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +145 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +100 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +130 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +48 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +66 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +103 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +48 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +140 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +80 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +101 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +221 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +215 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +109 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +46 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +56 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +23 -0
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +12247 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +85 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +415 -0
- package/skills/wix-manage/SKILL.md +28 -2
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +20 -13
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +24 -11
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +5 -5
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +141 -0
- package/skills/wix-manage/references/google-business-profile/connect-google-business-profile.md +134 -0
- package/skills/wix-manage/references/google-business-profile/manage-google-business-profile-locations.md +182 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +3 -0
- package/skills/wix-manage/references/seo/manage-seo-tags.md +315 -0
- package/skills/wix-manage/references/seo/manage-url-redirects.md +250 -0
- package/skills/wix-manage/references/sites/query-sites.md +72 -161
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +21 -6
- package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +5 -6
- package/skills/wix-vibe-headless/SKILL.md +18 -4
- package/skills/wix-vibe-headless/install/deploy.cjs +12 -4
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +2 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +16 -10
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +13 -8
- package/skills/wix-vibe-headless/platforms/base44.md +27 -62
- package/skills/wix-vibe-headless/platforms/generic.md +6 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +45 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +67 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +128 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +5 -3
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +9 -8
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +250 -0
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +12 -5
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +41 -0
- package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js +267 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +6 -3
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +470 -0
- package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +425 -0
- package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +187 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +238 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +108 -0
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +1762 -0
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +4 -1
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +23 -11
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +29 -31
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +152 -92
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +37 -38
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +2 -2
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +15 -8
- package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +37 -0
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +35 -35
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +18 -6
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +30 -7
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +46 -10
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +0 -172
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +0 -143
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +0 -516
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +0 -225
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +0 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +0 -95
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +0 -110
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +0 -51
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +0 -49
- package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +0 -92
- package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +0 -139
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// useWixForm — one form, minus the markup. It loads the LIVE schema, validates against it, submits,
|
|
2
|
+
// and lands the server's violations on the right controls. What it does NOT do is render: this
|
|
3
|
+
// vertical ships no UI, because a form is schema-driven (the owner picks the fields), so every form
|
|
4
|
+
// has a different field set and no "contact form" component could ship for it. You build the markup
|
|
5
|
+
// — see `references/forms/INSTRUCTIONS.md`, "Build the UI".
|
|
6
|
+
//
|
|
7
|
+
// const { form, values, setValues, bind, submit, validate, errors, loading, read } =
|
|
8
|
+
// useWixForm(WIX_FORMS.contact.formId);
|
|
9
|
+
//
|
|
10
|
+
// The fields to render are the RAW field objects Wix returned — no projection, nothing hidden. Take
|
|
11
|
+
// them off `form` with the same accessors the hook uses internally (`read` is the module
|
|
12
|
+
// `lib/wix-form-schema-utils.js`, handed back so the UI needs one import):
|
|
13
|
+
//
|
|
14
|
+
// read.orderedInputs(form).map((field) => <label key={read.targetOf(field)}>{read.labelOf(field)}…</label>)
|
|
15
|
+
//
|
|
16
|
+
// The controls are CONTROLLED: `values` (keyed by each field's `target`) is the form's state, so
|
|
17
|
+
// validate and submit read it directly — there is no FormData pass and no ref to hand over.
|
|
18
|
+
//
|
|
19
|
+
// Reading the live schema rather than a hardcoded field list is what makes an owner's dashboard edit
|
|
20
|
+
// (add, relabel, reorder, require, constrain a field) show up on the site with no code change, and
|
|
21
|
+
// what keeps the submission map from desyncing.
|
|
22
|
+
//
|
|
23
|
+
// Not on React? `validateField` below and everything in `lib/wix-form-schema-utils.js` are plain
|
|
24
|
+
// functions — port the ~80 lines of state around them.
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
26
|
+
import { getForm, phoneExample, normalizePhone } from "@/rest/wix-forms";
|
|
27
|
+
import {
|
|
28
|
+
uploadFormFiles,
|
|
29
|
+
toSubmissionValues,
|
|
30
|
+
createSubmission,
|
|
31
|
+
submissionViolations,
|
|
32
|
+
} from "@/rest/wix-forms-submissions";
|
|
33
|
+
import * as read from "@/lib/wix-form-schema-utils";
|
|
34
|
+
import {
|
|
35
|
+
targetOf, labelOf, isRequired, isMulti, isFile, isNumber, isAddress,
|
|
36
|
+
addressPartsOf, validationOf, phoneCountryOf, orderedInputs,
|
|
37
|
+
} from "@/lib/wix-form-schema-utils";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The key in `errors` for a message that belongs to the FORM rather than one field — a schema that
|
|
41
|
+
* failed to load, or a submit rejection with no per-field violations. `@` can't appear in a Wix
|
|
42
|
+
* form `target`, so this can never collide with a field's own error.
|
|
43
|
+
*/
|
|
44
|
+
export const FORM_ERROR = "@form";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The empty form: one entry per field, in the shape that field's control binds to — `[]` for a
|
|
48
|
+
* multi-choice field, `{}` for an address, and the owner's `default` prefill (else `""`) for the
|
|
49
|
+
* rest. Dropping `defaultValue` here would silently discard a dashboard setting.
|
|
50
|
+
*/
|
|
51
|
+
function defaultValues(fields) {
|
|
52
|
+
const values = {};
|
|
53
|
+
for (const field of fields) {
|
|
54
|
+
values[targetOf(field)] =
|
|
55
|
+
isAddress(field) ? {} :
|
|
56
|
+
isMulti(field) || isFile(field) ? [] : // a file field holds File objects
|
|
57
|
+
// The owner's prefill. Wix spells it `default` on most components and `defaultValue` on a
|
|
58
|
+
// rating, so read both — picking one silently drops a dashboard setting on the other.
|
|
59
|
+
read.componentOf(field).default ?? read.componentOf(field).defaultValue ?? "";
|
|
60
|
+
}
|
|
61
|
+
return values;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Client-side validation for one field — the rules behind `validate(target)`. It lives here rather
|
|
66
|
+
* than in `rest/` because nothing about it is a REST call: it's the UI's own read of the schema.
|
|
67
|
+
* Derives EVERY check from that schema — never from the field's name. (The classic mistake is keying the email check on `target === "email"`; doing it
|
|
68
|
+
* off `format` means an owner-added PHONE/URL/length rule is honored with no code change.)
|
|
69
|
+
*
|
|
70
|
+
* ⚠️ A client check LAXER than the server's is worse than none — the visitor then learns about the
|
|
71
|
+
* problem only after a round trip, in the server's wording rather than yours. Keep these at least
|
|
72
|
+
* as strict as the server, and submit the NORMALIZED value the check passed.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} field One raw field, from `fields`.
|
|
75
|
+
* @param {unknown} value The control's current value.
|
|
76
|
+
* @returns {string} A visitor-facing message, or "" when it passes.
|
|
77
|
+
*/
|
|
78
|
+
export function validateField(field, value) {
|
|
79
|
+
const rules = validationOf(field);
|
|
80
|
+
const label = labelOf(field);
|
|
81
|
+
const v = String(value ?? "").trim();
|
|
82
|
+
if (isRequired(field) && !v) return `${label} is required.`;
|
|
83
|
+
if (!v) return ""; // optional + empty → ok
|
|
84
|
+
if (rules.minLength && v.length < rules.minLength) return `${label} must be at least ${rules.minLength} characters.`;
|
|
85
|
+
if (rules.maxLength && v.length > rules.maxLength) return `${label} must be at most ${rules.maxLength} characters.`;
|
|
86
|
+
// A NUMBER field carries no `format`; its rules are the bounds, and the control hands back a
|
|
87
|
+
// string, so parse before comparing — `"9" > 10` is false but `"9" > "10"` is true.
|
|
88
|
+
if (isNumber(field)) {
|
|
89
|
+
const n = Number(v);
|
|
90
|
+
if (!Number.isFinite(n)) return `${label} must be a number.`;
|
|
91
|
+
const min = minimumOf(field);
|
|
92
|
+
const max = maximumOf(field);
|
|
93
|
+
if (min != null && n < min) return `${label} must be ${min} or more.`;
|
|
94
|
+
if (max != null && n > max) return `${label} must be ${max} or less.`;
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
if (rules.format === "EMAIL" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) return "Please enter a valid email address.";
|
|
98
|
+
if (rules.format === "URL" && !/^https?:\/\/.+/.test(v)) return "Please enter a valid URL.";
|
|
99
|
+
// PHONE is E.164 server-side: leading +, country code, digits only. Strip formatting first —
|
|
100
|
+
// visitors add spaces, dashes and parens, and rejecting those is a UX bug, not validation.
|
|
101
|
+
if (rules.format === "PHONE" && !/^\+[1-9]\d{6,14}$/.test(normalizePhone(v)))
|
|
102
|
+
return `Use international format, e.g. ${phoneExample(phoneCountryOf(field))}.`;
|
|
103
|
+
if (rules.pattern && !new RegExp(rules.pattern).test(v)) return `${label} is not in the expected format.`;
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A NUMBER field's bounds. The validation block is JSON-Schema shaped everywhere else
|
|
109
|
+
* (`minLength`, `maxLength`, `pattern`, `format`, `enum`), so the numeric pair is `minimum` /
|
|
110
|
+
* `maximum`; `minValue` / `maxValue` are read too, since the public reference documents neither
|
|
111
|
+
* spelling and an older schema may carry them. Undefined when the owner set no bound.
|
|
112
|
+
*/
|
|
113
|
+
const minimumOf = (field) => validationOf(field).minimum ?? validationOf(field).minValue;
|
|
114
|
+
const maximumOf = (field) => validationOf(field).maximum ?? validationOf(field).maxValue;
|
|
115
|
+
|
|
116
|
+
/** `postalCode` → "Postal code" — an address subfield is a key, not a label the schema carries. */
|
|
117
|
+
function humanizeSub(sub) {
|
|
118
|
+
const words = sub.replace(/([A-Z])|(\d+)/g, " $1$2").toLowerCase().trim();
|
|
119
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The error entries one field currently has, keyed by input NAME — the same keys the controls and
|
|
124
|
+
* the server's error paths use. (Distinct from `validateField` above, which judges a single VALUE
|
|
125
|
+
* against one field's rules and returns one message; this maps that onto the form's keys, and an
|
|
126
|
+
* address has several.) A plain field yields at most one entry (`target`); an ADDRESS
|
|
127
|
+
* yields one per failing subfield (`target/sub`).
|
|
128
|
+
*
|
|
129
|
+
* Every rule comes from the schema (`validateField` above): `required`,
|
|
130
|
+
* `minLength`/`maxLength`, `pattern`, and `format` — EMAIL, PHONE (E.164), URL. Nothing is keyed off
|
|
131
|
+
* a field's NAME, so an owner-added constraint is honored with no code change.
|
|
132
|
+
*
|
|
133
|
+
* An address subfield gets the `required` check only: `country` and `subdivision` are
|
|
134
|
+
* country-dependent enums the schema doesn't enumerate, so their content is the server's call.
|
|
135
|
+
*/
|
|
136
|
+
function errorsForField(field, values) {
|
|
137
|
+
const errors = {};
|
|
138
|
+
const target = targetOf(field);
|
|
139
|
+
|
|
140
|
+
if (isAddress(field)) {
|
|
141
|
+
const parts = values[target] ?? {};
|
|
142
|
+
for (const { sub, required } of addressPartsOf(field)) {
|
|
143
|
+
// Only `required` is checkable here: country/subdivision are country-dependent enums the
|
|
144
|
+
// schema doesn't enumerate, so their content is the server's call.
|
|
145
|
+
if (required && !String(parts[sub] ?? "").trim())
|
|
146
|
+
errors[`${target}/${sub}`] = `${humanizeSub(sub)} is required.`;
|
|
147
|
+
}
|
|
148
|
+
return errors;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// A file field holds File objects, which no string rule can judge — check the count instead.
|
|
152
|
+
if (isFile(field)) {
|
|
153
|
+
const limit = validationOf(field).fileLimit;
|
|
154
|
+
const picked = [].concat(values[target] ?? []).filter(Boolean);
|
|
155
|
+
if (isRequired(field) && !picked.length) errors[target] = `${labelOf(field)} is required.`;
|
|
156
|
+
else if (limit && picked.length > limit)
|
|
157
|
+
errors[target] = `Attach at most ${limit} file${limit === 1 ? "" : "s"}.`;
|
|
158
|
+
return errors;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const message = validateField(field, values[target]);
|
|
162
|
+
if (message) errors[target] = message;
|
|
163
|
+
return errors;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Every field's error entries, merged into one `name → message` map. */
|
|
167
|
+
function errorsForForm(fields, values) {
|
|
168
|
+
const errors = {};
|
|
169
|
+
for (const field of fields) Object.assign(errors, errorsForField(field, values));
|
|
170
|
+
return errors;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** `format` → the copy for a FORMAT_ERROR, since "wrong shape" means something different per format. */
|
|
174
|
+
const FORMAT_COPY = {
|
|
175
|
+
EMAIL: () => "Enter a valid email address.",
|
|
176
|
+
PHONE: (f) => `Use international format, e.g. ${phoneExample(phoneCountryOf(f))}.`,
|
|
177
|
+
URL: () => "Enter a full URL starting with https://",
|
|
178
|
+
DATE: () => "Choose a valid date.",
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/** errorType → visitor-facing copy, written from the field's own schema. */
|
|
182
|
+
const ERROR_COPY = {
|
|
183
|
+
REQUIRED_VALUE_ERROR: (f) => `${labelOf(f)} is required.`,
|
|
184
|
+
MIN_LENGTH_ERROR: (f) => `${labelOf(f)} must be at least ${validationOf(f).minLength} characters.`,
|
|
185
|
+
MAX_LENGTH_ERROR: (f) => `${labelOf(f)} must be at most ${validationOf(f).maxLength} characters.`,
|
|
186
|
+
MIN_VALUE_ERROR: (f) => `${labelOf(f)} must be ${minimumOf(f) ?? "higher"} or more.`,
|
|
187
|
+
MAX_VALUE_ERROR: (f) => `${labelOf(f)} must be ${maximumOf(f) ?? "lower"} or less.`,
|
|
188
|
+
FORMAT_ERROR: (f) => FORMAT_COPY[validationOf(f).format]?.(f) ?? `Please check ${labelOf(f)}.`,
|
|
189
|
+
PATTERN_ERROR: (f) => `${labelOf(f)} is not in the expected format.`,
|
|
190
|
+
NOT_ALLOWED_VALUE_ERROR: (f) => `Choose one of the listed options for ${labelOf(f)}.`,
|
|
191
|
+
TYPE_ERROR: (f) => `Please check ${labelOf(f)}.`,
|
|
192
|
+
UNKNOWN_VALUE_ERROR: (f) => `Please check ${labelOf(f)}.`,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Turn a failed `createSubmission` into per-control messages, keyed by input NAME — so each message
|
|
197
|
+
* lands on its own control instead of only a form-level banner. An address subfield's server path
|
|
198
|
+
* (`address/subdivision`) is exactly how its control is named, so it maps straight through.
|
|
199
|
+
*
|
|
200
|
+
* The copy is written from `errorType` plus the field's own schema; Wix's `errorMessage` is the
|
|
201
|
+
* validator's internal wording, so it goes to `console.debug` only. An unmapped `errorType` degrades
|
|
202
|
+
* to safe copy — the enum grows, and MIN/MAX_VALUE_ERROR, MIN/MAX_ITEMS_ERROR and
|
|
203
|
+
* DISABLED_FORM_ERROR are all reachable without being listed above.
|
|
204
|
+
*
|
|
205
|
+
* ⚠️ Two rejections here are SEED bugs, not frontend bugs — do NOT mangle the key or the value to
|
|
206
|
+
* work around them (fix them in `seed/SEED.md`):
|
|
207
|
+
* • `UNKNOWN_VALUE_ERROR` on a key that IS in the schema → the field was seeded with no
|
|
208
|
+
* `validation` block, and that block is what registers the target as an accepted value.
|
|
209
|
+
* • `NOT_ALLOWED_VALUE_ERROR` on a choice field → the seed's `options[].value` and its validation
|
|
210
|
+
* enum disagree; the two declarations must match.
|
|
211
|
+
*
|
|
212
|
+
* @param {Error & { body?: object }} err
|
|
213
|
+
* @param {object[]} fields The raw fields being rendered.
|
|
214
|
+
* @returns {Record<string, string>} Empty when the failure wasn't a validation error.
|
|
215
|
+
*/
|
|
216
|
+
export function mapSubmissionErrors(err, fields) {
|
|
217
|
+
const byTarget = new Map(fields.map((f) => [targetOf(f), f]));
|
|
218
|
+
const errors = {};
|
|
219
|
+
for (const violation of submissionViolations(err)) {
|
|
220
|
+
const field = byTarget.get(violation.errorPath.split("/")[0]);
|
|
221
|
+
if (!field) continue;
|
|
222
|
+
console.debug("wix-forms: server violation", violation.errorPath, violation.errorType, violation.errorMessage);
|
|
223
|
+
errors[violation.errorPath] = ERROR_COPY[violation.errorType]?.(field) ?? `Please check ${labelOf(field)}.`;
|
|
224
|
+
}
|
|
225
|
+
return errors;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Move focus to a control by input name. ⚠️ `namedItem` returns a `RadioNodeList` for a radio or
|
|
230
|
+
* checkbox group and an element for everything else — a guard that checks only for an element
|
|
231
|
+
* silently skips every choice group.
|
|
232
|
+
*/
|
|
233
|
+
function focusControl(formEl, name) {
|
|
234
|
+
const control = formEl?.elements?.namedItem?.(name);
|
|
235
|
+
const node =
|
|
236
|
+
typeof RadioNodeList !== "undefined" && control instanceof RadioNodeList ? control[0] : control;
|
|
237
|
+
node?.focus?.();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {string} formId The form's GUID — read it from `WIX_FORMS` in `rest/wix-forms.config.js`
|
|
242
|
+
* (written by the seed); never type a literal id into a component.
|
|
243
|
+
* @returns {{
|
|
244
|
+
* form: object|null,
|
|
245
|
+
* // The Form exactly as Wix returned it, once loaded. Nothing is stripped or renamed: the
|
|
246
|
+
* // fields to render are `read.orderedInputs(form)`, the submit wording
|
|
247
|
+
* // `read.submitTextOf(form)`. Never hardcode a field list.
|
|
248
|
+
* values: Record<string, unknown>,
|
|
249
|
+
* // The form's state, keyed by each field's `target`: a string for text/choice/date, an ARRAY
|
|
250
|
+
* // for a multi-choice field, an OBJECT for an address. Bind every control's `value` to it.
|
|
251
|
+
* setValues: (next: object | ((prev: object) => object)) => void,
|
|
252
|
+
* // The plain React setter — `setValues((v) => ({ ...v, [name]: value }))` from onChange.
|
|
253
|
+
* bind: (target: string) => object,
|
|
254
|
+
* // The props a text-ish control needs, ready to spread: name, value, onChange, onBlur and the
|
|
255
|
+
* // two aria attributes. `<input {...bind(target)} type="email" required />`.
|
|
256
|
+
* submit: (event?: SubmitEvent) => Promise<boolean>,
|
|
257
|
+
* // Use as `onSubmit`. Validates, submits, and resolves TRUE when the submission was created —
|
|
258
|
+
* // that resolved true IS the success signal (a visitor can't read submissions back), so flip
|
|
259
|
+
* // your own thank-you state on it. FALSE means `errors` now says why.
|
|
260
|
+
* validate: (target?: string) => boolean,
|
|
261
|
+
* // `validate("email")` checks that one field, `validate("address/city")` one address subfield,
|
|
262
|
+
* // `validate()` the whole form. Every rule comes from the schema — `required`,
|
|
263
|
+
* // `minLength`/`maxLength`, `pattern`, `format` (EMAIL/PHONE/URL) — never from a field's name.
|
|
264
|
+
* // Writes `errors` (clearing what now passes) and returns whether what it checked passed.
|
|
265
|
+
* errors: Record<string, string>,
|
|
266
|
+
* // input name → visitor-facing message. The name is the field's `target`, or `target/sub` for
|
|
267
|
+
* // an address subfield. `errors[FORM_ERROR]` holds a form-level message (load or submit).
|
|
268
|
+
* loading: boolean,
|
|
269
|
+
* // Busy: loading the schema (`form` still null) or submitting (`form` set).
|
|
270
|
+
* read: typeof import("@/lib/wix-form-schema-utils"),
|
|
271
|
+
* // The schema accessors — functions, not data: `read.labelOf(field)`, `read.choicesOf(field)`,
|
|
272
|
+
* // `read.submitTextOf(form)`, … Handed back so the UI needs one import.
|
|
273
|
+
* }}
|
|
274
|
+
*/
|
|
275
|
+
export function useWixForm(formId) {
|
|
276
|
+
const [form, setForm] = useState(null);
|
|
277
|
+
const [values, setValues] = useState({});
|
|
278
|
+
const [errors, setErrors] = useState({});
|
|
279
|
+
const [loading, setLoading] = useState(true);
|
|
280
|
+
// The empty form to fall back to after a successful submit — the schema's own defaults.
|
|
281
|
+
const emptyRef = useRef({});
|
|
282
|
+
|
|
283
|
+
// Every visible INPUT the schema lays out, in the owner's order — the list the hook seeds values
|
|
284
|
+
// from, validates and submits. The UI gets it the same way (`read.orderedInputs(form)`), so there
|
|
285
|
+
// is no second copy to keep in sync and no filtered subset to explain.
|
|
286
|
+
const fields = useMemo(() => orderedInputs(form), [form]);
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
let live = true;
|
|
290
|
+
if (!formId) {
|
|
291
|
+
setLoading(false);
|
|
292
|
+
setErrors({ [FORM_ERROR]: "No formId — pass one from WIX_FORMS (the seed writes it)." });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
setLoading(true);
|
|
296
|
+
getForm(formId)
|
|
297
|
+
.then((loaded) => {
|
|
298
|
+
if (!live) return;
|
|
299
|
+
setForm(loaded);
|
|
300
|
+
setErrors({});
|
|
301
|
+
setLoading(false);
|
|
302
|
+
})
|
|
303
|
+
.catch((e) => {
|
|
304
|
+
if (!live) return;
|
|
305
|
+
// Fail loudly. A form that can't load is a setup problem (wrong id, Forms app missing) —
|
|
306
|
+
// never fall back to a hand-built form, which would drop real enquiries silently.
|
|
307
|
+
setErrors({ [FORM_ERROR]: e.message || "Could not load the form." });
|
|
308
|
+
setLoading(false);
|
|
309
|
+
});
|
|
310
|
+
return () => {
|
|
311
|
+
live = false;
|
|
312
|
+
};
|
|
313
|
+
}, [formId]);
|
|
314
|
+
|
|
315
|
+
// Seed the controls once the schema is in: every control is controlled from the first render, so
|
|
316
|
+
// each `target` must hold a value of the right shape before any of them mount.
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
const empty = defaultValues(fields);
|
|
319
|
+
emptyRef.current = empty;
|
|
320
|
+
setValues(empty);
|
|
321
|
+
}, [fields]);
|
|
322
|
+
|
|
323
|
+
const validate = useCallback(
|
|
324
|
+
(target) => {
|
|
325
|
+
// No target → the whole form. This is what `submit` runs before it sends anything.
|
|
326
|
+
if (!target) {
|
|
327
|
+
const all = errorsForForm(fields, values);
|
|
328
|
+
setErrors(all);
|
|
329
|
+
return Object.keys(all).length === 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const key = String(target);
|
|
333
|
+
const field = fields.find((f) => targetOf(f) === key.split("/")[0]);
|
|
334
|
+
if (!field) return true;
|
|
335
|
+
|
|
336
|
+
// The error keys this call owns, so a re-check CLEARS what it fixed as well as flagging what
|
|
337
|
+
// it didn't: one key for a plain field, or all of an address's subfields (`target/sub`) when
|
|
338
|
+
// the address itself is named. Naming a single subfield scopes it to that one.
|
|
339
|
+
const owned = key.includes("/")
|
|
340
|
+
? [key]
|
|
341
|
+
: isAddress(field)
|
|
342
|
+
? addressPartsOf(field).map(({ sub }) => `${targetOf(field)}/${sub}`)
|
|
343
|
+
: [targetOf(field)];
|
|
344
|
+
|
|
345
|
+
const found = errorsForField(field, values);
|
|
346
|
+
setErrors((prev) => {
|
|
347
|
+
const next = { ...prev };
|
|
348
|
+
for (const k of owned) {
|
|
349
|
+
delete next[k];
|
|
350
|
+
if (found[k]) next[k] = found[k];
|
|
351
|
+
}
|
|
352
|
+
return next;
|
|
353
|
+
});
|
|
354
|
+
return owned.every((k) => !found[k]);
|
|
355
|
+
},
|
|
356
|
+
[fields, values],
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
// Everything a text-ish control needs, in one spread: `<input {...bind("email_a1b2")} type="email" />`.
|
|
360
|
+
// Covers input / textarea / select. A checkbox or radio group carries `checked` instead of `value`
|
|
361
|
+
// and a file input can't be controlled at all — wire those two by hand (INSTRUCTIONS.md, step 2),
|
|
362
|
+
// keeping the same `name`, `onBlur` and `aria-*` contract.
|
|
363
|
+
const bind = useCallback(
|
|
364
|
+
(target) => ({
|
|
365
|
+
name: target,
|
|
366
|
+
value: values[target] ?? "",
|
|
367
|
+
onChange: (event) => setValues((prev) => ({ ...prev, [target]: event.target.value })),
|
|
368
|
+
onBlur: () => validate(target),
|
|
369
|
+
"aria-describedby": `err-${target}`,
|
|
370
|
+
"aria-invalid": errors[target] ? true : undefined,
|
|
371
|
+
}),
|
|
372
|
+
[values, errors, validate],
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const submit = useCallback(
|
|
376
|
+
async (event) => {
|
|
377
|
+
event?.preventDefault?.();
|
|
378
|
+
// Capture the <form> now: React clears `currentTarget` once the handler returns, so reading it
|
|
379
|
+
// after the await below (to focus a server-rejected control) would come back null.
|
|
380
|
+
const formEl = event?.currentTarget ?? null;
|
|
381
|
+
if (!form) return false;
|
|
382
|
+
|
|
383
|
+
// Client pass first, so the visitor gets inline feedback in OUR wording before a round trip.
|
|
384
|
+
const clientErrors = errorsForForm(fields, values);
|
|
385
|
+
if (Object.keys(clientErrors).length) {
|
|
386
|
+
setErrors(clientErrors);
|
|
387
|
+
// FOCUS the first invalid control — `scrollIntoView` moves the viewport and nothing else,
|
|
388
|
+
// leaving a keyboard or screen-reader user where they were. The <form> comes from the event,
|
|
389
|
+
// and each control is found by its `name` (which is its `target`).
|
|
390
|
+
focusControl(formEl, Object.keys(clientErrors)[0]);
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
setLoading(true);
|
|
395
|
+
try {
|
|
396
|
+
// Any picked files go up FIRST — a `File` object is not something the submission API takes,
|
|
397
|
+
// and its value is the upload URL this hands back. No file fields → nothing happens here.
|
|
398
|
+
const uploaded = await uploadFormFiles(form.id, fields, values);
|
|
399
|
+
// Resolves on CONFIRMED, PENDING or PAYMENT_WAITING — all three are created submissions.
|
|
400
|
+
// There is nothing to read back, so this is the only confirmation there is. (A form that
|
|
401
|
+
// collects payment leaves the visitor a payment step; call createSubmission directly if you
|
|
402
|
+
// need its `status` to surface that inside your success state.)
|
|
403
|
+
await createSubmission(form.id, toSubmissionValues(fields, uploaded));
|
|
404
|
+
setErrors({});
|
|
405
|
+
setValues(emptyRef.current); // back to the schema's defaults, ready for another
|
|
406
|
+
return true;
|
|
407
|
+
} catch (e) {
|
|
408
|
+
// Per-field violations land on their controls; anything else is a form-level message.
|
|
409
|
+
const mapped = mapSubmissionErrors(e, fields);
|
|
410
|
+
if (Object.keys(mapped).length) {
|
|
411
|
+
setErrors(mapped);
|
|
412
|
+
focusControl(formEl, Object.keys(mapped)[0]);
|
|
413
|
+
} else {
|
|
414
|
+
setErrors({ [FORM_ERROR]: e.message || "Could not send the form. Please try again." });
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
} finally {
|
|
418
|
+
setLoading(false);
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
[fields, form, values],
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
return { form, values, setValues, bind, submit, validate, errors, loading, read };
|
|
425
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Reading a Wix Form schema — plain functions over the RAW `Form` and its `formFields[]`. No React,
|
|
2
|
+
// no REST, no projection: the field object Wix returns IS the model the UI renders from, and these
|
|
3
|
+
// are the accessors that make it readable.
|
|
4
|
+
//
|
|
5
|
+
// import * as read from "@/lib/wix-form-schema-utils";
|
|
6
|
+
// read.orderedInputs(form).map((field) => <Field key={read.targetOf(field)} field={field} />)
|
|
7
|
+
//
|
|
8
|
+
// Nothing is guessed, and nothing is dropped — which matters because the schema nests a field's
|
|
9
|
+
// settings TWO levels deep, under blocks named after its own enums:
|
|
10
|
+
//
|
|
11
|
+
// inputOptions
|
|
12
|
+
// ├─ target / inputType / required ← the field's identity
|
|
13
|
+
// └─ stringOptions | arrayOptions | … ← named after `inputType` (`optionsOf`)
|
|
14
|
+
// ├─ componentType / validation ← the control, and its rules (`validationOf`)
|
|
15
|
+
// └─ dropdownOptions | textInputOptions | … ← named after `componentType` (`componentOf`)
|
|
16
|
+
// └─ label / placeholder / options / default
|
|
17
|
+
//
|
|
18
|
+
// ⚠️ Those two block names are the whole reason this file exists. They are looked up in the tables
|
|
19
|
+
// below rather than hand-written at the call site, because `inputOptions.stringOptions.dropdownOptions`
|
|
20
|
+
// spelled out in a component is one typo away from silently reading back no label at all.
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `inputType` → the block holding that type's `componentType`, `validation`, and component block.
|
|
24
|
+
* Every input type the Form Schemas API defines
|
|
25
|
+
* (https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields.md).
|
|
26
|
+
*/
|
|
27
|
+
const INPUT_BLOCK = {
|
|
28
|
+
STRING: "stringOptions",
|
|
29
|
+
NUMBER: "numberOptions",
|
|
30
|
+
BOOLEAN: "booleanOptions",
|
|
31
|
+
ARRAY: "arrayOptions",
|
|
32
|
+
ADDRESS: "addressOptions",
|
|
33
|
+
WIX_FILE: "wixFileOptions",
|
|
34
|
+
PAYMENT: "paymentOptions",
|
|
35
|
+
SCHEDULING: "schedulingOptions",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `componentType` → the block holding that control's `label`, `placeholder`, `options`, `default`.
|
|
40
|
+
* Note several fields share one component — short AND long answer are both `TEXT_INPUT`, image
|
|
41
|
+
* choice and multi choice are both `CHECKBOX_GROUP` — which is why the render branches on
|
|
42
|
+
* `identifier` too.
|
|
43
|
+
*/
|
|
44
|
+
const COMPONENT_BLOCK = {
|
|
45
|
+
TEXT_INPUT: "textInputOptions",
|
|
46
|
+
NUMBER_INPUT: "numberInputOptions",
|
|
47
|
+
RATING_INPUT: "ratingInputOptions",
|
|
48
|
+
PHONE_INPUT: "phoneInputOptions",
|
|
49
|
+
DATE_INPUT: "dateInputOptions",
|
|
50
|
+
DATE_PICKER: "datePickerOptions",
|
|
51
|
+
DATE_TIME: "dateTimeOptions",
|
|
52
|
+
TIME_INPUT: "timeInputOptions",
|
|
53
|
+
CHECKBOX: "checkboxOptions",
|
|
54
|
+
CHECKBOX_GROUP: "checkboxGroupOptions",
|
|
55
|
+
RADIO_GROUP: "radioGroupOptions",
|
|
56
|
+
DROPDOWN: "dropdownOptions",
|
|
57
|
+
TAGS: "tagsOptions",
|
|
58
|
+
MULTILINE_ADDRESS: "multilineAddressOptions",
|
|
59
|
+
FILE_UPLOAD: "fileUploadOptions",
|
|
60
|
+
SIGNATURE: "signatureOptions",
|
|
61
|
+
FIXED_PAYMENT: "fixedPaymentOptions",
|
|
62
|
+
PAYMENT_INPUT: "paymentInputOptions",
|
|
63
|
+
DONATION_INPUT: "donationInputOptions",
|
|
64
|
+
APPOINTMENT: "appointmentOptions",
|
|
65
|
+
SERVICES_DROPDOWN: "servicesDropdownOptions",
|
|
66
|
+
SERVICES_CHECKBOX_GROUP: "servicesCheckboxGroupOptions",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// An enum missing from a table means Wix added a type — say so ONCE (these run per field per
|
|
70
|
+
// render) instead of quietly handing back an empty block and a field labelled by its target.
|
|
71
|
+
const warned = new Set();
|
|
72
|
+
function blockName(table, key, what) {
|
|
73
|
+
const name = table[key];
|
|
74
|
+
if (!name && key && !warned.has(key)) {
|
|
75
|
+
warned.add(key);
|
|
76
|
+
console.warn(
|
|
77
|
+
`wix-forms: no ${what} block mapped for "${key}" — that field will render without its label ` +
|
|
78
|
+
`or options. Add it to ${what === "inputType" ? "INPUT_BLOCK" : "COMPONENT_BLOCK"} in lib/wix-form-schema-utils.js.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return name;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The field's immutable storage key — the input's `name`, and THE submission key. */
|
|
85
|
+
export const targetOf = (field) => field?.inputOptions?.target;
|
|
86
|
+
|
|
87
|
+
/** STRING | NUMBER | ARRAY | ADDRESS | WIX_FILE | BOOLEAN | PAYMENT | SCHEDULING. */
|
|
88
|
+
export const inputTypeOf = (field) => field?.inputOptions?.inputType;
|
|
89
|
+
|
|
90
|
+
/** ⚠️ From `inputOptions`, not from `validation`. */
|
|
91
|
+
export const isRequired = (field) => field?.inputOptions?.required ?? false;
|
|
92
|
+
|
|
93
|
+
/** The input-type block: `componentType`, `validation`, and the component's own sub-block. */
|
|
94
|
+
export const optionsOf = (field) =>
|
|
95
|
+
field?.inputOptions?.[blockName(INPUT_BLOCK, inputTypeOf(field), "inputType")] ?? {};
|
|
96
|
+
|
|
97
|
+
/** DROPDOWN | RADIO_GROUP | CHECKBOX_GROUP | TEXT_INPUT | NUMBER_INPUT | FILE_UPLOAD | … */
|
|
98
|
+
export const componentTypeOf = (field) => optionsOf(field).componentType;
|
|
99
|
+
|
|
100
|
+
/** The component sub-block: `label`, `placeholder`, `options`, `default`, … */
|
|
101
|
+
export const componentOf = (field) =>
|
|
102
|
+
optionsOf(field)[blockName(COMPONENT_BLOCK, componentTypeOf(field), "componentType")] ?? {};
|
|
103
|
+
|
|
104
|
+
/** `format`, `minLength`, `maxLength`, `pattern`, `fields` (address), `fileLimit`, `phoneOptions`, … */
|
|
105
|
+
export const validationOf = (field) => optionsOf(field).validation ?? {};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The owner's label, falling back to the target so a control is never nameless.
|
|
109
|
+
*
|
|
110
|
+
* ⚠️ The fallback also covers a label that ISN'T A STRING: a BOOLEAN field (consent checkbox) labels
|
|
111
|
+
* itself with a Ricos rich-content object, which would otherwise reach the page — and any error copy
|
|
112
|
+
* built from it — as the text `[object Object]`. Render that field's label from
|
|
113
|
+
* `componentOf(field).label` yourself if you write a branch for it.
|
|
114
|
+
*/
|
|
115
|
+
export const labelOf = (field) => {
|
|
116
|
+
const label = componentOf(field).label;
|
|
117
|
+
return typeof label === "string" && label ? label : targetOf(field);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/** Multi-choice: submits an ARRAY of the checked option values. */
|
|
121
|
+
export const isMulti = (field) => inputTypeOf(field) === "ARRAY";
|
|
122
|
+
|
|
123
|
+
/** File upload or signature: holds `File` objects until submit swaps them for their upload URLs. */
|
|
124
|
+
export const isFile = (field) => inputTypeOf(field) === "WIX_FILE";
|
|
125
|
+
|
|
126
|
+
/** NUMBER covers both the number input and a rating — the value goes on the wire as a number. */
|
|
127
|
+
export const isNumber = (field) => inputTypeOf(field) === "NUMBER";
|
|
128
|
+
|
|
129
|
+
/** An ADDRESS submits a nested OBJECT, so its subfields are their own controls. */
|
|
130
|
+
export const isAddress = (field) => inputTypeOf(field) === "ADDRESS";
|
|
131
|
+
|
|
132
|
+
/** Choice options as `{ value, label }` — a label is optional in the schema, the value never is. */
|
|
133
|
+
export const choicesOf = (field) =>
|
|
134
|
+
componentOf(field).options?.map((o) => ({ value: o.value, label: o.label ?? o.value })) ?? [];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* An ADDRESS field's subfields as `{ sub, required }`, in schema order. `validation.fields` lists
|
|
138
|
+
* them; only `addressLine2` carries a visibility setting, so this hides just that one when the owner
|
|
139
|
+
* turned it off.
|
|
140
|
+
*/
|
|
141
|
+
export const addressPartsOf = (field) =>
|
|
142
|
+
Object.entries(validationOf(field).fields ?? {})
|
|
143
|
+
.filter(([sub]) => componentOf(field).fieldSettings?.[sub]?.show !== false)
|
|
144
|
+
.map(([sub, cfg]) => ({ sub, required: cfg?.required ?? false }));
|
|
145
|
+
|
|
146
|
+
/** The country a PHONE field's example should use — the FIELD's own before the site's. */
|
|
147
|
+
export const phoneCountryOf = (field) =>
|
|
148
|
+
componentOf(field).defaultCountryCode ?? validationOf(field).phoneOptions?.allowedCountryCodes?.[0];
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Every visible INPUT field, in the order the owner laid out.
|
|
152
|
+
*
|
|
153
|
+
* ⚠️ Display order comes from `steps[].layout`, NOT from `formFields[]` array order. Take the order
|
|
154
|
+
* only — the geometry (`row`/`column`/`width`) is Wix's editor layout, which your own design
|
|
155
|
+
* replaces. A field the owner never placed sorts LAST; it still stores values.
|
|
156
|
+
*
|
|
157
|
+
* The submit button is `fieldType: "DISPLAY"`, so filtering to INPUT drops it automatically.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} form A Form from `getForm` / `listForms`.
|
|
160
|
+
* @returns {object[]} Raw field objects — read them with the accessors above.
|
|
161
|
+
*/
|
|
162
|
+
export function orderedInputs(form) {
|
|
163
|
+
const order = new Map(
|
|
164
|
+
(form?.steps ?? [])
|
|
165
|
+
// Sort WITHIN each step, then concatenate in step order: `row` restarts at 0 in every step,
|
|
166
|
+
// so one sort across the flattened list interleaves them — step 1's second field would land
|
|
167
|
+
// after step 2's first.
|
|
168
|
+
.flatMap((s) =>
|
|
169
|
+
(s.layout?.large?.items ?? s.layout?.medium?.items ?? s.layout?.small?.items ?? [])
|
|
170
|
+
.slice()
|
|
171
|
+
.sort((a, b) => a.row - b.row || a.column - b.column),
|
|
172
|
+
)
|
|
173
|
+
.map((item, i) => [item.fieldId, i]),
|
|
174
|
+
);
|
|
175
|
+
return (form?.formFields ?? [])
|
|
176
|
+
.filter((f) => f.fieldType === "INPUT" && !f.hidden)
|
|
177
|
+
.sort((a, b) => (order.get(a.id) ?? Infinity) - (order.get(b.id) ?? Infinity));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The owner's submit-button wording, or "" when they didn't set one. It lives on the SUBMIT_BUTTON —
|
|
182
|
+
* a DISPLAY field, so it never appears in `orderedInputs` — and nests under `pageNavigationOptions`
|
|
183
|
+
* because one control drives both multi-page navigation and the final submit.
|
|
184
|
+
*/
|
|
185
|
+
export const submitTextOf = (form) =>
|
|
186
|
+
(form?.formFields ?? []).find((f) => f.identifier === "SUBMIT_BUTTON")
|
|
187
|
+
?.displayOptions?.pageNavigationOptions?.submitText ?? "";
|