@wix/agent-skills 1.19.2 → 1.20.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/references/SERVICE_PLUGIN.md +14 -1
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
- package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
- package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
- package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
- package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
- package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
- package/skills/wix-headless/references/CAPABILITIES.md +2 -2
- package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
- package/skills/wix-headless-fast/SKILL.md +200 -34
- package/skills/wix-headless-fast/entry/skill.md +10 -2
- package/skills/wix-headless-fast/install/attach.mjs +253 -0
- package/skills/wix-headless-fast/install/deploy.mjs +109 -9
- package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
- package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
- package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
- package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
- package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
- package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
- package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
- package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
- package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
- package/skills/wix-manage/SKILL.md +4 -1
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/events/create-wix-event.md +46 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
- package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
- package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
- package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44.md +19 -4
- package/skills/wix-vibe-headless/platforms/generic.md +6 -0
- package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
- package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
- package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
- package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The purchase control for one plan — wire as-is on every card, detail page, and home strip.
|
|
2
|
+
// It owns the rules a CTA gets wrong when rewritten: rendered only for a `buyable` plan (a PUBLIC
|
|
3
|
+
// plan can be merchant-assigned — `assignedText` shows instead, or nothing when null), the label
|
|
4
|
+
// from the DTO ("Get this plan" for a free plan, "Subscribe" otherwise, your children override it),
|
|
5
|
+
// "Redirecting…" while the hosted checkout is starting, the failure inline. The redirect itself is
|
|
6
|
+
// usePlanPurchase → purchasePlan; nothing here builds a URL or marks anything paid.
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import { usePlanPurchase } from "../../hooks/pricing-plans/usePlanPurchase";
|
|
9
|
+
import type { PurchaseOptions } from "../../wix/pricing-plans/purchase";
|
|
10
|
+
import type { PlanSummary } from "../../wix/pricing-plans/types";
|
|
11
|
+
|
|
12
|
+
export interface SubscribeButtonProps {
|
|
13
|
+
plan: PlanSummary;
|
|
14
|
+
/** thankYouPageUrl / postFlowUrl for the hosted flow — see purchase.ts; omit for the defaults. */
|
|
15
|
+
options?: PurchaseOptions;
|
|
16
|
+
/** Replaces the default label ("Get this plan" / "Subscribe"). */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
/** Shown for a plan that isn't buyable; null renders nothing (a grid card). */
|
|
20
|
+
assignedText?: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function SubscribeButton({
|
|
24
|
+
plan,
|
|
25
|
+
options,
|
|
26
|
+
children,
|
|
27
|
+
className = "rounded-full bg-primary px-6 py-2.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50",
|
|
28
|
+
assignedText = "This plan is assigned by the site owner.",
|
|
29
|
+
}: SubscribeButtonProps) {
|
|
30
|
+
const { purchase, purchasingId, error } = usePlanPurchase();
|
|
31
|
+
if (!plan.buyable) return assignedText ? <p className="text-sm text-muted-foreground">{assignedText}</p> : null;
|
|
32
|
+
const purchasing = purchasingId === plan.id;
|
|
33
|
+
return (
|
|
34
|
+
<div className="grid gap-2">
|
|
35
|
+
<button type="button" disabled={purchasing} onClick={() => void purchase(plan.id, options).catch(() => {})} className={className}>
|
|
36
|
+
{purchasing ? "Redirecting…" : children ?? (plan.free ? "Get this plan" : "Subscribe")}
|
|
37
|
+
</button>
|
|
38
|
+
{error && <p className="text-sm text-red-600" role="alert">{error}</p>}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import type
|
|
1
|
+
// React binding of the purchase store (wix/pricing-plans/plan-purchase-store.ts) — the purchase
|
|
2
|
+
// action for any surface (grid card, detail CTA, home strip). All correctness (the hosted redirect
|
|
3
|
+
// session, member login/signup, payment) lives in the data layer; the store tracks in-flight state;
|
|
4
|
+
// you own how it looks.
|
|
5
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
6
|
+
import { createPlanPurchaseStore, type PlanPurchaseState, type PlanPurchaseStore } from "../../wix/pricing-plans/plan-purchase-store";
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
/**
|
|
10
|
-
* Starts the hosted checkout — when it resolves the browser is already navigating away.
|
|
11
|
-
* Rejects with a visitor-facing message otherwise — surface it, don't swallow it.
|
|
12
|
-
*/
|
|
13
|
-
purchase: (planId: string, options?: PurchaseOptions) => Promise<void>;
|
|
14
|
-
/** The plan id a purchase is in flight for (null when idle) — key CTA spinners off it. */
|
|
15
|
-
purchasingId: string | null;
|
|
16
|
-
error: string | null;
|
|
17
|
-
}
|
|
8
|
+
export type UsePlanPurchase = PlanPurchaseState & Pick<PlanPurchaseStore, "purchase">;
|
|
18
9
|
|
|
19
10
|
export function usePlanPurchase(): UsePlanPurchase {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
setError(null);
|
|
26
|
-
try {
|
|
27
|
-
window.location.href = await purchasePlan(planId, options);
|
|
28
|
-
// stays "purchasing" — the browser is leaving for the hosted checkout
|
|
29
|
-
} catch (e) {
|
|
30
|
-
setPurchasingId(null);
|
|
31
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
32
|
-
throw e;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { purchase, purchasingId, error };
|
|
11
|
+
const ref = useRef<PlanPurchaseStore | null>(null);
|
|
12
|
+
if (!ref.current) ref.current = createPlanPurchaseStore();
|
|
13
|
+
const store = ref.current;
|
|
14
|
+
const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
15
|
+
return { ...state, purchase: store.purchase };
|
|
37
16
|
}
|
package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts
CHANGED
|
@@ -1,39 +1,22 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// React binding of the plans store (wix/pricing-plans/plans-store.ts) — the listing logic lives
|
|
2
|
+
// there, framework-free; this hook subscribes to one instance per mounted listing. SSR-friendly:
|
|
3
|
+
// pass server-fetched data as `initialPlans` (Astro frontmatter / server component) and no client
|
|
4
|
+
// fetch happens; a SPA passes nothing. Astro islands and React SPAs use this; a static page, Vue,
|
|
5
|
+
// or Svelte uses the store directly.
|
|
6
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
7
|
+
import { createPlansStore, type PlansState, type PlansStore, type PlansStoreOptions } from "../../wix/pricing-plans/plans-store";
|
|
6
8
|
|
|
7
|
-
export
|
|
8
|
-
initialPlans?: PlanSummary[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface UsePlans {
|
|
12
|
-
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
13
|
-
plans: PlanSummary[] | null;
|
|
14
|
-
error: string | null;
|
|
15
|
-
}
|
|
9
|
+
export type UsePlansOptions = PlansStoreOptions;
|
|
16
10
|
|
|
17
|
-
export
|
|
18
|
-
const [plans, setPlans] = useState<PlanSummary[] | null>(initialPlans ?? null);
|
|
19
|
-
const [error, setError] = useState<string | null>(null);
|
|
11
|
+
export type UsePlans = PlansState;
|
|
20
12
|
|
|
13
|
+
export function usePlans(options: UsePlansOptions = {}): UsePlans {
|
|
14
|
+
const ref = useRef<PlansStore | null>(null);
|
|
15
|
+
if (!ref.current) ref.current = createPlansStore(options);
|
|
16
|
+
const store = ref.current;
|
|
21
17
|
useEffect(() => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
.catch((e) => {
|
|
27
|
-
if (!alive) return;
|
|
28
|
-
setPlans([]);
|
|
29
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
return () => {
|
|
33
|
-
alive = false;
|
|
34
|
-
};
|
|
35
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
return { plans, error };
|
|
18
|
+
store.start();
|
|
19
|
+
return () => store.stop();
|
|
20
|
+
}, [store]);
|
|
21
|
+
return useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
39
22
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// The purchase action as a framework-free store — the logic behind usePlanPurchase, usable from
|
|
2
|
+
// React (usePlanPurchase wraps it), from a static page's card or detail CTA, from Vue/Svelte, or
|
|
3
|
+
// as the specification for a port. All correctness (the hosted redirect session, member
|
|
4
|
+
// login/signup, payment) lives in the data layer; this store tracks the in-flight plan and the
|
|
5
|
+
// last failure, and navigates the FULL document to the hosted checkout.
|
|
6
|
+
//
|
|
7
|
+
// One store per purchase surface (a grid and a detail CTA each key their own spinner):
|
|
8
|
+
// createPlanPurchaseStore(), not a singleton. No async work on mount, so no start/stop.
|
|
9
|
+
import { purchasePlan, type PurchaseOptions } from "./purchase";
|
|
10
|
+
|
|
11
|
+
export interface PlanPurchaseState {
|
|
12
|
+
/** The plan id a purchase is in flight for (null when idle) — key CTA spinners off it. */
|
|
13
|
+
purchasingId: string | null;
|
|
14
|
+
/** The last failed purchase's visitor-facing message; a new purchase clears it. */
|
|
15
|
+
error: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PlanPurchaseStore {
|
|
19
|
+
getState(): PlanPurchaseState;
|
|
20
|
+
subscribe(listener: () => void): () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Starts the hosted checkout — when it resolves the browser is already navigating away, and
|
|
23
|
+
* `purchasingId` stays set. Rejects (and records `error`) otherwise — surface it, don't swallow it.
|
|
24
|
+
*/
|
|
25
|
+
purchase(planId: string, options?: PurchaseOptions): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createPlanPurchaseStore(): PlanPurchaseStore {
|
|
29
|
+
let state: PlanPurchaseState = { purchasingId: null, error: null };
|
|
30
|
+
const listeners = new Set<() => void>();
|
|
31
|
+
|
|
32
|
+
function setState(patch: Partial<PlanPurchaseState>): void {
|
|
33
|
+
state = { ...state, ...patch };
|
|
34
|
+
for (const fn of listeners) fn();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
getState: () => state,
|
|
39
|
+
subscribe(fn) {
|
|
40
|
+
listeners.add(fn);
|
|
41
|
+
return () => listeners.delete(fn);
|
|
42
|
+
},
|
|
43
|
+
async purchase(planId, options) {
|
|
44
|
+
setState({ purchasingId: planId, error: null });
|
|
45
|
+
try {
|
|
46
|
+
window.location.href = await purchasePlan(planId, options);
|
|
47
|
+
// stays "purchasing" — the browser is leaving for the hosted checkout
|
|
48
|
+
} catch (e) {
|
|
49
|
+
setState({ purchasingId: null, error: e instanceof Error ? e.message : String(e) });
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Plan rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./plans.ts (the
|
|
2
|
+
// SDK, managed Astro and React) and the REST twin in references/pricing-plans/rest/plans.ts
|
|
3
|
+
// (fetch, a static site or a port to another language). Every rule about prices, billing labels,
|
|
4
|
+
// perks, buyability, images lives HERE, once. A raw plan may come from the SDK (`_id`, image as a
|
|
5
|
+
// `wix:image://` string) or from REST (`id`, image as { id, url }); the mappers accept both.
|
|
6
|
+
// Imports are type-only so a strip to JS emits no imports.
|
|
7
|
+
import type { PlanDetail, PlanSummary } from "./types";
|
|
8
|
+
|
|
9
|
+
/** A raw Plans V3 entity as either transport returns it. */
|
|
10
|
+
export type Raw = Record<string, any>;
|
|
11
|
+
/** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
|
|
12
|
+
export type ImgSrc = (value: any, width: number, height: number) => string;
|
|
13
|
+
|
|
14
|
+
const id = (raw: Raw | undefined): string => raw?._id ?? raw?.id ?? "";
|
|
15
|
+
|
|
16
|
+
// ---- query ----------------------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface PlansQueryOptions {
|
|
19
|
+
/** 1–100, default 100 (the grid shows every public plan). */
|
|
20
|
+
limit?: number;
|
|
21
|
+
/** One plan by URL slug (the detail page). */
|
|
22
|
+
slug?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The Query Plans body both transports send: only PUBLIC plans (a HIDDEN plan is merchant-assigned
|
|
27
|
+
* and never listed), cursor paging, and the slug when one plan is wanted. The SDK builder spells the
|
|
28
|
+
* same filter as `.eq("visibility", "PUBLIC")`.
|
|
29
|
+
*/
|
|
30
|
+
export function plansQuery({ limit = 100, slug }: PlansQueryOptions = {}): Raw {
|
|
31
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("limit must be between 1 and 100.");
|
|
32
|
+
const filter: Raw = { visibility: "PUBLIC" };
|
|
33
|
+
if (slug) filter.slug = slug;
|
|
34
|
+
return { filter, cursorPaging: { limit } };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---- price rules ----------------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** "€29.00" in the visitor's locale for the plan's currency; "Free" for no or zero amount. Never assume USD. */
|
|
40
|
+
export function formatPrice(value: string | undefined, currency: string | undefined): string {
|
|
41
|
+
if (value == null || value === "" || Number(value) === 0) return "Free";
|
|
42
|
+
try {
|
|
43
|
+
return new Intl.NumberFormat(undefined, { style: "currency", currency: currency || "USD" }).format(Number(value));
|
|
44
|
+
} catch {
|
|
45
|
+
return `${value} ${currency ?? ""}`.trim();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* billingTerms → a display label. billingCycle.count is a STRING on both transports ("1") — Number()
|
|
51
|
+
* it. No billingCycle → a one-time payment; endType CYCLES_COMPLETED with billingCycleCount 1 also
|
|
52
|
+
* bills once; more cycles read "per month × 6".
|
|
53
|
+
*/
|
|
54
|
+
export function billingLabel(terms: Raw | undefined): string {
|
|
55
|
+
const cycle = terms?.billingCycle;
|
|
56
|
+
if (!cycle) return "one-time";
|
|
57
|
+
const cyclesTotal = Number(terms?.cyclesCompletedDetails?.billingCycleCount ?? 0);
|
|
58
|
+
if (terms?.endType === "CYCLES_COMPLETED" && cyclesTotal === 1) return "one-time";
|
|
59
|
+
const count = Number(cycle.count ?? 1) || 1;
|
|
60
|
+
const period = String(cycle.period ?? "MONTH").toLowerCase();
|
|
61
|
+
const base = count === 1 ? `per ${period}` : `every ${count} ${period}s`;
|
|
62
|
+
return terms?.endType === "CYCLES_COMPLETED" && cyclesTotal > 1 ? `${base} × ${cyclesTotal}` : base;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The plan's image as a value imgSrc resolves: the SDK hands a `wix:image://…` string; REST hands
|
|
67
|
+
* an Image object { id, url } — its url when present, else the media id in the wix:image form the
|
|
68
|
+
* URL builder scales. "" when the plan has no image.
|
|
69
|
+
*/
|
|
70
|
+
export function planImage(raw: Raw): string {
|
|
71
|
+
const img = raw.image;
|
|
72
|
+
if (!img) return "";
|
|
73
|
+
if (typeof img === "string") return img;
|
|
74
|
+
if (img.url) return String(img.url);
|
|
75
|
+
return img.id ? `wix:image://v1/${img.id}/${img.id}` : "";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---- DTO mappers -----------------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The price is DISPLAY-ONLY: a decimal string at pricingVariants[0].pricingStrategies[0]
|
|
82
|
+
* .flatRate.amount ("0" = free), paired with plan.currency (site-derived). Wix settles the actual
|
|
83
|
+
* charge, tax, and schedule at the hosted checkout.
|
|
84
|
+
*/
|
|
85
|
+
export function toSummary(raw: Raw, imgSrc: ImgSrc): PlanSummary {
|
|
86
|
+
const variant: Raw = raw.pricingVariants?.[0] ?? {};
|
|
87
|
+
const amount: string | undefined = variant.pricingStrategies?.[0]?.flatRate?.amount;
|
|
88
|
+
const free = amount == null || Number(amount) === 0;
|
|
89
|
+
return {
|
|
90
|
+
id: id(raw),
|
|
91
|
+
slug: raw.slug ?? "",
|
|
92
|
+
name: raw.name ?? "",
|
|
93
|
+
description: raw.description ?? "",
|
|
94
|
+
price: free ? "Free" : formatPrice(amount, raw.currency),
|
|
95
|
+
free,
|
|
96
|
+
billing: free ? "" : billingLabel(variant.billingTerms),
|
|
97
|
+
freeTrialDays: variant.freeTrialDays || null,
|
|
98
|
+
perks: ((raw.perks ?? []) as Raw[]).map((p) => p.description ?? "").filter((d: string) => d),
|
|
99
|
+
buyable: raw.buyable === true, // a PUBLIC plan can still be merchant-assigned — no CTA then
|
|
100
|
+
imageUrl: imgSrc(planImage(raw), 800, 800),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function toDetail(raw: Raw, imgSrc: ImgSrc): PlanDetail {
|
|
105
|
+
return { ...toSummary(raw, imgSrc), termsAndConditions: raw.termsAndConditions ?? "" };
|
|
106
|
+
}
|
package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// The plans listing as a framework-free store — the logic behind usePlans, usable from React
|
|
2
|
+
// (usePlans wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and
|
|
3
|
+
// render), or as the specification for a port. State, subscribe/getState, start/stop, emit after
|
|
4
|
+
// every change.
|
|
5
|
+
//
|
|
6
|
+
// SSR-friendly: seed with `initialPlans` and they render at once with no client fetch; without a
|
|
7
|
+
// seed `start()` fetches the public plans. A failed load leaves `plans: []` with the message in
|
|
8
|
+
// `error`, so the surface renders its empty state and the message, never a skeleton forever.
|
|
9
|
+
//
|
|
10
|
+
// One store per mounted listing (a pricing page and a home-page strip can coexist): createPlansStore(),
|
|
11
|
+
// not a singleton. It imports the data layer by names the REST twin exports identically, so the
|
|
12
|
+
// same file runs over the SDK in Astro and over REST on a static page.
|
|
13
|
+
import { fetchPlans } from "./plans";
|
|
14
|
+
import type { PlanSummary } from "./types";
|
|
15
|
+
|
|
16
|
+
export interface PlansStoreOptions {
|
|
17
|
+
initialPlans?: PlanSummary[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Everything a pricing grid renders from. Read it with getState() or through a subscription. */
|
|
21
|
+
export interface PlansState {
|
|
22
|
+
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
23
|
+
plans: PlanSummary[] | null;
|
|
24
|
+
error: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PlansStore {
|
|
28
|
+
getState(): PlansState;
|
|
29
|
+
subscribe(listener: () => void): () => void;
|
|
30
|
+
/** Fetch the plans when no `initialPlans` were given. Call once when mounted (a browser). */
|
|
31
|
+
start(): void;
|
|
32
|
+
/** Stop reacting; drop a late response. */
|
|
33
|
+
stop(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createPlansStore({ initialPlans }: PlansStoreOptions = {}): PlansStore {
|
|
37
|
+
let state: PlansState = { plans: initialPlans ?? null, error: null };
|
|
38
|
+
let started = false;
|
|
39
|
+
const listeners = new Set<() => void>();
|
|
40
|
+
|
|
41
|
+
function setState(patch: Partial<PlansState>): void {
|
|
42
|
+
state = { ...state, ...patch };
|
|
43
|
+
for (const fn of listeners) fn();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
getState: () => state,
|
|
48
|
+
subscribe(fn) {
|
|
49
|
+
listeners.add(fn);
|
|
50
|
+
return () => listeners.delete(fn);
|
|
51
|
+
},
|
|
52
|
+
start() {
|
|
53
|
+
if (started) return;
|
|
54
|
+
started = true;
|
|
55
|
+
if (initialPlans) return;
|
|
56
|
+
fetchPlans()
|
|
57
|
+
.then((plans) => { if (started) setState({ plans }); })
|
|
58
|
+
.catch((e) => { if (started) setState({ plans: [], error: e instanceof Error ? e.message : String(e) }); });
|
|
59
|
+
},
|
|
60
|
+
stop() {
|
|
61
|
+
started = false;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,75 +1,27 @@
|
|
|
1
|
-
// Plan reads (Wix Pricing Plans, Plans V3) — the only file that touches raw plan
|
|
2
|
-
// Everything it returns is a plain DTO from ./types.
|
|
3
|
-
//
|
|
1
|
+
// Plan reads (Wix Pricing Plans, Plans V3) over the SDK — the only file that touches raw plan
|
|
2
|
+
// entities on this transport. Everything it returns is a plain DTO from ./types. The rules and
|
|
3
|
+
// mappers live in ./plans-core (shared with the REST twin in references/pricing-plans/rest/);
|
|
4
|
+
// this file is the transport only. Copy as-is; extend by adding functions, not by editing these.
|
|
4
5
|
//
|
|
5
6
|
// The read module is plansV3 — NOT `plans` (that's the V2 namespace; it has no queryPlans).
|
|
6
7
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/plans-v3/query-plans.md
|
|
7
8
|
import { plansV3 } from "@wix/pricing-plans";
|
|
8
9
|
import { wixModule } from "../sdk";
|
|
9
10
|
import { imgSrc } from "../media";
|
|
11
|
+
import { toDetail, toSummary, type Raw } from "./plans-core";
|
|
10
12
|
import type { PlanDetail, PlanSummary } from "./types";
|
|
11
13
|
|
|
12
14
|
const plans = wixModule(plansV3);
|
|
13
15
|
|
|
14
|
-
type Raw = Record<string, any>;
|
|
15
|
-
|
|
16
|
-
function formatPrice(value: string | undefined, currency: string | undefined): string {
|
|
17
|
-
if (value == null || value === "" || Number(value) === 0) return "Free";
|
|
18
|
-
try {
|
|
19
|
-
return new Intl.NumberFormat(undefined, { style: "currency", currency: currency || "USD" }).format(Number(value));
|
|
20
|
-
} catch {
|
|
21
|
-
return `${value} ${currency ?? ""}`.trim();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// billingTerms → a display label. billingCycle.count comes back as a STRING in SDK reads
|
|
26
|
-
// ("1") — Number() it. endType CYCLES_COMPLETED with billingCycleCount 1 bills once.
|
|
27
|
-
function billingLabel(terms: Raw | undefined): string {
|
|
28
|
-
const cycle = terms?.billingCycle;
|
|
29
|
-
if (!cycle) return "one-time";
|
|
30
|
-
const cyclesTotal = Number(terms?.cyclesCompletedDetails?.billingCycleCount ?? 0);
|
|
31
|
-
if (terms?.endType === "CYCLES_COMPLETED" && cyclesTotal === 1) return "one-time";
|
|
32
|
-
const count = Number(cycle.count ?? 1) || 1;
|
|
33
|
-
const period = String(cycle.period ?? "MONTH").toLowerCase();
|
|
34
|
-
const base = count === 1 ? `per ${period}` : `every ${count} ${period}s`;
|
|
35
|
-
return terms?.endType === "CYCLES_COMPLETED" && cyclesTotal > 1 ? `${base} × ${cyclesTotal}` : base;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// The price is DISPLAY-ONLY: a decimal string at pricingVariants[0].pricingStrategies[0]
|
|
39
|
-
// .flatRate.amount ("0" = free), paired with plan.currency (site-derived — never assume
|
|
40
|
-
// USD). Wix settles the actual charge, tax, and schedule at the hosted checkout.
|
|
41
|
-
function toSummary(raw: Raw): PlanSummary {
|
|
42
|
-
const variant: Raw = raw.pricingVariants?.[0] ?? {};
|
|
43
|
-
const amount: string | undefined = variant.pricingStrategies?.[0]?.flatRate?.amount;
|
|
44
|
-
const free = amount == null || Number(amount) === 0;
|
|
45
|
-
return {
|
|
46
|
-
id: raw._id ?? "", // SDK entity id is _id, never id (that's the REST view)
|
|
47
|
-
slug: raw.slug ?? "",
|
|
48
|
-
name: raw.name ?? "",
|
|
49
|
-
description: raw.description ?? "",
|
|
50
|
-
price: free ? "Free" : formatPrice(amount, raw.currency),
|
|
51
|
-
free,
|
|
52
|
-
billing: free ? "" : billingLabel(variant.billingTerms),
|
|
53
|
-
freeTrialDays: variant.freeTrialDays || null,
|
|
54
|
-
perks: (raw.perks ?? []).map((p: Raw) => p.description ?? "").filter((d: string) => d),
|
|
55
|
-
buyable: raw.buyable === true,
|
|
56
|
-
imageUrl: imgSrc(raw.image, 800, 800),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function toDetail(raw: Raw): PlanDetail {
|
|
61
|
-
return { ...toSummary(raw), termsAndConditions: raw.termsAndConditions ?? "" };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
16
|
/** List the public plans for the pricing grid, as card-ready DTOs. */
|
|
65
17
|
export async function fetchPlans({ limit = 100 } = {}): Promise<PlanSummary[]> {
|
|
66
18
|
const res = await plans.queryPlans().eq("visibility", "PUBLIC").limit(limit).find();
|
|
67
|
-
return (res.items ?? []).map((p
|
|
19
|
+
return (res.items ?? []).map((p) => toSummary(p as Raw, imgSrc));
|
|
68
20
|
}
|
|
69
21
|
|
|
70
22
|
/** Fetch one public plan by its URL slug. Null when not found. */
|
|
71
23
|
export async function fetchPlanBySlug(slug: string): Promise<PlanDetail | null> {
|
|
72
24
|
const res = await plans.queryPlans().eq("visibility", "PUBLIC").eq("slug", slug).limit(1).find();
|
|
73
25
|
const raw = res.items?.[0];
|
|
74
|
-
return raw ? toDetail(raw as Raw) : null;
|
|
26
|
+
return raw ? toDetail(raw as Raw, imgSrc) : null;
|
|
75
27
|
}
|
package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Purchase rules — transport-agnostic, imported by both ./purchase.ts (SDK) and the REST twin in
|
|
2
|
+
// references/pricing-plans/rest/purchase.ts (fetch). The redirect-session body for a plan and the
|
|
3
|
+
// reading of its result live HERE, once. Type-only imports: a strip to JS emits none.
|
|
4
|
+
import type { Raw } from "./plans-core";
|
|
5
|
+
|
|
6
|
+
export interface PurchaseOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Success-only return URL — Wix appends `?planOrderId=<GUID>` to it. Omit → Wix shows its
|
|
9
|
+
* hosted thank-you page, then returns to postFlowUrl. Point it at a page of yours only if
|
|
10
|
+
* that page reads the param and renders a real confirmation.
|
|
11
|
+
*/
|
|
12
|
+
thankYouPageUrl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Where a completed, abandoned, or interrupted flow returns (default: the current page).
|
|
15
|
+
* Landing here is NOT a success signal — only thankYouPageUrl carries one.
|
|
16
|
+
*/
|
|
17
|
+
postFlowUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The Create Redirect Session body for a plan: `paidPlansCheckout: { planId }` plus the callbacks.
|
|
22
|
+
* postFlowUrl defaults to the current page (browser); a server caller passes it explicitly.
|
|
23
|
+
* The return origin must be the site's real https origin as registered on the OAuth app's allowed
|
|
24
|
+
* domains — an unlisted or http origin 403s on return.
|
|
25
|
+
*/
|
|
26
|
+
export function purchaseBody(planId: string, { thankYouPageUrl, postFlowUrl }: PurchaseOptions = {}): Raw {
|
|
27
|
+
if (!planId) throw new Error("A plan id is required to start checkout.");
|
|
28
|
+
const href = typeof window !== "undefined" ? window.location.href : "";
|
|
29
|
+
return {
|
|
30
|
+
paidPlansCheckout: { planId },
|
|
31
|
+
callbacks: {
|
|
32
|
+
postFlowUrl: postFlowUrl ?? (href || undefined),
|
|
33
|
+
...(thankYouPageUrl ? { thankYouPageUrl } : {}),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The hosted checkout URL out of a redirect-session response; throws when there is none. */
|
|
39
|
+
export function redirectUrl(session: Raw | null | undefined): string {
|
|
40
|
+
const url = session?.redirectSession?.fullUrl;
|
|
41
|
+
if (!url) throw new Error("Checkout couldn't start — please try again.");
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
@@ -1,45 +1,22 @@
|
|
|
1
|
-
// The purchase flow: one call that turns a plan id into a Wix-hosted checkout URL.
|
|
1
|
+
// The purchase flow over the SDK: one call that turns a plan id into a Wix-hosted checkout URL.
|
|
2
2
|
// Purchasing a plan is a MEMBER action, but the hosted flow handles member login/signup,
|
|
3
3
|
// the order form, and payment itself — so this works from an anonymous visitor session.
|
|
4
4
|
// Never create the order yourself (orders.createOnlineOrder needs a logged-in member and
|
|
5
|
-
// still leaves payment unhandled); never hand-build a checkout URL.
|
|
5
|
+
// still leaves payment unhandled); never hand-build a checkout URL. The body and the result
|
|
6
|
+
// reading live in ./purchase-core (shared with the REST twin); this file is the transport only.
|
|
7
|
+
// Copy as-is.
|
|
6
8
|
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
|
|
7
9
|
import { redirects as redirectsModule } from "@wix/redirects";
|
|
8
10
|
import { wixModule } from "../sdk";
|
|
11
|
+
import { purchaseBody, redirectUrl, type PurchaseOptions } from "./purchase-core";
|
|
12
|
+
import type { Raw } from "./plans-core";
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type Raw = Record<string, any>;
|
|
14
|
+
export type { PurchaseOptions };
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Success-only return URL — Wix appends `?planOrderId=<GUID>` to it. Omit → Wix shows its
|
|
17
|
-
* hosted thank-you page, then returns to postFlowUrl. Point it at a page of yours only if
|
|
18
|
-
* that page reads the param and renders a real confirmation.
|
|
19
|
-
*/
|
|
20
|
-
thankYouPageUrl?: string;
|
|
21
|
-
/**
|
|
22
|
-
* Where a completed, abandoned, or interrupted flow returns (default: the current page).
|
|
23
|
-
* Landing here is NOT a success signal — only thankYouPageUrl carries one.
|
|
24
|
-
*/
|
|
25
|
-
postFlowUrl?: string;
|
|
26
|
-
}
|
|
16
|
+
const redirects = wixModule(redirectsModule);
|
|
27
17
|
|
|
28
18
|
/** Start the hosted purchase for a plan; resolves to the URL to send the browser to. */
|
|
29
|
-
export async function purchasePlan(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
): Promise<string> {
|
|
33
|
-
if (!planId) throw new Error("A plan id is required to start checkout.");
|
|
34
|
-
const href = typeof window !== "undefined" ? window.location.href : "";
|
|
35
|
-
const session: Raw = await redirects.createRedirectSession({
|
|
36
|
-
paidPlansCheckout: { planId },
|
|
37
|
-
callbacks: {
|
|
38
|
-
postFlowUrl: postFlowUrl ?? (href || undefined),
|
|
39
|
-
...(thankYouPageUrl ? { thankYouPageUrl } : {}),
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
const url = session?.redirectSession?.fullUrl;
|
|
43
|
-
if (!url) throw new Error("Checkout couldn't start — please try again.");
|
|
44
|
-
return url;
|
|
19
|
+
export async function purchasePlan(planId: string, options: PurchaseOptions = {}): Promise<string> {
|
|
20
|
+
const session: Raw = await redirects.createRedirectSession(purchaseBody(planId, options) as any);
|
|
21
|
+
return redirectUrl(session);
|
|
45
22
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Plan reads over REST — the twin of app/wix/pricing-plans/plans.ts. Same exports, same DTOs; the
|
|
2
|
+
// rules and mappers come from plans-core (the SAME file the SDK transport uses, deployed flat next
|
|
3
|
+
// to this one by deploy.mjs --stack static), so this file is only the transport: one fetch with a
|
|
4
|
+
// literal body per function. Every call here is safe from a browser with a visitor token.
|
|
5
|
+
// Porting: keep the path, keep the body, port the core once.
|
|
6
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/pricing-plans/plans-v3/query-plans.md
|
|
7
|
+
import { wixRequest } from "./client.js";
|
|
8
|
+
import { imgSrc } from "./media.js";
|
|
9
|
+
import { plansQuery, toDetail, toSummary, type Raw } from "./plans-core.js";
|
|
10
|
+
import type { PlanDetail, PlanSummary } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const QUERY = "/pricing-plans/v3/plans/query";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The public plans for the pricing grid, as card-ready DTOs.
|
|
16
|
+
* POST /pricing-plans/v3/plans/query { query: { filter: { visibility: "PUBLIC" }, cursorPaging: { limit } } }
|
|
17
|
+
*/
|
|
18
|
+
export async function fetchPlans({ limit = 100 } = {}): Promise<PlanSummary[]> {
|
|
19
|
+
const res = await wixRequest<Raw>(QUERY, { body: { query: plansQuery({ limit }) } });
|
|
20
|
+
return ((res?.plans ?? []) as Raw[]).map((p) => toSummary(p, imgSrc));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One public plan by its URL slug; null when the slug doesn't resolve (an empty `plans`, not a 404).
|
|
25
|
+
* POST /pricing-plans/v3/plans/query { query: { filter: { visibility: "PUBLIC", slug }, cursorPaging: { limit: 1 } } }
|
|
26
|
+
*/
|
|
27
|
+
export async function fetchPlanBySlug(slug: string): Promise<PlanDetail | null> {
|
|
28
|
+
const res = await wixRequest<Raw>(QUERY, { body: { query: plansQuery({ limit: 1, slug }) } });
|
|
29
|
+
const raw: Raw | undefined = res?.plans?.[0];
|
|
30
|
+
return raw ? toDetail(raw, imgSrc) : null;
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The purchase flow over REST — the twin of app/wix/pricing-plans/purchase.ts. Same export, same
|
|
2
|
+
// signature; the body and the result reading come from purchase-core (the SAME file the SDK
|
|
3
|
+
// transport uses). Purchasing is a MEMBER action, but the hosted flow handles login/signup, the
|
|
4
|
+
// order form, and payment itself, so a visitor token starts it. Never create the order yourself,
|
|
5
|
+
// never hand-build a checkout URL.
|
|
6
|
+
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
|
|
7
|
+
import { wixRequest } from "./client.js";
|
|
8
|
+
import { purchaseBody, redirectUrl, type PurchaseOptions } from "./purchase-core.js";
|
|
9
|
+
import type { Raw } from "./plans-core.js";
|
|
10
|
+
|
|
11
|
+
export type { PurchaseOptions };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Start the hosted purchase for a plan; resolves to the URL to navigate the FULL document to.
|
|
15
|
+
* POST /headless/v1/redirect-session { paidPlansCheckout: { planId }, callbacks: { postFlowUrl, thankYouPageUrl? } }
|
|
16
|
+
*/
|
|
17
|
+
export async function purchasePlan(planId: string, options: PurchaseOptions = {}): Promise<string> {
|
|
18
|
+
const session = await wixRequest<Raw>("/headless/v1/redirect-session", { body: purchaseBody(planId, options) });
|
|
19
|
+
return redirectUrl(session);
|
|
20
|
+
}
|
|
@@ -61,6 +61,14 @@ omits both — coverage is skipped automatically.
|
|
|
61
61
|
**Seeding is additive — never delete or overwrite existing content**; ask first if a cleanup
|
|
62
62
|
seems needed.
|
|
63
63
|
|
|
64
|
+
## Supplied content
|
|
65
|
+
|
|
66
|
+
The general rules are in `references/shared/SUPPLIED-CONTENT.md`. For pricing plans, each plan the
|
|
67
|
+
user lists is one entry. Its name becomes `name`; a price with a billing period becomes `price`
|
|
68
|
+
with `type: "recurring"` and the matching `billingCycle`; a price paid once becomes
|
|
69
|
+
`type: "one-time"`; no price means `type: "free"`; a list of features or benefits becomes `perks`;
|
|
70
|
+
the description becomes `description`. A price with no period is a question for the user.
|
|
71
|
+
|
|
64
72
|
## Escape hatch — individual functions
|
|
65
73
|
`setupPricingPlans` composes exported steps — `installPricingPlansApp`, `createPlans`,
|
|
66
74
|
`attachBookingsCoverage` (and its `getProgramDefinition`, `createPoolDefinition`,
|