@wix/agent-skills 1.20.0 → 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-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 +29 -34
- 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 +45 -4
- 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 +182 -63
- 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 +29 -58
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
- 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 +49 -162
- 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 +47 -370
- 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/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 -9
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +6 -35
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
- 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
package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Project detail + media gallery, by slug, as a framework-free store — the logic behind
|
|
2
|
+
// useProjectDetail, usable from React (the hook wraps it), from a static page's project view,
|
|
3
|
+
// from Vue/Svelte, or as the specification for a port.
|
|
4
|
+
//
|
|
5
|
+
// SSR-friendly: pass BOTH `initialProject` and `initialItems` and nothing is fetched. With only
|
|
6
|
+
// the project seeded, `start()` fetches its gallery; with nothing seeded it resolves the slug
|
|
7
|
+
// first. `notFound` is the real not-found signal (project stays null while loading). One store
|
|
8
|
+
// per (slug, mount): createProjectDetailStore(slug, options), not a singleton.
|
|
9
|
+
import { fetchProjectBySlug, fetchProjectGallery } from "./portfolio";
|
|
10
|
+
import type { GalleryItem, ProjectDetail } from "./types";
|
|
11
|
+
|
|
12
|
+
export interface ProjectDetailStoreOptions {
|
|
13
|
+
initialProject?: ProjectDetail;
|
|
14
|
+
initialItems?: GalleryItem[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProjectDetailState {
|
|
18
|
+
/** null while loading AND when not found — check `notFound` to tell them apart. */
|
|
19
|
+
project: ProjectDetail | null;
|
|
20
|
+
notFound: boolean;
|
|
21
|
+
/** null while the gallery load is in flight — render skeletons, not an empty state. */
|
|
22
|
+
items: GalleryItem[] | null;
|
|
23
|
+
error: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ProjectDetailStore {
|
|
27
|
+
getState(): ProjectDetailState;
|
|
28
|
+
subscribe(listener: () => void): () => void;
|
|
29
|
+
/** Resolve the slug and load its gallery unless both were seeded. Call once when mounted. */
|
|
30
|
+
start(): void;
|
|
31
|
+
/** Stop reacting; drop late responses. */
|
|
32
|
+
stop(): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createProjectDetailStore(
|
|
36
|
+
slug: string,
|
|
37
|
+
{ initialProject, initialItems }: ProjectDetailStoreOptions = {},
|
|
38
|
+
): ProjectDetailStore {
|
|
39
|
+
let project: ProjectDetail | null = initialProject ?? null;
|
|
40
|
+
let notFound = false;
|
|
41
|
+
let items: GalleryItem[] | null = initialItems ?? null;
|
|
42
|
+
let error: string | null = null;
|
|
43
|
+
let started = false;
|
|
44
|
+
const listeners = new Set<() => void>();
|
|
45
|
+
let snapshot: ProjectDetailState | null = null;
|
|
46
|
+
const emit = () => { snapshot = null; for (const fn of listeners) fn(); };
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
getState() {
|
|
50
|
+
return (snapshot ??= { project, notFound, items, error });
|
|
51
|
+
},
|
|
52
|
+
subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
|
|
53
|
+
start() {
|
|
54
|
+
if (started) return;
|
|
55
|
+
started = true;
|
|
56
|
+
if (initialProject && initialItems) return;
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const proj = initialProject ?? (await fetchProjectBySlug(slug));
|
|
60
|
+
if (!started) return;
|
|
61
|
+
if (!proj) {
|
|
62
|
+
notFound = true;
|
|
63
|
+
items = [];
|
|
64
|
+
emit();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
project = proj;
|
|
68
|
+
emit();
|
|
69
|
+
const gallery = await fetchProjectGallery(proj.id);
|
|
70
|
+
if (started) { items = gallery; emit(); }
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (!started) return;
|
|
73
|
+
items = [];
|
|
74
|
+
error = e instanceof Error ? e.message : String(e);
|
|
75
|
+
emit();
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
},
|
|
79
|
+
stop() { started = false; },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Portfolio reads over REST — the twin of app/wix/portfolio/portfolio.ts. Same exports, same DTOs;
|
|
2
|
+
// the rules and mappers come from portfolio-core (the SAME file the SDK transport uses, deployed
|
|
3
|
+
// flat next to this one by deploy.mjs --stack static), so this file is only the transport: one
|
|
4
|
+
// fetch with a literal URL and body per function. Every call here is a public read, safe from a
|
|
5
|
+
// browser with a visitor token. Porting: keep the paths, keep the bodies, port the core once.
|
|
6
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/collections/list-collections.md
|
|
7
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/collections/query-collections.md
|
|
8
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/projects/list-projects.md
|
|
9
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/projects/query-projects.md
|
|
10
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/portfolio/project-items/list-project-items.md
|
|
11
|
+
import { wixRequest } from "./client.js";
|
|
12
|
+
import { imgSrc } from "./media.js";
|
|
13
|
+
import {
|
|
14
|
+
galleryItems,
|
|
15
|
+
visibleCollection,
|
|
16
|
+
visibleCollections,
|
|
17
|
+
visibleProject,
|
|
18
|
+
visibleProjects,
|
|
19
|
+
type Raw,
|
|
20
|
+
type VideoSrc,
|
|
21
|
+
} from "./portfolio-core.js";
|
|
22
|
+
import type { CollectionSummary, GalleryItem, ProjectDetail, ProjectSummary } from "./types.js";
|
|
23
|
+
|
|
24
|
+
// REST never returns the bare wix:video:// string — videoInfo arrives as { resolutions, posters },
|
|
25
|
+
// which the core reads directly. A string here has no resolver on this transport → dropped.
|
|
26
|
+
const videoSrc: VideoSrc = () => null;
|
|
27
|
+
|
|
28
|
+
/** Visible collections in dashboard order. GET /portfolio/v1/collections?paging.limit=N → { collections } */
|
|
29
|
+
export async function fetchCollections({ limit = 100 } = {}): Promise<CollectionSummary[]> {
|
|
30
|
+
const res = await wixRequest<Raw>("/portfolio/v1/collections", { method: "GET", query: { "paging.limit": String(limit) } });
|
|
31
|
+
return visibleCollections((res?.collections ?? []) as Raw[], imgSrc);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One visible collection by URL slug; null when missing or hidden → a real 404.
|
|
36
|
+
* POST /portfolio/v1/collections/query { query: { filter: { slug }, cursorPaging: { limit: 1 } } } → { collections }
|
|
37
|
+
*/
|
|
38
|
+
export async function fetchCollectionBySlug(slug: string): Promise<CollectionSummary | null> {
|
|
39
|
+
const res = await wixRequest<Raw>("/portfolio/v1/collections/query", {
|
|
40
|
+
body: { query: { filter: { slug }, cursorPaging: { limit: 1 } } },
|
|
41
|
+
});
|
|
42
|
+
return visibleCollection(res?.collections?.[0] as Raw | undefined, imgSrc);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Visible projects in list order, optionally one collection's. GET /portfolio/v1/projects?paging.limit=N → { projects } */
|
|
46
|
+
export async function fetchProjects({
|
|
47
|
+
collectionId,
|
|
48
|
+
limit = 100,
|
|
49
|
+
}: { collectionId?: string; limit?: number } = {}): Promise<ProjectSummary[]> {
|
|
50
|
+
const res = await wixRequest<Raw>("/portfolio/v1/projects", { method: "GET", query: { "paging.limit": String(limit) } });
|
|
51
|
+
return visibleProjects((res?.projects ?? []) as Raw[], collectionId, imgSrc, videoSrc);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* One visible project with its details rows by URL slug; null when missing or hidden.
|
|
56
|
+
* POST /portfolio/v1/projects/query { query: { filter: { slug }, cursorPaging: { limit: 1 } } } → { projects }
|
|
57
|
+
*/
|
|
58
|
+
export async function fetchProjectBySlug(slug: string): Promise<ProjectDetail | null> {
|
|
59
|
+
const res = await wixRequest<Raw>("/portfolio/v1/projects/query", {
|
|
60
|
+
body: { query: { filter: { slug }, cursorPaging: { limit: 1 } } },
|
|
61
|
+
});
|
|
62
|
+
return visibleProject(res?.projects?.[0] as Raw | undefined, imgSrc, videoSrc);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A project's gallery in dashboard order; IMAGE and VIDEO items only.
|
|
67
|
+
* GET /portfolio/v1/projectItems/{projectId}/items?paging.limit=100 → { items }
|
|
68
|
+
*/
|
|
69
|
+
export async function fetchProjectGallery(projectId: string): Promise<GalleryItem[]> {
|
|
70
|
+
const res = await wixRequest<Raw>(`/portfolio/v1/projectItems/${encodeURIComponent(projectId)}/items`, {
|
|
71
|
+
method: "GET",
|
|
72
|
+
query: { "paging.limit": "100" },
|
|
73
|
+
});
|
|
74
|
+
return galleryItems((res?.items ?? []) as Raw[], imgSrc, videoSrc);
|
|
75
|
+
}
|
|
@@ -67,6 +67,14 @@ rows (Role, Year, Client…).
|
|
|
67
67
|
is additive — never delete or overwrite existing content**; if removing the samples seems
|
|
68
68
|
wanted, ask the owner first.
|
|
69
69
|
|
|
70
|
+
## Supplied content
|
|
71
|
+
|
|
72
|
+
The general rules are in `references/shared/SUPPLIED-CONTENT.md`. For a portfolio, each project the
|
|
73
|
+
user lists is one entry. Its title becomes `title`; its category or series becomes `collection`
|
|
74
|
+
(and joins the `collections` list); client, role, year and similar facts become `details`; their
|
|
75
|
+
cover image becomes `coverImageUrl`; their project images become `items` in the order given. A
|
|
76
|
+
photo with no caption gets its file name as `title`.
|
|
77
|
+
|
|
70
78
|
## Escape hatch — individual functions
|
|
71
79
|
`setupPortfolio` composes exported steps — `installPortfolioApp`, `createCollections`,
|
|
72
80
|
`createProjects`, `importImage`, `attachProjectCovers`, `attachCollectionCovers`,
|
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
# Pricing Plans — playbook
|
|
2
2
|
|
|
3
|
-
The plans machinery ships as files — plan reads (grid + by-slug)
|
|
4
|
-
|
|
5
|
-
card, the pricing grid, and the plan detail surface on the shipped hooks/DTOs, plus the
|
|
6
|
-
page and the brand. You never write purchase logic; you never skip designing.
|
|
3
|
+
The plans machinery ships as files — plan reads (grid + by-slug), the hosted purchase redirect,
|
|
4
|
+
the purchase control, typed end-to-end. **The presentation is yours**: you design and implement
|
|
5
|
+
the plan card, the pricing grid, and the plan detail surface on the shipped hooks/DTOs, plus the
|
|
6
|
+
home page and the brand. You never write purchase logic; you never skip designing.
|
|
7
7
|
|
|
8
8
|
## The file map (deployed into `src/`)
|
|
9
9
|
|
|
10
|
-
**
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
**On Astro and React the shipped files are tested and work as they are** — this table and the
|
|
11
|
+
contracts below are everything you need to use them, so don't spend the run reading their source;
|
|
12
|
+
wire them and build your surfaces. Reading them is the right move when something is off (a runtime
|
|
13
|
+
error, a field this playbook doesn't cover) or when the brief wants a behaviour they don't offer —
|
|
14
|
+
then read the file that owns it and change or extend it. On `lib`, `static`, and a port the
|
|
15
|
+
components don't deploy at all, and each wiring section below opens with the files to read before
|
|
16
|
+
writing their equivalents. Files you edit: `SiteLayout.astro` and `styles/global.css`. Files you
|
|
17
|
+
**create** (skeletons below): your pricing grid and plan detail islands, plus your home page.
|
|
13
18
|
|
|
14
19
|
| file | what it is |
|
|
15
20
|
|---|---|
|
|
16
|
-
| `wix/config.ts` · `wix/sdk.ts`
|
|
17
|
-
| `wix/
|
|
18
|
-
| `wix/pricing-plans/
|
|
19
|
-
| `wix/pricing-plans/
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
21
|
+
| `wix/config.ts` · `wix/sdk.ts` | shared auth seam (deploy configures it — nothing to set by hand) |
|
|
22
|
+
| `wix/media.ts` · `wix/money.ts` | `imgAttrs(url, sizes)` — every `<img>` attribute for a DTO image (`src`, `srcSet`, `sizes`, lazy): `<img {...imgAttrs(plan.imageUrl, "50vw")} alt={plan.name} />`; `imgSrc()` / `imgSrcSet()` / `formatMoney()` underneath |
|
|
23
|
+
| `wix/pricing-plans/types.ts` | the DTOs (`PlanSummary`, `PlanDetail`) — contracts inlined below |
|
|
24
|
+
| `wix/pricing-plans/plans.ts` | `fetchPlans`, `fetchPlanBySlug` — the transport; the rules and DTO mappers are in `plans-core.ts` beside it (shared with the REST layer) |
|
|
25
|
+
| `wix/pricing-plans/purchase.ts` | `purchasePlan` — the hosted-checkout redirect session; its body lives in `purchase-core.ts` (shared with the REST layer) |
|
|
26
|
+
| `wix/pricing-plans/plans-store.ts` · `plan-purchase-store.ts` | the listing and purchase state machines, framework-free (`createPlansStore()`, `createPlanPurchaseStore()` — `getState`/`subscribe` + actions, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
|
|
27
|
+
| `hooks/pricing-plans/usePlans.ts` | React binding of `plans-store.ts`: the plans listing — contract below |
|
|
28
|
+
| `hooks/pricing-plans/usePlanPurchase.ts` | React binding of `plan-purchase-store.ts`: the purchase action — contract below |
|
|
29
|
+
| `components/pricing-plans/SubscribeButton.tsx` | the purchase control for one plan — rendered only when `buyable`, label from the DTO ("Get this plan" free / "Subscribe"), "Redirecting…" in flight, the failure inline — **wire as-is** on every card, the detail page, a home strip (`<SubscribeButton plan={p} />`) |
|
|
30
|
+
| `components/pricing-plans/PlansView.tsx` (+ `PlanCard`) · `PlanDetailView.tsx` | **REFERENCE implementations** — correct, plain; build your own instead of shipping them (skeletons below) |
|
|
31
|
+
| `styles/global.css` | **the design system**: Tailwind v4 + the `@theme` token block (colors, radii, fonts — shared across verticals). Everything, shipped and yours, styles from these tokens |
|
|
24
32
|
|
|
25
33
|
Astro stack additionally gets:
|
|
26
34
|
|
|
@@ -28,34 +36,48 @@ Astro stack additionally gets:
|
|
|
28
36
|
|---|---|
|
|
29
37
|
| `layouts/SiteLayout.astro` | site chrome — **yours to brand** (keep the `seo-tags` slot + global.css import). If another vertical is also deployed, its layout won — add a Plans nav link there |
|
|
30
38
|
| `pages/plans.astro` | SSR pricing grid — **keep the frontmatter**, swap the island import to YOUR component |
|
|
31
|
-
| `pages/plans/[slug].astro` | SSR plan detail — **keep the frontmatter** (plain `<title>`/`<meta>` from the DTO: Pricing Plans has no owner-editable SEO item type); swap the island import |
|
|
39
|
+
| `pages/plans/[slug].astro` | SSR plan detail, a real 404 on a bad slug — **keep the frontmatter** (plain `<title>`/`<meta>` from the DTO: Pricing Plans has no owner-editable SEO item type); swap the island import |
|
|
32
40
|
|
|
33
41
|
## What you build — the design job
|
|
34
42
|
|
|
35
43
|
1. **The plan card + pricing grid** — your tier card (name, price + billing cadence, trial
|
|
36
|
-
badge, perks list,
|
|
37
|
-
|
|
38
|
-
on `usePlans` + `usePlanPurchase`.
|
|
44
|
+
badge, perks list, the shipped `SubscribeButton`) and grid rhythm (a highlighted recommended
|
|
45
|
+
tier is a classic), with skeletons while loading and an honest empty state — on `usePlans`.
|
|
39
46
|
2. **The plan detail surface** — the full pitch: price block, perks, terms & conditions
|
|
40
|
-
(plain text — render pre-wrap),
|
|
41
|
-
|
|
47
|
+
(plain text — render pre-wrap), the shipped `SubscribeButton` (the plan itself arrives as an
|
|
48
|
+
SSR-fetched DTO prop).
|
|
42
49
|
3. **The home page** — hero, a featured-plans strip (fetch in frontmatter → your components),
|
|
43
50
|
brand story.
|
|
44
51
|
|
|
45
52
|
Plus the **theme** (`@theme` block, one edit) and the **chrome** (`SiteLayout`, one pass).
|
|
46
|
-
Style everything with Tailwind utilities on the tokens.
|
|
47
53
|
|
|
48
|
-
###
|
|
54
|
+
### What a complete pricing site shows (recommended defaults)
|
|
55
|
+
|
|
56
|
+
Defaults for a brief that says nothing about them; the prompt wins where it differs. Look at the
|
|
57
|
+
plans before designing (how many tiers, free/recurring/one-time mix, trials, perks, images).
|
|
58
|
+
|
|
59
|
+
- **Pricing page:** every public plan as a card in the first screen on desktop — name, price with
|
|
60
|
+
its cadence, perks, the CTA; a free plan reads "Free" with no cadence; a recommended tier may be
|
|
61
|
+
highlighted, never invented; loading (`plans === null`), empty, and error states look different.
|
|
62
|
+
- **Plan page:** name, price, cadence, the CTA in the first screen at 390px wide too; perks and
|
|
63
|
+
terms after the CTA, never between the price and the action; a plan without an image renders no
|
|
64
|
+
empty image box.
|
|
65
|
+
- **CTA:** the shipped `SubscribeButton` on every purchase surface — it disappears for a plan that
|
|
66
|
+
isn't buyable (`assignedText` or nothing), and a free plan's label is not "Subscribe".
|
|
67
|
+
- **Copy:** nothing the merchant didn't supply — no invented savings, guarantees, or member counts;
|
|
68
|
+
no Wix IDs or technical words in visible text.
|
|
69
|
+
|
|
70
|
+
### The contracts your components consume (tested and work as they are; read the source when something is off or the brief wants more)
|
|
49
71
|
|
|
50
72
|
```ts
|
|
51
73
|
// PlanSummary (cards) — display-ready:
|
|
52
|
-
// { id, slug, name, description, price /* "
|
|
53
|
-
// billing /* "per month" | "every 3 months" | "one-time" | "" (free) */,
|
|
74
|
+
// { id, slug, name, description, price /* "€29.00" | "Free" */, free,
|
|
75
|
+
// billing /* "per month" | "every 3 months" | "one-time" | "per month × 6" | "" (free) */,
|
|
54
76
|
// freeTrialDays: number|null, perks: string[], buyable, imageUrl /* "" when none */ }
|
|
55
77
|
// PlanDetail adds: termsAndConditions (plain text; "" when not set).
|
|
56
78
|
|
|
57
79
|
// usePlans({ initialPlans? }) →
|
|
58
|
-
// { plans: PlanSummary[]|null /* null = loading → skeletons */, error }
|
|
80
|
+
// { plans: PlanSummary[]|null /* null = loading → skeletons; [] after a failed load, with error */, error }
|
|
59
81
|
|
|
60
82
|
// usePlanPurchase() →
|
|
61
83
|
// { purchase(planId, { thankYouPageUrl?, postFlowUrl? }?): Promise<void>,
|
|
@@ -63,28 +85,163 @@ Style everything with Tailwind utilities on the tokens.
|
|
|
63
85
|
// // rejects with a visitor-facing message — surface it
|
|
64
86
|
// purchasingId, // plan id in flight (null when idle) — key the CTA spinner off it
|
|
65
87
|
// error }
|
|
88
|
+
// <SubscribeButton plan={p} options? children? className? assignedText? /> wraps it: you only
|
|
89
|
+
// place it; it decides whether to render, what to say, and shows its own error.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### The islands you create — skeletons
|
|
93
|
+
|
|
94
|
+
The pages ship; the islands they mount are yours. Each island is a thin view over a hook. Hooks
|
|
95
|
+
first, branches after (an early return above a hook changes hook order between renders and React
|
|
96
|
+
throws). `client:load` islands render on the server too — render every state totally; nothing in
|
|
97
|
+
a render path may throw. The class names are the Astro/React spelling of layout rules that hold on
|
|
98
|
+
every stack; on a stack where the components don't deploy, keep the rule and write it in your CSS.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// src/components/pricing-plans/PricingGrid.tsx — YOU build it; pages/plans.astro mounts it
|
|
102
|
+
// (swap its island import from PlansView to this).
|
|
103
|
+
import { usePlans } from "../../hooks/pricing-plans/usePlans";
|
|
104
|
+
import SubscribeButton from "./SubscribeButton";
|
|
105
|
+
import type { PlanSummary } from "../../wix/pricing-plans/types";
|
|
106
|
+
|
|
107
|
+
export default function PricingGrid(props: { initialPlans?: PlanSummary[] /* SSR prop — pass straight to usePlans; omitted in a SPA */ }) {
|
|
108
|
+
const { plans, error } = usePlans(props);
|
|
109
|
+
// …you implement the render:
|
|
110
|
+
// • error → a short inline message
|
|
111
|
+
// • plans === null → skeleton cards; [] → your honest empty state
|
|
112
|
+
// • else YOUR grid of YOUR cards (PlanSummary contract above): name, price on its own line
|
|
113
|
+
// with billing beside it (nothing beside "Free"), `${freeTrialDays}-day free trial` when not
|
|
114
|
+
// null, every perk, a link to `/plans/${p.slug}`, and <SubscribeButton plan={p} assignedText={null} />
|
|
115
|
+
// as the card's LAST ROW — the card root a flex column, the control pinned to the bottom
|
|
116
|
+
// (mt-auto) so CTAs share one baseline across the row; never a button inside the card's <a>.
|
|
117
|
+
// • the name WRAPS (`min-w-0`, `break-words`) — no truncation.
|
|
118
|
+
}
|
|
66
119
|
```
|
|
67
120
|
|
|
121
|
+
```tsx
|
|
122
|
+
// src/components/pricing-plans/PlanPitch.tsx — YOU build it; pages/plans/[slug].astro mounts it
|
|
123
|
+
// with the server-fetched plan (swap its island import from PlanDetailView to this).
|
|
124
|
+
import SubscribeButton from "./SubscribeButton";
|
|
125
|
+
import type { PlanDetail } from "../../wix/pricing-plans/types";
|
|
126
|
+
|
|
127
|
+
export default function PlanPitch({ plan }: { plan: PlanDetail }) {
|
|
128
|
+
// …you implement the render, laid out for the brand: price + billing, the trial line, the
|
|
129
|
+
// perks, <SubscribeButton plan={plan} /> right under the price (its label for a paid plan may
|
|
130
|
+
// carry the price: `Subscribe · ${plan.price}` as children), then termsAndConditions pre-wrap.
|
|
131
|
+
// Image via <img {...imgAttrs(plan.imageUrl, "(min-width: 768px) 50vw, 100vw")} /> only when
|
|
132
|
+
// plan.imageUrl is non-empty — a bounded band on phones (`max-h-[40vh]`), not a full-screen hero.
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### The reference files for stacks where the components don't deploy
|
|
137
|
+
|
|
138
|
+
On `lib`, `static`, and a port, nothing under `components/` or `hooks/` arrives. The state
|
|
139
|
+
machines behind the hooks do arrive — `wix/pricing-plans/plans-store.ts`, `plan-purchase-store.ts`
|
|
140
|
+
— so you never rewrite them: create a store per surface, `subscribe`, render from `getState()`,
|
|
141
|
+
call its actions. Their `*State` interfaces are the render contract; read those. What you write is
|
|
142
|
+
the rendering — grid, card, plan page, the CTA — and for that read first:
|
|
143
|
+
|
|
144
|
+
1. `components/pricing-plans/SubscribeButton.tsx` — the purchase control as working code: the
|
|
145
|
+
`buyable` gate and what shows instead, the free/paid label, the in-flight label, the inline
|
|
146
|
+
error; the reference views place it as the last row of a card and under the price on the page.
|
|
147
|
+
|
|
148
|
+
Under `references/pricing-plans/app/`.
|
|
149
|
+
|
|
68
150
|
### Wiring — Astro (default)
|
|
69
151
|
|
|
70
152
|
1. Set the `@theme` tokens (one edit); brand `SiteLayout.astro` (one pass — merge into the
|
|
71
153
|
other vertical's layout instead if both are deployed).
|
|
72
|
-
2. Write your
|
|
73
|
-
the references), swap the island imports in `pages/plans.astro` and
|
|
74
|
-
`pages/plans/[slug].astro`. Both islands: `client:load` with the SSR DTO props.
|
|
75
|
-
|
|
154
|
+
2. Write your islands under `src/components/pricing-plans/` per the skeletons (new names — don't
|
|
155
|
+
overwrite the references), swap the island imports in `pages/plans.astro` and
|
|
156
|
+
`pages/plans/[slug].astro`. Both islands: `client:load` with the SSR DTO props. Author your
|
|
157
|
+
surfaces in as few messages as possible — batch multiple Writes per message.
|
|
76
158
|
3. Write `pages/index.astro` (home) — it exists from the scaffold; Read it before overwriting.
|
|
77
159
|
|
|
160
|
+
### Wiring — another JS framework (`--stack lib`: Vue, Svelte, Solid, plain Vite)
|
|
161
|
+
|
|
162
|
+
Read the reference file listed above before writing any surface.
|
|
163
|
+
|
|
164
|
+
`deploy.mjs pricing-plans --stack lib` put the data layer in `src/wix/` and nothing else: `sdk.ts`
|
|
165
|
+
(the visitor client, configured with the public client id), `media.ts`, `money.ts`, and
|
|
166
|
+
`wix/pricing-plans/` — `plans.ts`, `purchase.ts`, `types.ts`, the `*-core.ts` rules, and the two
|
|
167
|
+
stores `plans-store.ts`, `plan-purchase-store.ts`. None of it is React. The hooks and components
|
|
168
|
+
don't ship on this stack; the stores replace the hooks, and you write the components in your
|
|
169
|
+
framework to the contracts on this page:
|
|
170
|
+
|
|
171
|
+
- bind the stores with your framework's external-store primitive (Vue: `shallowRef` updated in
|
|
172
|
+
`subscribe`; Svelte: `readable(store.getState(), (set) => store.subscribe(() => set(store.getState())))`;
|
|
173
|
+
Solid: a signal set in `subscribe`). `createPlansStore({ initialPlans? })` per listing (`start()`
|
|
174
|
+
when mounted, `stop()` when unmounted), `createPlanPurchaseStore()` per purchase surface
|
|
175
|
+
(`purchase(planId, options?)` navigates the document itself). State in, actions out — exactly
|
|
176
|
+
the hooks' contracts above;
|
|
177
|
+
- your CTA to the `SubscribeButton.tsx` contract: rendered only when `buyable`, the DTO's label,
|
|
178
|
+
disabled with "Redirecting…" while `purchasingId === plan.id`, `error` inline.
|
|
179
|
+
|
|
180
|
+
Routes `/plans`, `/plans/:slug` (via `fetchPlanBySlug`, null → your 404); dev server on 4321; a
|
|
181
|
+
static build goes through `npx @wix/cli@latest release` with `site.outputDirectory` pointing at the
|
|
182
|
+
build folder, an SSR build is hosted by you. Page title and meta description from the DTO's `name`
|
|
183
|
+
and `description` — plans carry no `seoData`.
|
|
184
|
+
|
|
185
|
+
### Wiring — static site (`--stack static`, no bundler)
|
|
186
|
+
|
|
187
|
+
Read the reference file listed above before writing any surface.
|
|
188
|
+
|
|
189
|
+
`deploy.mjs pricing-plans --stack static --out site` put the REST layer in `site/js/wix/` (browser
|
|
190
|
+
ESM, the `.ts` beside each `.js` for reading). Everything the visitor loads lives under `site/` —
|
|
191
|
+
pages, styles, `js/` — and `wix.config.json`'s `site.outputDirectory` is `"./site"`; the project
|
|
192
|
+
root (config, plan, seed output) is never the upload. Same function names and DTOs as the table
|
|
193
|
+
above, so the contracts on this page hold unchanged: `fetchPlans`, `fetchPlanBySlug` from
|
|
194
|
+
`./js/wix/plans.js`; `purchasePlan` from `./js/wix/purchase.js`. The state machines ship too:
|
|
195
|
+
`createPlansStore` from `./js/wix/plans-store.js` (the grid — `start()` once the page is up,
|
|
196
|
+
render from `getState()` in `subscribe`) and `createPlanPurchaseStore` from
|
|
197
|
+
`./js/wix/plan-purchase-store.js` (one per surface; its `purchase(planId)` sets `purchasingId`,
|
|
198
|
+
then navigates the document to the hosted checkout, or records `error`). No components ship — you
|
|
199
|
+
write the rendering in plain JS: one render function per surface that reads `getState()`, called
|
|
200
|
+
from `subscribe`, with the CTA calling `purchase`. Pages are `plans.html` and `plan.html?slug=…`
|
|
201
|
+
(Wix static hosting serves files, not directories — name the file and link to it); the plan page
|
|
202
|
+
reads the slug, `fetchPlanBySlug`, renders its not-found state on null, and sets `document.title`
|
|
203
|
+
and the meta description from the DTO's `name`/`description`. The visitor token persists in
|
|
204
|
+
`localStorage` on its own; never mint per page. `npx @wix/cli@latest release` uploads `site/`.
|
|
205
|
+
|
|
206
|
+
### Wiring — server-rendered, another language (Flask, Laravel, Rails, …)
|
|
207
|
+
|
|
208
|
+
Read the reference file listed above before writing any surface.
|
|
209
|
+
|
|
210
|
+
Run `deploy.mjs pricing-plans --stack static` in the project folder anyway: `js/wix/` is both the
|
|
211
|
+
browser-side code and the readable spec. Then split by where the call runs. **Reads on the
|
|
212
|
+
server:** port `js/wix/plans.ts` and `plans-core.ts` to your language — the same two functions
|
|
213
|
+
returning the same DTO shapes as dicts (one POST with the literal body in the file), one anonymous
|
|
214
|
+
visitor token per process for these public reads (mint and refresh per `client.ts`) — and render
|
|
215
|
+
the grid and the plan page in your templates to the contracts above, so plan names and prices are
|
|
216
|
+
in the HTML; title and meta description from the DTO. **Purchasing in the browser:** the CTA on
|
|
217
|
+
`./js/wix/plan-purchase-store.js` (pass the plan id rendered into the page), exactly as the static
|
|
218
|
+
wiring above — the redirect session is created by the visitor's own token from the page, so the
|
|
219
|
+
server never handles per-visitor tokens. Routes stay `/plans`, `/plans/<slug>`. Add your public
|
|
220
|
+
https origin to the OAuth app's allowed domains before the hosted checkout can return.
|
|
221
|
+
|
|
222
|
+
**Pre-rendered (Frozen-Flask, Pelican, any static-site generator) → Wix-hosted.** Same port for
|
|
223
|
+
the reads, run at build time with one anonymous token; the generator emits `/plans` and one page
|
|
224
|
+
per slug from `fetchPlans()` (at most 100 public plans — one call). Run `deploy.mjs pricing-plans
|
|
225
|
+
--stack static --out <build dir>` so `js/wix/` is inside the output the pages import from, point
|
|
226
|
+
`site.outputDirectory` at that folder, `wix release`. Pages sit at different depths (`/`,
|
|
227
|
+
`/plans/…`): give the templates one base path to `js/wix/` (a template variable, or root-relative
|
|
228
|
+
`/js/wix/…`), never a relative `./js/wix/` — it breaks one level down. The frozen page is the
|
|
229
|
+
first paint; the CTA still runs client-side through `createPlanPurchaseStore()`. Close with the
|
|
230
|
+
live URL, the rebuild + release command, and one line for the owner: dashboard edits to plans
|
|
231
|
+
reach the site when that command runs; purchasing is live regardless.
|
|
232
|
+
|
|
78
233
|
### Wiring — React SPA (Vite etc.)
|
|
79
234
|
|
|
80
235
|
Import `./styles/global.css` once at the app entry (needs `@tailwindcss/vite` in the vite
|
|
81
|
-
plugins — deploy added the dep). Routes: `/plans` → your grid
|
|
82
|
-
`
|
|
236
|
+
plugins — deploy added the dep). Routes: `/plans` → your grid (`usePlans()` fetches client-side
|
|
237
|
+
when no `initialPlans` is passed); `/plans/:slug` → fetch with `fetchPlanBySlug(slug)`
|
|
238
|
+
client-side, then your detail surface (null → your not-found state). Deploy wrote the public
|
|
239
|
+
client id into `wix/config.ts`; nothing else to configure.
|
|
83
240
|
|
|
84
241
|
## Hard rules
|
|
85
242
|
|
|
86
|
-
- **Purchase only through the shipped exports** — `usePlanPurchase
|
|
87
|
-
hosted redirect session. Never hand-build a checkout URL, never call
|
|
243
|
+
- **Purchase only through the shipped exports** — `SubscribeButton` / `usePlanPurchase` /
|
|
244
|
+
`purchasePlan` own the hosted redirect session. Never hand-build a checkout URL, never call
|
|
88
245
|
`orders.createOnlineOrder` (member-only, and it leaves payment unhandled), never mark
|
|
89
246
|
anything paid.
|
|
90
247
|
- **Purchasing is members-only, and that's fine as-is**: the hosted flow handles member
|
|
@@ -96,9 +253,15 @@ plugins — deploy added the dep). Routes: `/plans` → your grid; `/plans/:slug
|
|
|
96
253
|
confirmation off the mere return.
|
|
97
254
|
- **Prices are display-only.** `price`/`billing` come pre-formatted; never compute a charge,
|
|
98
255
|
discount, or proration — Wix settles price, tax, and schedule at the hosted checkout.
|
|
99
|
-
- **
|
|
100
|
-
|
|
256
|
+
- **The CTA is the shipped `SubscribeButton`** — it alone decides to render (`buyable`) and what
|
|
257
|
+
to say; a card never rebuilds that decision.
|
|
258
|
+
- Don't wrap shipped calls in your own API routes — they run client-side by design.
|
|
259
|
+
- Where the shipped components deploy (Astro, React): theme via the `@theme` tokens, and your
|
|
260
|
+
markup uses Tailwind utilities on the same tokens — one design system across shipped and written
|
|
261
|
+
code. No parallel theme files, no hardcoded palette values. Where they don't (`lib`, `static`, a
|
|
262
|
+
port): style with whatever your stack does well, on one token set of your own.
|
|
101
263
|
- Live data or an honest empty state — never mock plans, prices, or perks.
|
|
264
|
+
- **Call every hook before any conditional return.** Hooks first, branches after.
|
|
102
265
|
|
|
103
266
|
## Out of scope (don't improvise these)
|
|
104
267
|
|
|
@@ -107,27 +270,25 @@ cancel/pause flows, and booking a covered bookings service with a membership —
|
|
|
107
270
|
logged-in member session this skill doesn't ship yet. Subscribers manage their plan through
|
|
108
271
|
Wix's emails and hosted member flows. If the user asks for a member area, route to
|
|
109
272
|
`wix-headless` (members recipes) rather than shipping code that returns nothing for visitors.
|
|
273
|
+
Anything elevated (creating or editing plans) runs server-side per `references/shared/CUSTOM_OPERATIONS.md`.
|
|
110
274
|
|
|
111
275
|
## Point the user to their dashboard
|
|
112
276
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
277
|
+
Hand the owner these links — `{siteId}` is `siteId` in `wix.config.json` (the deploy JSON prints it as
|
|
278
|
+
`dashboardUrl`).
|
|
279
|
+
|
|
280
|
+
| page | `https://manage.wix.com/dashboard/{siteId}/` + |
|
|
281
|
+
|---|---|
|
|
282
|
+
| Plans (purchases are a tab of this page) | `pricing-plans` |
|
|
283
|
+
| Create a plan | `pricing-plans/new` |
|
|
284
|
+
| Record a manual order | `pricing-plans/new-order` |
|
|
285
|
+
| Settings | `pricing-plans/settings` |
|
|
286
|
+
|
|
287
|
+
Editing a plan is reached from the list (no id path). Taking real payments needs a premium plan + a
|
|
288
|
+
connected payment method — mention it.
|
|
117
289
|
|
|
118
290
|
## Seeding
|
|
119
291
|
|
|
120
292
|
Per `seed/SEED.md` — plain-data `plan.json` into `seed-pricing-plans.mjs` from the project
|
|
121
293
|
root. Seed a tier ladder that exercises the UI (a free tier, a monthly, a yearly or
|
|
122
294
|
one-time; 3–4 perks each).
|
|
123
|
-
|
|
124
|
-
## Verify (before declaring done)
|
|
125
|
-
|
|
126
|
-
- [ ] `/plans` renders live plans SSR (view-source shows names) through YOUR components;
|
|
127
|
-
empty catalog shows your honest empty state.
|
|
128
|
-
- [ ] Every price shape reads right: free ("Free", no cadence), recurring ("$29.00 per
|
|
129
|
-
month"), one-time; Subscribe renders only on `buyable` plans.
|
|
130
|
-
- [ ] A plan page loads by slug and 404s (Astro) / shows not-found (SPA) on a bad slug.
|
|
131
|
-
- [ ] Subscribe redirects to the Wix-hosted checkout (login/signup + payment live there).
|
|
132
|
-
- [ ] Card/grid/detail/home are YOUR designs on the tokens; data-layer/hook files unedited.
|
|
133
|
-
- [ ] Dashboard links handed to the owner.
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
// REFERENCE detail surface: price/billing block + perks + terms +
|
|
2
|
-
// @theme tokens. Correct and complete; per the skill's model you design and build your own
|
|
3
|
-
//
|
|
4
|
-
import
|
|
1
|
+
// REFERENCE detail surface: price/billing block + perks + terms + the shipped SubscribeButton, on
|
|
2
|
+
// the @theme tokens. Correct and complete; per the skill's model you design and build your own
|
|
3
|
+
// (the plan itself arrives as an SSR-fetched DTO prop; the purchase control is SubscribeButton).
|
|
4
|
+
import SubscribeButton from "./SubscribeButton";
|
|
5
5
|
import type { PlanDetail } from "../../wix/pricing-plans/types";
|
|
6
6
|
|
|
7
7
|
export default function PlanDetailView({ plan }: { plan: PlanDetail }) {
|
|
8
|
-
const { purchase, purchasingId, error } = usePlanPurchase();
|
|
9
|
-
const purchasing = purchasingId === plan.id;
|
|
10
|
-
|
|
11
8
|
return (
|
|
12
9
|
<div>
|
|
13
10
|
<p>
|
|
@@ -31,19 +28,11 @@ export default function PlanDetailView({ plan }: { plan: PlanDetail }) {
|
|
|
31
28
|
</ul>
|
|
32
29
|
)}
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
onClick={() => void purchase(plan.id).catch(() => {})}
|
|
40
|
-
className="mt-6 rounded-full bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
41
|
-
>
|
|
42
|
-
{purchasing ? "Redirecting…" : plan.free ? "Get this plan" : `Subscribe · ${plan.price}`}
|
|
43
|
-
</button>
|
|
44
|
-
) : (
|
|
45
|
-
<p className="mt-6 text-sm text-muted-foreground">This plan is assigned by the site owner.</p>
|
|
46
|
-
)}
|
|
31
|
+
<div className="mt-6">
|
|
32
|
+
<SubscribeButton plan={plan} className="rounded-full bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50">
|
|
33
|
+
{plan.free ? "Get this plan" : `Subscribe · ${plan.price}`}
|
|
34
|
+
</SubscribeButton>
|
|
35
|
+
</div>
|
|
47
36
|
|
|
48
37
|
{plan.termsAndConditions && (
|
|
49
38
|
<div className="mt-10 border-t border-border pt-5">
|
package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// REFERENCE pricing surface: plan cards in a grid on the @theme tokens. Correct and
|
|
2
|
-
// complete; per the skill's model you design and build your own on usePlans
|
|
3
|
-
//
|
|
2
|
+
// complete; per the skill's model you design and build your own on usePlans, with the shipped
|
|
3
|
+
// SubscribeButton as each card's purchase control.
|
|
4
4
|
import type { ComponentType, ReactNode } from "react";
|
|
5
5
|
import { usePlans } from "../../hooks/pricing-plans/usePlans";
|
|
6
|
-
import
|
|
6
|
+
import SubscribeButton from "./SubscribeButton";
|
|
7
7
|
import type { PlanSummary } from "../../wix/pricing-plans/types";
|
|
8
8
|
|
|
9
9
|
export interface LinkLikeProps {
|
|
@@ -20,19 +20,11 @@ const PlainLink = ({ href, className, children }: LinkLikeProps) => (
|
|
|
20
20
|
|
|
21
21
|
export interface PlanCardProps {
|
|
22
22
|
plan: PlanSummary;
|
|
23
|
-
onSubscribe: (planId: string) => void;
|
|
24
|
-
purchasing: boolean;
|
|
25
23
|
planHref?: (slug: string) => string;
|
|
26
24
|
LinkComponent?: ComponentType<LinkLikeProps>;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
export function PlanCard({
|
|
30
|
-
plan,
|
|
31
|
-
onSubscribe,
|
|
32
|
-
purchasing,
|
|
33
|
-
planHref = (slug) => `/plans/${slug}`,
|
|
34
|
-
LinkComponent = PlainLink,
|
|
35
|
-
}: PlanCardProps) {
|
|
27
|
+
export function PlanCard({ plan, planHref = (slug) => `/plans/${slug}`, LinkComponent = PlainLink }: PlanCardProps) {
|
|
36
28
|
return (
|
|
37
29
|
<div className="flex h-full flex-col rounded-lg border border-border bg-background p-6">
|
|
38
30
|
<p className="text-base font-semibold text-foreground">{plan.name}</p>
|
|
@@ -57,16 +49,7 @@ export function PlanCard({
|
|
|
57
49
|
</ul>
|
|
58
50
|
)}
|
|
59
51
|
<div className="mt-auto grid gap-2 pt-6">
|
|
60
|
-
{plan
|
|
61
|
-
<button
|
|
62
|
-
type="button"
|
|
63
|
-
disabled={purchasing}
|
|
64
|
-
onClick={() => onSubscribe(plan.id)}
|
|
65
|
-
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"
|
|
66
|
-
>
|
|
67
|
-
{purchasing ? "Redirecting…" : "Subscribe"}
|
|
68
|
-
</button>
|
|
69
|
-
)}
|
|
52
|
+
<SubscribeButton plan={plan} assignedText={null} />
|
|
70
53
|
<LinkComponent
|
|
71
54
|
href={planHref(plan.slug)}
|
|
72
55
|
className="rounded-full border border-border px-6 py-2.5 text-center text-sm font-semibold text-foreground no-underline transition-colors hover:bg-secondary"
|
|
@@ -94,11 +77,10 @@ export default function PlansView({
|
|
|
94
77
|
CardComponent = PlanCard,
|
|
95
78
|
}: PlansViewProps) {
|
|
96
79
|
const { plans, error } = usePlans({ initialPlans });
|
|
97
|
-
const { purchase, purchasingId, error: purchaseError } = usePlanPurchase();
|
|
98
80
|
|
|
99
81
|
return (
|
|
100
82
|
<div>
|
|
101
|
-
{
|
|
83
|
+
{error && <p className="mb-4 text-sm text-red-600">{error}</p>}
|
|
102
84
|
{plans === null ? (
|
|
103
85
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3" aria-busy="true">
|
|
104
86
|
{Array.from({ length: 3 }, (_, i) => (
|
|
@@ -110,14 +92,7 @@ export default function PlansView({
|
|
|
110
92
|
) : (
|
|
111
93
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
112
94
|
{plans.map((p) => (
|
|
113
|
-
<CardComponent
|
|
114
|
-
key={p.id}
|
|
115
|
-
plan={p}
|
|
116
|
-
onSubscribe={(id) => void purchase(id).catch(() => {})}
|
|
117
|
-
purchasing={purchasingId === p.id}
|
|
118
|
-
planHref={planHref}
|
|
119
|
-
LinkComponent={LinkComponent}
|
|
120
|
-
/>
|
|
95
|
+
<CardComponent key={p.id} plan={p} planHref={planHref} LinkComponent={LinkComponent} />
|
|
121
96
|
))}
|
|
122
97
|
</div>
|
|
123
98
|
)}
|