@wix/agent-skills 1.17.0 → 1.18.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/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 +74 -83
- package/skills/wix-app/references/SITE_PLUGIN.md +12 -0
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
- 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/ANIMATED-COMPONENTS.md +12 -11
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +1 -0
- package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +16 -8
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +47 -14
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +3 -0
- package/skills/wix-base44-connector/SKILL.md +133 -44
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
- package/skills/wix-base44-connector/scripts/utils.js +216 -30
- 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 +4 -1
- 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 +26 -8
- 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-rentals.md +254 -0
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +57 -10
- 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/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 +113 -89
- package/skills/wix-manage/references/dashboard-navigation/routes.json +12 -52
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-api.md +0 -2
- package/skills/wix-manage/references/forms/create-form.md +272 -184
- package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +13 -10
- package/skills/wix-manage/references/forms/update-form.md +122 -0
- package/skills/wix-manage/references/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/media/generate-image-with-ai.md +86 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +37 -7
- package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
- 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 +3 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44.md +27 -61
- package/skills/wix-vibe-headless/platforms/generic.md +6 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +43 -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-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 +486 -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 +1836 -0
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +137 -84
- 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-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-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,155 @@
|
|
|
1
|
+
// Shipped custom login form. The member stays on this site: credential calls exchange
|
|
2
|
+
// directly into member tokens; only logout leaves the app.
|
|
3
|
+
import { FormEvent, useState } from "react";
|
|
4
|
+
import { useMember } from "../../hooks/members/useMember";
|
|
5
|
+
|
|
6
|
+
export interface LoginFormProps {
|
|
7
|
+
onSuccess?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function LoginForm({ onSuccess }: LoginFormProps) {
|
|
11
|
+
const { login, register, verifyEmail, error } = useMember();
|
|
12
|
+
const [mode, setMode] = useState<"login" | "register">("login");
|
|
13
|
+
const [email, setEmail] = useState("");
|
|
14
|
+
const [password, setPassword] = useState("");
|
|
15
|
+
const [firstName, setFirstName] = useState("");
|
|
16
|
+
const [lastName, setLastName] = useState("");
|
|
17
|
+
const [code, setCode] = useState("");
|
|
18
|
+
const [verification, setVerification] = useState(false);
|
|
19
|
+
const [pending, setPending] = useState(false);
|
|
20
|
+
const [busy, setBusy] = useState(false);
|
|
21
|
+
|
|
22
|
+
function done() {
|
|
23
|
+
if (onSuccess) return onSuccess();
|
|
24
|
+
const returnTo = new URLSearchParams(window.location.search).get(
|
|
25
|
+
"returnTo",
|
|
26
|
+
);
|
|
27
|
+
window.location.assign(returnTo?.startsWith("/") ? returnTo : "/account");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function submit(event: FormEvent) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
setBusy(true);
|
|
33
|
+
try {
|
|
34
|
+
const result =
|
|
35
|
+
mode === "login"
|
|
36
|
+
? await login(email, password)
|
|
37
|
+
: await register(email, password, { firstName, lastName });
|
|
38
|
+
if (result.state === "SUCCESS") done();
|
|
39
|
+
if (result.state === "EMAIL_VERIFICATION_REQUIRED") setVerification(true);
|
|
40
|
+
if (result.state === "OWNER_APPROVAL_REQUIRED") setPending(true);
|
|
41
|
+
} finally {
|
|
42
|
+
setBusy(false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function submitCode(event: FormEvent) {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
setBusy(true);
|
|
49
|
+
try {
|
|
50
|
+
const result = await verifyEmail(code);
|
|
51
|
+
if (result.state === "SUCCESS") done();
|
|
52
|
+
if (result.state === "OWNER_APPROVAL_REQUIRED") setPending(true);
|
|
53
|
+
} finally {
|
|
54
|
+
setBusy(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (pending)
|
|
59
|
+
return (
|
|
60
|
+
<p className="rounded-lg border border-border p-4 text-sm">
|
|
61
|
+
Your account is awaiting approval.
|
|
62
|
+
</p>
|
|
63
|
+
);
|
|
64
|
+
if (verification) {
|
|
65
|
+
return (
|
|
66
|
+
<form onSubmit={submitCode} className="space-y-4">
|
|
67
|
+
<label className="block text-sm">
|
|
68
|
+
Verification code
|
|
69
|
+
<input
|
|
70
|
+
required
|
|
71
|
+
value={code}
|
|
72
|
+
onChange={(event) => setCode(event.target.value)}
|
|
73
|
+
className="mt-1 w-full rounded border border-border bg-background p-2"
|
|
74
|
+
/>
|
|
75
|
+
</label>
|
|
76
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
77
|
+
<button
|
|
78
|
+
disabled={busy}
|
|
79
|
+
className="rounded bg-primary px-4 py-2 text-primary-foreground"
|
|
80
|
+
>
|
|
81
|
+
{busy ? "Verifying…" : "Verify"}
|
|
82
|
+
</button>
|
|
83
|
+
</form>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return (
|
|
87
|
+
<form onSubmit={submit} className="space-y-4">
|
|
88
|
+
<div className="flex gap-2">
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={() => setMode("login")}
|
|
92
|
+
className="text-sm underline"
|
|
93
|
+
>
|
|
94
|
+
Sign in
|
|
95
|
+
</button>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
onClick={() => setMode("register")}
|
|
99
|
+
className="text-sm underline"
|
|
100
|
+
>
|
|
101
|
+
Sign up
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
{mode === "register" && (
|
|
105
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
106
|
+
<label className="text-sm">
|
|
107
|
+
First name
|
|
108
|
+
<input
|
|
109
|
+
value={firstName}
|
|
110
|
+
onChange={(event) => setFirstName(event.target.value)}
|
|
111
|
+
className="mt-1 w-full rounded border border-border bg-background p-2"
|
|
112
|
+
/>
|
|
113
|
+
</label>
|
|
114
|
+
<label className="text-sm">
|
|
115
|
+
Last name
|
|
116
|
+
<input
|
|
117
|
+
value={lastName}
|
|
118
|
+
onChange={(event) => setLastName(event.target.value)}
|
|
119
|
+
className="mt-1 w-full rounded border border-border bg-background p-2"
|
|
120
|
+
/>
|
|
121
|
+
</label>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
<label className="block text-sm">
|
|
125
|
+
Email
|
|
126
|
+
<input
|
|
127
|
+
required
|
|
128
|
+
type="email"
|
|
129
|
+
autoComplete="email"
|
|
130
|
+
value={email}
|
|
131
|
+
onChange={(event) => setEmail(event.target.value)}
|
|
132
|
+
className="mt-1 w-full rounded border border-border bg-background p-2"
|
|
133
|
+
/>
|
|
134
|
+
</label>
|
|
135
|
+
<label className="block text-sm">
|
|
136
|
+
Password
|
|
137
|
+
<input
|
|
138
|
+
required
|
|
139
|
+
type="password"
|
|
140
|
+
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
|
141
|
+
value={password}
|
|
142
|
+
onChange={(event) => setPassword(event.target.value)}
|
|
143
|
+
className="mt-1 w-full rounded border border-border bg-background p-2"
|
|
144
|
+
/>
|
|
145
|
+
</label>
|
|
146
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
147
|
+
<button
|
|
148
|
+
disabled={busy}
|
|
149
|
+
className="rounded bg-primary px-4 py-2 text-primary-foreground"
|
|
150
|
+
>
|
|
151
|
+
{busy ? "Working…" : mode === "login" ? "Sign in" : "Create account"}
|
|
152
|
+
</button>
|
|
153
|
+
</form>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// REFERENCE header account control on the @theme tokens: "Log in" for a visitor; account
|
|
2
|
+
// link + log-out for a member. Correct and complete; per the skill's model you design your
|
|
3
|
+
// own on useMember. Mount client:only="react" — it reads browser session state.
|
|
4
|
+
import type { ComponentType, ReactNode } from "react";
|
|
5
|
+
import { useMember } from "../../hooks/members/useMember";
|
|
6
|
+
|
|
7
|
+
export interface LinkLikeProps {
|
|
8
|
+
href: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PlainLink = ({ href, className, children }: LinkLikeProps) => (
|
|
14
|
+
<a href={href} className={className}>
|
|
15
|
+
{children}
|
|
16
|
+
</a>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export interface MemberMenuProps {
|
|
20
|
+
accountHref?: string;
|
|
21
|
+
LinkComponent?: ComponentType<LinkLikeProps>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function MemberMenu({
|
|
25
|
+
accountHref = "/account",
|
|
26
|
+
LinkComponent = PlainLink,
|
|
27
|
+
}: MemberMenuProps) {
|
|
28
|
+
const { member, loggedIn, loading, logout } = useMember();
|
|
29
|
+
|
|
30
|
+
if (loading) {
|
|
31
|
+
return (
|
|
32
|
+
<span
|
|
33
|
+
className="inline-block h-8 w-16 animate-pulse rounded-full bg-secondary"
|
|
34
|
+
aria-hidden="true"
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!loggedIn) {
|
|
40
|
+
return (
|
|
41
|
+
<a
|
|
42
|
+
href="/login"
|
|
43
|
+
className="rounded-full bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90"
|
|
44
|
+
>
|
|
45
|
+
Log in
|
|
46
|
+
</a>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<span className="flex items-center gap-3">
|
|
52
|
+
<LinkComponent
|
|
53
|
+
href={accountHref}
|
|
54
|
+
className="flex items-center gap-2 text-sm font-medium text-foreground no-underline transition-colors hover:text-muted-foreground"
|
|
55
|
+
>
|
|
56
|
+
{member?.photoUrl ? (
|
|
57
|
+
<img
|
|
58
|
+
src={member.photoUrl}
|
|
59
|
+
alt=""
|
|
60
|
+
className="h-7 w-7 rounded-full object-cover"
|
|
61
|
+
/>
|
|
62
|
+
) : (
|
|
63
|
+
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-secondary text-xs font-semibold">
|
|
64
|
+
{(member?.displayName ?? "M").slice(0, 1).toUpperCase()}
|
|
65
|
+
</span>
|
|
66
|
+
)}
|
|
67
|
+
{member?.displayName ?? "My account"}
|
|
68
|
+
</LinkComponent>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
onClick={() => void logout()}
|
|
72
|
+
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
73
|
+
>
|
|
74
|
+
Log out
|
|
75
|
+
</button>
|
|
76
|
+
</span>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// REFERENCE gate for member-only surfaces on the REACT stack — on Astro, gate server-side
|
|
2
|
+
// in frontmatter instead (pages/account.astro shows the shape); a client-side check there
|
|
3
|
+
// flashes gated UI. Renders children only for a logged-in member; a visitor gets a login
|
|
4
|
+
// prompt in place (routing-free — no router or redirect assumptions).
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { useMember } from "../../hooks/members/useMember";
|
|
7
|
+
|
|
8
|
+
export interface RequireAuthProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
/** Rendered for an anonymous visitor instead of the default login prompt. */
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function RequireAuth({ children, fallback }: RequireAuthProps) {
|
|
15
|
+
const { loggedIn, loading } = useMember();
|
|
16
|
+
|
|
17
|
+
// Wait for the initial session read — deciding early bounces a logged-in member on first paint.
|
|
18
|
+
if (loading) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="py-16 text-center text-muted-foreground" aria-busy="true">
|
|
21
|
+
Loading…
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!loggedIn) {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
{fallback ?? (
|
|
30
|
+
<div className="mx-auto max-w-md rounded-lg border border-border p-8 text-center">
|
|
31
|
+
<p className="text-sm text-muted-foreground">
|
|
32
|
+
This page is for members.
|
|
33
|
+
</p>
|
|
34
|
+
<a
|
|
35
|
+
href={`/login?returnTo=${encodeURIComponent(window.location.pathname)}`}
|
|
36
|
+
className="mt-4 rounded-full bg-primary px-5 py-2 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90"
|
|
37
|
+
>
|
|
38
|
+
Log in / Sign up
|
|
39
|
+
</a>
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return <>{children}</>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// React binding for the member store. Works in any React root — Astro islands (several on
|
|
2
|
+
// one page share the same session) and SPAs alike. SSR-friendly: pass a server-resolved
|
|
3
|
+
// member as `initialMember` (Astro island props) and no client re-fetch happens.
|
|
4
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
5
|
+
import {
|
|
6
|
+
getMemberState,
|
|
7
|
+
hydrateMember,
|
|
8
|
+
login,
|
|
9
|
+
register,
|
|
10
|
+
logout,
|
|
11
|
+
refreshMember,
|
|
12
|
+
subscribeMember,
|
|
13
|
+
verifyEmail,
|
|
14
|
+
type MemberState,
|
|
15
|
+
} from "../../wix/members/member-store";
|
|
16
|
+
import type { LoginResult } from "../../wix/members/auth";
|
|
17
|
+
import type { CurrentMember } from "../../wix/members/types";
|
|
18
|
+
|
|
19
|
+
export interface UseMemberOptions {
|
|
20
|
+
/** Server-resolved member (null = a known-anonymous visitor); omit to resolve client-side. */
|
|
21
|
+
initialMember?: CurrentMember | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseMember extends MemberState {
|
|
25
|
+
/** Submit the shipped in-app credential form. */
|
|
26
|
+
login: (email: string, password: string) => Promise<LoginResult>;
|
|
27
|
+
register: (
|
|
28
|
+
email: string,
|
|
29
|
+
password: string,
|
|
30
|
+
profile?: { firstName?: string; lastName?: string },
|
|
31
|
+
) => Promise<LoginResult>;
|
|
32
|
+
verifyEmail: (code: string) => Promise<LoginResult>;
|
|
33
|
+
/** Log out through the Wix logout flow; navigates away. */
|
|
34
|
+
logout: (returnTo?: string) => Promise<void>;
|
|
35
|
+
refresh: () => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useMember({ initialMember }: UseMemberOptions = {}): UseMember {
|
|
39
|
+
// Adopt SSR props into the shared store before the first subscription (a no-op once any
|
|
40
|
+
// island has settled the session).
|
|
41
|
+
if (typeof window !== "undefined" && initialMember !== undefined)
|
|
42
|
+
hydrateMember(initialMember);
|
|
43
|
+
const server = useRef<MemberState | null>(null);
|
|
44
|
+
server.current ??=
|
|
45
|
+
initialMember !== undefined
|
|
46
|
+
? {
|
|
47
|
+
member: initialMember,
|
|
48
|
+
loggedIn: initialMember !== null,
|
|
49
|
+
loading: false,
|
|
50
|
+
error: null,
|
|
51
|
+
}
|
|
52
|
+
: getMemberState();
|
|
53
|
+
const state = useSyncExternalStore(
|
|
54
|
+
subscribeMember,
|
|
55
|
+
getMemberState,
|
|
56
|
+
() => server.current as MemberState,
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
...state,
|
|
60
|
+
login,
|
|
61
|
+
register,
|
|
62
|
+
verifyEmail,
|
|
63
|
+
logout,
|
|
64
|
+
refresh: refreshMember,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Custom, in-app member authentication. Credentials are submitted from the branded
|
|
2
|
+
// LoginForm; there is no Wix-hosted login-page redirect or callback route.
|
|
3
|
+
import { membersAuth } from "./client";
|
|
4
|
+
|
|
5
|
+
export type LoginState =
|
|
6
|
+
| "SUCCESS"
|
|
7
|
+
| "EMAIL_VERIFICATION_REQUIRED"
|
|
8
|
+
| "OWNER_APPROVAL_REQUIRED"
|
|
9
|
+
| "FAILURE";
|
|
10
|
+
|
|
11
|
+
export interface LoginResult {
|
|
12
|
+
state: LoginState;
|
|
13
|
+
stateToken?: string;
|
|
14
|
+
errorCode?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type AuthResponse = {
|
|
19
|
+
loginState?: LoginState;
|
|
20
|
+
data?: { sessionToken?: string };
|
|
21
|
+
stateToken?: string;
|
|
22
|
+
errorCode?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function loggedInHint(): boolean {
|
|
27
|
+
return membersAuth.loggedIn();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function finish(response: AuthResponse): Promise<LoginResult> {
|
|
31
|
+
const state = response.loginState ?? "FAILURE";
|
|
32
|
+
if (state === "SUCCESS") {
|
|
33
|
+
const sessionToken = response.data?.sessionToken;
|
|
34
|
+
if (!sessionToken)
|
|
35
|
+
return {
|
|
36
|
+
state: "FAILURE",
|
|
37
|
+
error: "Wix did not return a member session.",
|
|
38
|
+
};
|
|
39
|
+
const tokens =
|
|
40
|
+
await membersAuth.getMemberTokensForDirectLogin(sessionToken);
|
|
41
|
+
membersAuth.setTokens(tokens);
|
|
42
|
+
return { state };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
state,
|
|
46
|
+
...(response.stateToken ? { stateToken: response.stateToken } : {}),
|
|
47
|
+
...(response.errorCode ? { errorCode: response.errorCode } : {}),
|
|
48
|
+
...(response.error ? { error: response.error } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function loginMember(
|
|
53
|
+
email: string,
|
|
54
|
+
password: string,
|
|
55
|
+
): Promise<LoginResult> {
|
|
56
|
+
return finish((await membersAuth.login({ email, password })) as AuthResponse);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function registerMember(
|
|
60
|
+
email: string,
|
|
61
|
+
password: string,
|
|
62
|
+
profile?: { firstName?: string; lastName?: string },
|
|
63
|
+
): Promise<LoginResult> {
|
|
64
|
+
return finish(
|
|
65
|
+
(await membersAuth.register({
|
|
66
|
+
email,
|
|
67
|
+
password,
|
|
68
|
+
...(profile ? { profile } : {}),
|
|
69
|
+
})) as AuthResponse,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function verifyMemberEmail(
|
|
74
|
+
verificationCode: string,
|
|
75
|
+
): Promise<LoginResult> {
|
|
76
|
+
return finish(
|
|
77
|
+
(await membersAuth.processVerification({
|
|
78
|
+
verificationCode,
|
|
79
|
+
})) as AuthResponse,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function logoutMember(returnTo = "/"): Promise<void> {
|
|
84
|
+
const { logoutUrl } = await membersAuth.logout(
|
|
85
|
+
new URL(returnTo, window.location.origin).href,
|
|
86
|
+
);
|
|
87
|
+
window.location.assign(logoutUrl);
|
|
88
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Explicit custom-login client for the Members vertical. Unlike the shared SDK seam,
|
|
2
|
+
// this client is used in a browser island even on managed Astro: direct credential login
|
|
3
|
+
// cannot use the Wix-hosted /api/auth redirect flow.
|
|
4
|
+
import { createClient, EMPTY_TOKENS, OAuthStrategy } from "@wix/sdk";
|
|
5
|
+
import type { TokenStorage, Tokens } from "@wix/sdk";
|
|
6
|
+
import { members } from "@wix/members";
|
|
7
|
+
import { WIX_MEMBERS_CLIENT_ID } from "../../config";
|
|
8
|
+
|
|
9
|
+
// `@wix/astro` uses this cookie as the session source for both browser islands
|
|
10
|
+
// and the next server render. Direct login must write the exact same contract,
|
|
11
|
+
// so the member identity becomes the managed Astro identity too.
|
|
12
|
+
const SESSION_COOKIE_NAME = "wixSession";
|
|
13
|
+
|
|
14
|
+
const tokenStorage: TokenStorage = {
|
|
15
|
+
getTokens(): Tokens {
|
|
16
|
+
try {
|
|
17
|
+
const raw = document.cookie
|
|
18
|
+
.split("; ")
|
|
19
|
+
.find((cookie) => cookie.startsWith(`${SESSION_COOKIE_NAME}=`));
|
|
20
|
+
if (!raw) return EMPTY_TOKENS;
|
|
21
|
+
const session = JSON.parse(
|
|
22
|
+
decodeURIComponent(raw.slice(SESSION_COOKIE_NAME.length + 1)),
|
|
23
|
+
) as { clientId?: string; tokens?: Tokens };
|
|
24
|
+
return session.clientId === WIX_MEMBERS_CLIENT_ID && session.tokens
|
|
25
|
+
? session.tokens
|
|
26
|
+
: EMPTY_TOKENS;
|
|
27
|
+
} catch {
|
|
28
|
+
return EMPTY_TOKENS;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
setTokens(tokens: Tokens): void {
|
|
32
|
+
try {
|
|
33
|
+
const value = encodeURIComponent(
|
|
34
|
+
JSON.stringify({ clientId: WIX_MEMBERS_CLIENT_ID, tokens }),
|
|
35
|
+
);
|
|
36
|
+
document.cookie = `${SESSION_COOKIE_NAME}=${value}; path=/; secure; samesite=none`;
|
|
37
|
+
} catch {
|
|
38
|
+
// A blocked cookie only prevents the member session from surviving navigation.
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (!WIX_MEMBERS_CLIENT_ID) {
|
|
44
|
+
throw new Error("Custom member login needs WIX_MEMBERS_CLIENT_ID.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const client = createClient({
|
|
48
|
+
modules: { members },
|
|
49
|
+
auth: OAuthStrategy({ clientId: WIX_MEMBERS_CLIENT_ID, tokenStorage }),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const membersAuth = client.auth;
|
|
53
|
+
export const membersApi = client.members;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Member session state — a module-scope store, deliberately NOT a React context.
|
|
2
|
+
// A context can't span Astro islands (each island is its own React root); a module
|
|
3
|
+
// singleton is shared by every island in the page bundle, and works identically in a
|
|
4
|
+
// single-root SPA. Consume it through useMember() (hooks/), or subscribe directly.
|
|
5
|
+
import { fetchCurrentMember } from "./members";
|
|
6
|
+
import {
|
|
7
|
+
loggedInHint,
|
|
8
|
+
loginMember,
|
|
9
|
+
logoutMember,
|
|
10
|
+
registerMember,
|
|
11
|
+
verifyMemberEmail,
|
|
12
|
+
type LoginResult,
|
|
13
|
+
} from "./auth";
|
|
14
|
+
import type { CurrentMember } from "./types";
|
|
15
|
+
|
|
16
|
+
export interface MemberState {
|
|
17
|
+
member: CurrentMember | null;
|
|
18
|
+
/** True for a logged-in member — can be true with `member` null when the Members Area app is absent. */
|
|
19
|
+
loggedIn: boolean;
|
|
20
|
+
/** True until the first session read settles — render skeletons, not the logged-out state. */
|
|
21
|
+
loading: boolean;
|
|
22
|
+
/** Last failed operation's message — render it; a new operation clears it. */
|
|
23
|
+
error: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const EMPTY: MemberState = {
|
|
27
|
+
member: null,
|
|
28
|
+
loggedIn: false,
|
|
29
|
+
loading: true,
|
|
30
|
+
error: null,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let state: MemberState = EMPTY;
|
|
34
|
+
const listeners = new Set<() => void>();
|
|
35
|
+
let started = false;
|
|
36
|
+
|
|
37
|
+
function setState(patch: Partial<MemberState>): void {
|
|
38
|
+
state = { ...state, ...patch };
|
|
39
|
+
for (const l of listeners) l();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getMemberState(): MemberState {
|
|
43
|
+
return state;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Adopt an SSR-resolved member (Astro island props) so no client re-fetch happens. */
|
|
47
|
+
export function hydrateMember(member: CurrentMember | null): void {
|
|
48
|
+
if (started) return;
|
|
49
|
+
started = true;
|
|
50
|
+
state = { member, loggedIn: member !== null, loading: false, error: null };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function subscribeMember(listener: () => void): () => void {
|
|
54
|
+
listeners.add(listener);
|
|
55
|
+
// First subscriber triggers the initial session read (browser only — SSR renders `loading`).
|
|
56
|
+
if (!started && typeof window !== "undefined") {
|
|
57
|
+
started = true;
|
|
58
|
+
void refreshMember();
|
|
59
|
+
}
|
|
60
|
+
return () => listeners.delete(listener);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Re-read the session — call after anything that may have changed it. */
|
|
64
|
+
export async function refreshMember(): Promise<void> {
|
|
65
|
+
setState({ loading: true, error: null });
|
|
66
|
+
const hint = loggedInHint();
|
|
67
|
+
if (hint === false) {
|
|
68
|
+
setState({ member: null, loggedIn: false, loading: false });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// hint true → read the profile (null = profile data missing, still logged in).
|
|
72
|
+
// hint null (ambient) → the member read IS the session check: null = anonymous.
|
|
73
|
+
const member = await fetchCurrentMember();
|
|
74
|
+
setState({ member, loggedIn: hint ?? member !== null, loading: false });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function runLogin(
|
|
78
|
+
action: () => Promise<LoginResult>,
|
|
79
|
+
): Promise<LoginResult> {
|
|
80
|
+
try {
|
|
81
|
+
const result = await action();
|
|
82
|
+
if (result.state === "SUCCESS") await refreshMember();
|
|
83
|
+
if (result.state === "FAILURE")
|
|
84
|
+
setState({
|
|
85
|
+
error: result.error ?? result.errorCode ?? "Could not log in.",
|
|
86
|
+
});
|
|
87
|
+
return result;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
setState({ error: e instanceof Error ? e.message : String(e) });
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Submit the branded in-app sign-in form. No Wix-hosted login redirect is used. */
|
|
95
|
+
export function login(email: string, password: string): Promise<LoginResult> {
|
|
96
|
+
return runLogin(() => loginMember(email, password));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Submit the branded in-app sign-up form. */
|
|
100
|
+
export function register(
|
|
101
|
+
email: string,
|
|
102
|
+
password: string,
|
|
103
|
+
profile?: { firstName?: string; lastName?: string },
|
|
104
|
+
): Promise<LoginResult> {
|
|
105
|
+
return runLogin(() => registerMember(email, password, profile));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Finish an email-verification branch from the same in-app form. */
|
|
109
|
+
export function verifyEmail(code: string): Promise<LoginResult> {
|
|
110
|
+
return runLogin(() => verifyMemberEmail(code));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Log out (navigates away); local state resets for the instant before the redirect lands. */
|
|
114
|
+
export async function logout(returnTo?: string): Promise<void> {
|
|
115
|
+
setState({ member: null, loggedIn: false });
|
|
116
|
+
try {
|
|
117
|
+
await logoutMember(returnTo);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
setState({ error: e instanceof Error ? e.message : String(e) });
|
|
120
|
+
throw e;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Current-member read (Wix Members) — the only file that touches raw member entities.
|
|
2
|
+
// Identity vs. profile are different layers: "is this caller logged in?" needs no app
|
|
3
|
+
// install, but getCurrentMember returns profile DATA only once the Wix Members Area app is
|
|
4
|
+
// installed (the seed installs it). Copy as-is; extend by adding functions, not by editing
|
|
5
|
+
// these.
|
|
6
|
+
// docs: https://dev.wix.com/docs/api-reference/crm/members-contacts/members/members/get-my-member.md
|
|
7
|
+
import { membersApi } from "./client";
|
|
8
|
+
import { imgSrc } from "../media";
|
|
9
|
+
import type { CurrentMember } from "./types";
|
|
10
|
+
|
|
11
|
+
type Raw = Record<string, any>;
|
|
12
|
+
|
|
13
|
+
function toCurrentMember(raw: Raw): CurrentMember {
|
|
14
|
+
const nickname = raw.profile?.nickname ?? "";
|
|
15
|
+
const firstName = raw.contact?.firstName ?? "";
|
|
16
|
+
const lastName = raw.contact?.lastName ?? "";
|
|
17
|
+
const loginEmail = raw.loginEmail ?? "";
|
|
18
|
+
const fullName = [firstName, lastName].filter(Boolean).join(" ");
|
|
19
|
+
return {
|
|
20
|
+
id: raw._id ?? "",
|
|
21
|
+
loginEmail,
|
|
22
|
+
displayName: nickname || fullName || loginEmail.split("@")[0] || "Member",
|
|
23
|
+
firstName,
|
|
24
|
+
lastName,
|
|
25
|
+
nickname,
|
|
26
|
+
photoUrl: imgSrc(raw.profile?.photo, 200, 200),
|
|
27
|
+
contactId: raw.contactId ?? "",
|
|
28
|
+
memberSince: raw._createdDate
|
|
29
|
+
? new Date(raw._createdDate).toISOString().slice(0, 10)
|
|
30
|
+
: "",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The current member, or null for an anonymous visitor — and null for a LOGGED-IN member
|
|
36
|
+
* when the Members Area app isn't installed (setup, not a code bug; the seed installs it).
|
|
37
|
+
* Never throws: anonymous is a normal state for the client-side custom-login gate.
|
|
38
|
+
* ⚠️ The SDK export is getCurrentMember — the REST name "Get My Member" does not exist on
|
|
39
|
+
* the module; getMyMember(...) throws `is not a function`, and only once a real member
|
|
40
|
+
* loads the page.
|
|
41
|
+
*/
|
|
42
|
+
export async function fetchCurrentMember(): Promise<CurrentMember | null> {
|
|
43
|
+
try {
|
|
44
|
+
const res = await membersApi.getCurrentMember({ fieldsets: ["FULL"] });
|
|
45
|
+
const raw = res.member as Raw | undefined;
|
|
46
|
+
return raw?._id ? toCurrentMember(raw) : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Members DTOs — the serializable shapes every hook, component, and page consumes.
|
|
2
|
+
// Plain JSON: safe as Astro island props or across server/client boundaries. The photo is a
|
|
3
|
+
// resolved https URL.
|
|
4
|
+
|
|
5
|
+
/** The logged-in member as the header/account surfaces need it. */
|
|
6
|
+
export interface CurrentMember {
|
|
7
|
+
id: string;
|
|
8
|
+
/** Email the member logs in with ("" when the fieldset hides it). */
|
|
9
|
+
loginEmail: string;
|
|
10
|
+
/** Best display name: nickname → first+last → the login email's local part. */
|
|
11
|
+
displayName: string;
|
|
12
|
+
firstName: string;
|
|
13
|
+
lastName: string;
|
|
14
|
+
nickname: string;
|
|
15
|
+
/** Resolved https URL ("" when the member has no photo). */
|
|
16
|
+
photoUrl: string;
|
|
17
|
+
/** Contact-list id — the key for member-owned CMS/content lookups. */
|
|
18
|
+
contactId: string;
|
|
19
|
+
/** "YYYY-MM-DD" the membership was created ("" when unknown). */
|
|
20
|
+
memberSince: string;
|
|
21
|
+
}
|