@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
|
@@ -1,55 +1,83 @@
|
|
|
1
1
|
# Portfolio — playbook
|
|
2
2
|
|
|
3
3
|
The portfolio machinery ships as files — collections, projects, the per-project media gallery
|
|
4
|
-
(image AND video items), typed end-to-end. Portfolio is
|
|
5
|
-
no `@wix/ecom`. **The presentation is yours**: you design and
|
|
6
|
-
the project card + grid, the project-detail gallery, and the home
|
|
7
|
-
hooks/DTOs, plus the brand. You never write read logic; you never skip designing.
|
|
4
|
+
(image AND video items), the state machines behind every surface, typed end-to-end. Portfolio is
|
|
5
|
+
**read-only**: no cart, no checkout, no `@wix/ecom`. **The presentation is yours**: you design and
|
|
6
|
+
implement the collection card, the project card + grid, the project-detail gallery, and the home
|
|
7
|
+
page on the shipped hooks/DTOs, plus the brand. You never write read logic; you never skip designing.
|
|
8
8
|
|
|
9
9
|
## The file map (deployed into `src/`)
|
|
10
10
|
|
|
11
|
-
**
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
**On Astro and React the shipped files are tested and work as they are** — this table and the
|
|
12
|
+
contracts below are everything you need to use them, so don't spend the run reading their source;
|
|
13
|
+
wire them and build your surfaces. Reading them is the right move when something is off (a runtime
|
|
14
|
+
error, a field this playbook doesn't cover) or when the brief wants a behaviour they don't offer —
|
|
15
|
+
then read the file that owns it and change or extend it. On `lib`, `static`, and a port the
|
|
16
|
+
components don't deploy at all, and each wiring section below opens with the files to read before
|
|
17
|
+
writing their equivalents. Files you edit: `SiteLayout.astro`, `styles/global.css`, and the three
|
|
18
|
+
pages' island imports. Files you **create**: your gallery, collection, and project-detail islands
|
|
19
|
+
(skeletons below), plus your home page.
|
|
14
20
|
|
|
15
21
|
| file | what it is |
|
|
16
22
|
|---|---|
|
|
17
|
-
| `wix/config.ts` · `wix/sdk.ts`
|
|
18
|
-
| `wix/
|
|
19
|
-
| `wix/portfolio/
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `hooks/portfolio/
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
23
|
+
| `wix/config.ts` · `wix/sdk.ts` | shared auth seam (deploy configures it — nothing to set by hand) |
|
|
24
|
+
| `wix/media.ts` · `wix/money.ts` | `imgAttrs(url, sizes, ratio?)` — every `<img>` attribute for a DTO image (`src`, `srcSet`, `sizes`, lazy): `<img {...imgAttrs(c.imageUrl, "33vw", 0.75)} alt={c.title} />`; `imgSrc()` / `imgSrcSet()` underneath, already used by everything shipped |
|
|
25
|
+
| `wix/portfolio/types.ts` | the DTOs (`CollectionSummary`, `ProjectSummary`, `ProjectDetail`, `ProjectDetailRow`, `GalleryItem`) — contracts inlined below |
|
|
26
|
+
| `wix/portfolio/portfolio.ts` | `fetchCollections`, `fetchCollectionBySlug`, `fetchProjects`, `fetchProjectBySlug`, `fetchProjectGallery` — the transport; the rules and DTO mappers (hidden filter, dashboard order, cover fallback, media kinds, `mediaElement`) are in `portfolio-core.ts` beside it (shared with the REST layer) |
|
|
27
|
+
| `wix/portfolio/collections-store.ts` · `collection-projects-store.ts` · `project-detail-store.ts` | the gallery, collection-page, and project-page state machines, framework-free (`createCollectionsStore()`, `createCollectionProjectsStore(slug)`, `createProjectDetailStore(slug)` — `getState`/`subscribe`/`start`/`stop`, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
|
|
28
|
+
| `hooks/portfolio/useCollections.ts` | React binding of `collections-store.ts` — contract below |
|
|
29
|
+
| `hooks/portfolio/useCollectionProjects.ts` | React binding of `collection-projects-store.ts`: one collection + its projects, by slug — contract below |
|
|
30
|
+
| `hooks/portfolio/useProjectDetail.ts` | React binding of `project-detail-store.ts`: one project + its media gallery, by slug — contract below |
|
|
31
|
+
| `components/portfolio/GalleryMedia.tsx` | one gallery item as its one element — `<video>` with poster for a video item, `<img>` for an image item, nothing for an unrenderable one; `GalleryLink` wraps it in the owner's link when set — **wire as-is** in your project surface (`<GalleryLink item={i}><GalleryMedia item={i} /></GalleryLink>`) |
|
|
32
|
+
| `components/portfolio/CollectionsView.tsx` (+ `CollectionCard`) · `CollectionProjectsView.tsx` (+ `ProjectCard`) · `ProjectDetailView.tsx` | **REFERENCE implementations** — correct, plain; build your own instead of shipping them |
|
|
33
|
+
| `styles/global.css` | **the design system**: Tailwind v4 + the `@theme` token block (colors, radii, fonts). Everything, shipped and yours, styles from these tokens |
|
|
25
34
|
|
|
26
35
|
Astro stack additionally gets:
|
|
27
36
|
|
|
28
37
|
| file | what it is |
|
|
29
38
|
|---|---|
|
|
30
|
-
| `layouts/SiteLayout.astro` | site chrome — **yours to brand
|
|
39
|
+
| `layouts/SiteLayout.astro` | the site chrome — **yours to brand**: header, footer, nav. Keep the `<slot name="seo-tags" />` and the global.css import. If another vertical is also deployed, its layout won — add a Portfolio nav link there |
|
|
31
40
|
| `pages/portfolio.astro` | SSR collections gallery — **keep the frontmatter**, swap the island import to YOUR component |
|
|
32
41
|
| `pages/portfolio/[slug].astro` | SSR collection page (header + projects) — **keep the frontmatter**, swap the island import |
|
|
33
|
-
| `pages/projects/[slug].astro` | SSR project detail + gallery — **keep the frontmatter**, swap the island import. Portfolio has no
|
|
42
|
+
| `pages/projects/[slug].astro` | SSR project detail + gallery — **keep the frontmatter**, swap the island import. Portfolio has no Wix SEO item type, so these pages carry plain `<title>`/`<meta>` from the DTO (no `wixMetadata`/`<SEO.Tags>`) |
|
|
34
43
|
|
|
35
44
|
## What you build — the design job
|
|
36
45
|
|
|
37
|
-
1. **The collection card + gallery surface** — your tile (cover, title, description) and
|
|
38
|
-
|
|
46
|
+
1. **The collection card + gallery surface** — your tile (cover, title, description) and rhythm,
|
|
47
|
+
with skeletons while loading and an honest empty state — on `useCollections`.
|
|
39
48
|
2. **The project card + collection page** — the collection header and your project grid — on
|
|
40
|
-
`useCollectionProjects` (route a not-found state off `notFound`, never off a transient
|
|
41
|
-
null).
|
|
49
|
+
`useCollectionProjects` (route a not-found state off `notFound`, never off a transient null).
|
|
42
50
|
3. **The project-detail surface** — title/description, the `details` rows (text or link, as
|
|
43
|
-
given), and the media gallery
|
|
44
|
-
|
|
45
|
-
the project cover when it exists, else an honest empty note — on `useProjectDetail`.
|
|
51
|
+
given), and the media gallery through the shipped `GalleryMedia`; empty gallery → the project
|
|
52
|
+
cover when it exists, else an honest empty note — on `useProjectDetail`.
|
|
46
53
|
4. **The home page** — hero, featured collections or projects (fetch in frontmatter → your
|
|
47
54
|
components), brand story.
|
|
48
55
|
|
|
49
56
|
Plus the **theme** (`@theme` block, one edit) and the **chrome** (`SiteLayout`, one pass).
|
|
50
|
-
Style everything with Tailwind utilities on the tokens.
|
|
51
57
|
|
|
52
|
-
###
|
|
58
|
+
### What a complete portfolio shows (recommended defaults)
|
|
59
|
+
|
|
60
|
+
Defaults for a brief that says nothing about them; the prompt wins where it differs. Look at the
|
|
61
|
+
content before designing (how many collections, covers, project count, media mix) and design for
|
|
62
|
+
this portfolio, not for a stereotype of its field. Then, by default:
|
|
63
|
+
|
|
64
|
+
- **Home:** what the work is and one path into it in the first screen; real collections or
|
|
65
|
+
projects under truthful headings; a collection tile links to `/portfolio/<slug>` and shows that
|
|
66
|
+
collection's own cover.
|
|
67
|
+
- **Gallery (`/portfolio`):** a real collection card — cover, title — in the first screen;
|
|
68
|
+
loading, empty, and error states that look different.
|
|
69
|
+
- **Collection page:** the collection's title and description as the header, then only its
|
|
70
|
+
projects; a slug that resolves to nothing shows the not-found state alone — no header or empty
|
|
71
|
+
grid around it.
|
|
72
|
+
- **Project page:** title, description, and the `details` rows before the gallery; gallery items
|
|
73
|
+
in dashboard order, each through `GalleryMedia`, captions from `item.title` when present; on a
|
|
74
|
+
phone a gallery image is a bounded band, not a full-height hero.
|
|
75
|
+
- **Images:** every DTO image through `imgAttrs` (a `src`, a `srcSet`, and `sizes` in one spread;
|
|
76
|
+
an empty `imageUrl` gives `{}` — render your placeholder then).
|
|
77
|
+
- **Copy:** nothing the owner didn't supply — no invented clients, awards, or dates; no Wix IDs or
|
|
78
|
+
technical words in visible text.
|
|
79
|
+
|
|
80
|
+
### The contracts your components consume (tested and work as they are; read when something is off or the brief wants more)
|
|
53
81
|
|
|
54
82
|
```ts
|
|
55
83
|
// CollectionSummary (tiles): { id, slug, title, description, imageUrl /* "" when none */ }
|
|
@@ -73,62 +101,213 @@ Style everything with Tailwind utilities on the tokens.
|
|
|
73
101
|
// { project: ProjectDetail|null, notFound,
|
|
74
102
|
// items: GalleryItem[]|null, // dashboard order; null = loading
|
|
75
103
|
// error }
|
|
104
|
+
|
|
105
|
+
// <GalleryMedia item={i} className? /> renders i as ONE element by kind (video+poster / img /
|
|
106
|
+
// nothing); <GalleryLink item={i}>…</GalleryLink> wraps in the owner's link when linkUrl is set.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### The pages and islands you create — skeletons
|
|
110
|
+
|
|
111
|
+
The class names here are the Astro/React spelling of layout rules that hold on every stack; on a
|
|
112
|
+
stack where the components don't deploy (`lib`, `static`, a port), keep the rule and write it in
|
|
113
|
+
your own CSS. The three pages ship with their frontmatter (fetch → DTO props → island, guarded
|
|
114
|
+
against an SSR throw, a real 404 on a missing slug) — keep it, and point the island import at
|
|
115
|
+
yours. Hooks first, branches after (an early return above a hook changes hook order between
|
|
116
|
+
renders and React throws). Render every state totally; nothing in a render path may throw.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// src/components/portfolio/Gallery.tsx — YOU build it; pages/portfolio.astro mounts it.
|
|
120
|
+
import { useCollections } from "../../hooks/portfolio/useCollections";
|
|
121
|
+
import { imgAttrs } from "../../wix/media";
|
|
122
|
+
import type { CollectionSummary } from "../../wix/portfolio/types";
|
|
123
|
+
|
|
124
|
+
export default function Gallery(props: { initialCollections?: CollectionSummary[] }) {
|
|
125
|
+
const { collections, error } = useCollections(props);
|
|
126
|
+
// …you implement the render:
|
|
127
|
+
// • error → a short inline message
|
|
128
|
+
// • collections === null → skeleton tiles; [] → your honest empty state
|
|
129
|
+
// • else YOUR grid of YOUR tiles: <a href={`/portfolio/${c.slug}`}> around the cover
|
|
130
|
+
// (<img {...imgAttrs(c.imageUrl, "(min-width: 1024px) 33vw, 50vw", 0.75)} alt={c.title} />,
|
|
131
|
+
// your placeholder when imageUrl is ""), title, description; the title WRAPS (`min-w-0`,
|
|
132
|
+
// `break-words`), never `truncate`
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// src/components/portfolio/CollectionPage.tsx — YOU build it; pages/portfolio/[slug].astro mounts it.
|
|
138
|
+
import { useCollectionProjects } from "../../hooks/portfolio/useCollectionProjects";
|
|
139
|
+
import type { CollectionSummary, ProjectSummary } from "../../wix/portfolio/types";
|
|
140
|
+
|
|
141
|
+
export default function CollectionPage(props: { slug: string; initialCollection?: CollectionSummary; initialProjects?: ProjectSummary[] }) {
|
|
142
|
+
const { collection, notFound, projects, error } = useCollectionProjects(props.slug, props);
|
|
143
|
+
// …you implement the render. Handle in order:
|
|
144
|
+
// notFound → the not-found state ALONE (no header, no empty grid around it)
|
|
145
|
+
// header: collection.title and description (a skeleton line while collection is null)
|
|
146
|
+
// error → inline message; projects === null → skeleton tiles; [] → your empty state
|
|
147
|
+
// else YOUR grid of YOUR project tiles linking to `/projects/${p.slug}` (cover via imgAttrs)
|
|
148
|
+
}
|
|
76
149
|
```
|
|
77
150
|
|
|
151
|
+
```tsx
|
|
152
|
+
// src/components/portfolio/ProjectPage.tsx — YOU build it; pages/projects/[slug].astro mounts it.
|
|
153
|
+
import { useProjectDetail } from "../../hooks/portfolio/useProjectDetail";
|
|
154
|
+
import { GalleryLink, GalleryMedia } from "./GalleryMedia";
|
|
155
|
+
import { imgAttrs } from "../../wix/media";
|
|
156
|
+
import type { GalleryItem, ProjectDetail } from "../../wix/portfolio/types";
|
|
157
|
+
|
|
158
|
+
export default function ProjectPage(props: { slug: string; initialProject?: ProjectDetail; initialItems?: GalleryItem[] }) {
|
|
159
|
+
const { project, notFound, items, error } = useProjectDetail(props.slug, props);
|
|
160
|
+
// …you implement the render. Handle in order:
|
|
161
|
+
// notFound → "doesn't exist (anymore)"; !project → a loading placeholder
|
|
162
|
+
// else: title, description, then project.details as a definition list — a row with url is a
|
|
163
|
+
// link (target as given), else plain text; never invent a row
|
|
164
|
+
// items === null → a skeleton; [] → the cover (imgAttrs) when project.imageUrl, else an honest
|
|
165
|
+
// empty note; else each item as
|
|
166
|
+
// <figure><GalleryLink item={i}><GalleryMedia item={i} className="…" /></GalleryLink>
|
|
167
|
+
// {i.title && <figcaption>{i.title}</figcaption>}</figure> — never your own <img>/<video>
|
|
168
|
+
// branch (the shipped one owns kind, poster, and the unrenderable case)
|
|
169
|
+
// on a phone the media column is a bounded band (`max-h-[70vh]`, `object-contain`)
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### The reference files for stacks where the components don't deploy
|
|
174
|
+
|
|
175
|
+
On `lib`, `static`, and a port, nothing under `components/` or `hooks/` arrives. The state machines
|
|
176
|
+
behind the hooks do arrive — `wix/portfolio/collections-store.ts`, `collection-projects-store.ts`,
|
|
177
|
+
`project-detail-store.ts` — so you never rewrite them: create a store per surface, `subscribe`,
|
|
178
|
+
render from `getState()`. Their `*State` interfaces are the render contract; read those. The one
|
|
179
|
+
rendering rule worth reading before writing your gallery:
|
|
180
|
+
|
|
181
|
+
1. `components/portfolio/GalleryMedia.tsx` — the kind branch as working code, and the rule it
|
|
182
|
+
renders, `mediaElement(item)` in `wix/portfolio/portfolio-core.ts`, which ships on every stack:
|
|
183
|
+
it returns `{ tag: "video", attrs }`, `{ tag: "img", attrs }`, or null — create the element
|
|
184
|
+
from it (`document.createElement(el.tag)` + `setAttribute` per entry) instead of branching on
|
|
185
|
+
`kind` yourself.
|
|
186
|
+
|
|
187
|
+
All under `references/portfolio/app/`.
|
|
188
|
+
|
|
78
189
|
### Wiring — Astro (default)
|
|
79
190
|
|
|
80
191
|
1. Set the `@theme` tokens (one edit); brand `SiteLayout.astro` (one pass — merge into the
|
|
81
192
|
existing layout instead if another vertical is deployed).
|
|
82
|
-
2. Write your
|
|
83
|
-
references)
|
|
84
|
-
`pages/portfolio/[slug].astro`, and `pages/projects/[slug].astro`. Islands are
|
|
85
|
-
|
|
86
|
-
|
|
193
|
+
2. Write your islands under `src/components/portfolio/` (new names — don't overwrite the
|
|
194
|
+
references) per the skeletons above; swap the island imports in `pages/portfolio.astro`,
|
|
195
|
+
`pages/portfolio/[slug].astro`, and `pages/projects/[slug].astro`. Islands are `client:load`
|
|
196
|
+
with the SSR props. Author your surfaces in as few messages as possible — batch multiple Writes
|
|
197
|
+
per message.
|
|
87
198
|
3. Write `pages/index.astro` (home) — it exists from the scaffold; Read it before overwriting.
|
|
88
199
|
|
|
200
|
+
### Wiring — another JS framework (`--stack lib`: Vue, Svelte, Solid, plain Vite)
|
|
201
|
+
|
|
202
|
+
Read the reference files listed above before writing any surface.
|
|
203
|
+
|
|
204
|
+
`deploy.mjs portfolio --stack lib` put the data layer in `src/wix/` and nothing else: `sdk.ts`
|
|
205
|
+
(the visitor client, configured with the public client id), `media.ts`, `money.ts`, and
|
|
206
|
+
`wix/portfolio/` — `portfolio.ts`, `types.ts`, the `portfolio-core.ts` rules, and the three stores.
|
|
207
|
+
None of it is React. The hooks and components don't ship on this stack; the stores replace the
|
|
208
|
+
hooks, and you write the components in your framework to the contracts on this page:
|
|
209
|
+
|
|
210
|
+
- bind the stores with your framework's external-store primitive (Vue: `shallowRef` updated in
|
|
211
|
+
`subscribe`; Svelte: `readable(store.getState(), (set) => store.subscribe(() => set(store.getState())))`;
|
|
212
|
+
Solid: a signal set in `subscribe`). `createCollectionsStore(options)` per gallery,
|
|
213
|
+
`createCollectionProjectsStore(slug, options)` per collection page,
|
|
214
|
+
`createProjectDetailStore(slug, options)` per project page — `start()` when mounted, `stop()`
|
|
215
|
+
when unmounted. State in, exactly the hooks' contracts above;
|
|
216
|
+
- gallery items through `mediaElement(item)` from `portfolio-core.ts`; images through
|
|
217
|
+
`imgAttrs` from `media.ts`.
|
|
218
|
+
|
|
219
|
+
Routes `/portfolio`, `/portfolio/:slug` (`fetchCollectionBySlug`, null → your 404),
|
|
220
|
+
`/projects/:slug` (`fetchProjectBySlug`, null → your 404); dev server on 4321; a static build goes
|
|
221
|
+
through `npx @wix/cli@latest release` with `site.outputDirectory` pointing at the build folder, an
|
|
222
|
+
SSR build is hosted by you. Page titles and descriptions from the DTO (Portfolio has no SEO item type).
|
|
223
|
+
|
|
224
|
+
### Wiring — static site (`--stack static`, no bundler)
|
|
225
|
+
|
|
226
|
+
Read the reference files listed above before writing any surface.
|
|
227
|
+
|
|
228
|
+
`deploy.mjs portfolio --stack static --out site` put the REST layer in `site/js/wix/` (browser ESM,
|
|
229
|
+
the `.ts` beside each `.js` for reading). Everything the visitor loads lives under `site/` — pages,
|
|
230
|
+
styles, `js/` — and `wix.config.json`'s `site.outputDirectory` is `"./site"`; the project root
|
|
231
|
+
(config, plan, seed output) is never the upload. Same function names and DTOs as the table above,
|
|
232
|
+
so the contracts on this page hold unchanged: `fetchCollections`, `fetchCollectionBySlug`,
|
|
233
|
+
`fetchProjects`, `fetchProjectBySlug`, `fetchProjectGallery` from `./js/wix/portfolio.js`. The
|
|
234
|
+
state machines ship too: `createCollectionsStore` from `./js/wix/collections-store.js`,
|
|
235
|
+
`createCollectionProjectsStore` from `./js/wix/collection-projects-store.js`,
|
|
236
|
+
`createProjectDetailStore` from `./js/wix/project-detail-store.js` (`start()` once the page is up;
|
|
237
|
+
`notFound` is the 404 signal), `mediaElement` from `./js/wix/portfolio-core.js` (the gallery item
|
|
238
|
+
→ element rule), and `imgAttrs` from `./js/wix/media.js` (attribute names as HTML spells them —
|
|
239
|
+
`setAttribute` per entry). No components ship — you write the rendering in plain JS: one render
|
|
240
|
+
function per surface that reads `getState()`, called from `subscribe`. Wix static hosting serves
|
|
241
|
+
files, not directories: pages are `portfolio.html`, `collection.html?slug=…`,
|
|
242
|
+
`project.html?slug=…`; name the file and link to it. Set `document.title` and the meta description
|
|
243
|
+
from the DTO (`collection.title`/`description`, `project.title`/`description`) once the store
|
|
244
|
+
populates — Portfolio has no `seoData`. The visitor token persists in `localStorage` on its own;
|
|
245
|
+
never mint per page. `npx @wix/cli@latest release` uploads `site/`.
|
|
246
|
+
|
|
247
|
+
### Wiring — server-rendered, another language (Flask, Laravel, Rails, …)
|
|
248
|
+
|
|
249
|
+
Read the reference files listed above before writing any surface.
|
|
250
|
+
|
|
251
|
+
Run `deploy.mjs portfolio --stack static` in the project folder anyway: `js/wix/` is the readable
|
|
252
|
+
spec. This vertical is reads only, so everything renders on the server: port `js/wix/portfolio.ts`
|
|
253
|
+
and `portfolio-core.ts` to your language — the same five functions returning the same DTO shapes
|
|
254
|
+
as dicts, one anonymous visitor token per process (mint and refresh per `client.ts`) — and render
|
|
255
|
+
gallery, collection, and project pages in your templates to the contracts above, so titles are in
|
|
256
|
+
the HTML; `<title>`/`<meta>` from the DTO. Carry the URLs and bodies over as they are (the list
|
|
257
|
+
calls are `GET` with `paging.limit`; the slug lookups `POST …/query` with
|
|
258
|
+
`{ query: { filter: { slug }, cursorPaging: { limit: 1 } } }`). The gallery's kind rule is
|
|
259
|
+
`mediaElement` — port it, don't re-derive it. Nothing here runs in the browser unless you want
|
|
260
|
+
client-side loading; then the static wiring above applies. Routes stay `/portfolio`,
|
|
261
|
+
`/portfolio/<slug>`, `/projects/<slug>`.
|
|
262
|
+
|
|
263
|
+
**Pre-rendered (Frozen-Flask, Pelican, any static-site generator) → Wix-hosted.** Same port for
|
|
264
|
+
the reads, run at build time with one anonymous token; the generator must emit a page for every
|
|
265
|
+
collection (`fetchCollections()`) and every project (`fetchProjects()`) — both return one page of
|
|
266
|
+
up to 100 in list order, the same as the SDK path; a portfolio larger than that walks
|
|
267
|
+
`pagingMetadataV2.cursors.next` on the list response (add it to your port when you hit it). Run
|
|
268
|
+
`deploy.mjs portfolio --stack static --out <build dir>` only if a page loads anything client-side;
|
|
269
|
+
point `site.outputDirectory` at the build folder, `wix release`. Pages sit at different depths
|
|
270
|
+
(`/`, `/portfolio/…`, `/projects/…`): give the templates one base path to any `js/wix/` import (a
|
|
271
|
+
template variable, or root-relative `/js/wix/…`), never a relative `./js/wix/` — it breaks one
|
|
272
|
+
level down. Close with the live URL, the rebuild + release command, and one line for the owner:
|
|
273
|
+
dashboard edits reach the site when that command runs.
|
|
274
|
+
|
|
89
275
|
### Wiring — React SPA (Vite etc.)
|
|
90
276
|
|
|
91
|
-
Import `./styles/global.css` once at the app entry (needs `@tailwindcss/vite` in the vite
|
|
92
|
-
|
|
93
|
-
your collection page on `useCollectionProjects(slug)`; `/projects/:slug` → your detail
|
|
94
|
-
|
|
277
|
+
Import `./styles/global.css` once at the app entry (needs `@tailwindcss/vite` in the vite plugins —
|
|
278
|
+
deploy added the dep). Routes: `/portfolio` → your gallery on `useCollections()`; `/portfolio/:slug`
|
|
279
|
+
→ your collection page on `useCollectionProjects(slug)`; `/projects/:slug` → your detail surface on
|
|
280
|
+
`useProjectDetail(slug)` (the hooks fetch client-side when no `initial*` is passed; route the 404
|
|
281
|
+
view off `notFound`). Deploy wrote the public client id into `wix/config.ts`; nothing else to configure.
|
|
95
282
|
|
|
96
283
|
## Hard rules
|
|
97
284
|
|
|
98
285
|
- **Reads only through the shipped exports** — they filter `hidden`, sort collections by the
|
|
99
286
|
owner's `sortOrder` (projects have none — list order), and resolve every `wix:image://` /
|
|
100
|
-
`wix:video://` value. Never hand-build a `wixstatic.com` URL, never render a raw media
|
|
101
|
-
|
|
287
|
+
`wix:video://` value. Never hand-build a `wixstatic.com` URL, never render a raw media string
|
|
288
|
+
into `src`.
|
|
102
289
|
- **This vertical sells nothing** — no cart, no checkout, no `@wix/ecom`/`@wix/redirects`
|
|
103
290
|
imports. A "contact about this project" CTA is a link, not a purchase flow.
|
|
104
|
-
- **Route not-found off `notFound`**, and surface `error` — a transient `null` is loading,
|
|
105
|
-
not a 404.
|
|
291
|
+
- **Route not-found off `notFound`**, and surface `error` — a transient `null` is loading, not a 404.
|
|
106
292
|
- **Render `details` rows as given** (text or link per row); don't invent metadata.
|
|
107
|
-
- **Gallery items
|
|
108
|
-
|
|
109
|
-
-
|
|
293
|
+
- **Gallery items render through `GalleryMedia`** (or `mediaElement` where it doesn't deploy) —
|
|
294
|
+
never your own branch on `kind`.
|
|
295
|
+
- Where the shipped components deploy (Astro, React): theme via the `@theme` tokens, and your
|
|
296
|
+
markup uses Tailwind utilities on the same tokens — one design system across shipped and written
|
|
297
|
+
code. No parallel theme files, no hardcoded palette values. Where they don't (`lib`, `static`, a
|
|
298
|
+
port): style with whatever your stack does well, on one token set of your own.
|
|
110
299
|
- Live data or an honest empty state — never mock collections, projects, or media.
|
|
300
|
+
- **Call every hook before any conditional return.** Hooks first, branches after.
|
|
111
301
|
|
|
112
302
|
## Point the user to their dashboard
|
|
113
303
|
|
|
114
304
|
Give the owner the dashboard link — the deploy step's JSON printed `dashboardUrl`; append
|
|
115
|
-
`/wix-portfolio/projects` for Portfolio management (Projects and Collections are tabs on that
|
|
116
|
-
|
|
305
|
+
`/wix-portfolio/projects` for Portfolio management (Projects and Collections are tabs on that one
|
|
306
|
+
page: Projects adds projects and their media galleries; Collections groups them).
|
|
117
307
|
|
|
118
308
|
## Seeding
|
|
119
309
|
|
|
120
|
-
Per `seed/SEED.md` — plain-data `plan.json` into `seed-portfolio.mjs` from the project root.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
## Verify (before declaring done)
|
|
126
|
-
|
|
127
|
-
- [ ] `/portfolio` renders live collections SSR (view-source shows titles) through YOUR
|
|
128
|
-
components; empty portfolio shows your honest empty state.
|
|
129
|
-
- [ ] A collection page lists its projects (and only its); an unknown slug 404s.
|
|
130
|
-
- [ ] A project page renders the gallery in order, the `details` rows, and — if any project
|
|
131
|
-
has a video item — a playing `<video>` with poster.
|
|
132
|
-
- [ ] Card/grid/detail/home are YOUR designs on the tokens; data-layer/hook files unedited.
|
|
133
|
-
- [ ] No `@wix/ecom` import anywhere; no mocked content.
|
|
134
|
-
- [ ] Dashboard link handed to the owner.
|
|
310
|
+
Per `seed/SEED.md` — plain-data `plan.json` into `seed-portfolio.mjs` from the project root. Seed
|
|
311
|
+
collections + projects that exercise the UI (covers on everything, 2–3 gallery images per project,
|
|
312
|
+
`details` rows on a couple of projects). A fresh install ships sample content — seeding never
|
|
313
|
+
deletes it; ask the owner before any cleanup.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// useCollectionProjects (ProjectCard is reusable for an all-work grid or a home strip).
|
|
4
4
|
import type { ComponentType, ReactNode } from "react";
|
|
5
5
|
import { useCollectionProjects } from "../../hooks/portfolio/useCollectionProjects";
|
|
6
|
+
import { imgAttrs } from "../../wix/media";
|
|
6
7
|
import type { CollectionSummary, ProjectSummary } from "../../wix/portfolio/types";
|
|
7
8
|
|
|
8
9
|
export interface LinkLikeProps {
|
|
@@ -33,9 +34,8 @@ export function ProjectCard({
|
|
|
33
34
|
<div className="relative aspect-[4/3] overflow-hidden rounded-lg bg-secondary">
|
|
34
35
|
{project.imageUrl && (
|
|
35
36
|
<img
|
|
36
|
-
|
|
37
|
+
{...imgAttrs(project.imageUrl, "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw", 0.75)}
|
|
37
38
|
alt={project.title}
|
|
38
|
-
loading="lazy"
|
|
39
39
|
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
|
|
40
40
|
/>
|
|
41
41
|
)}
|
package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Correct and complete; per the skill's model you design and build your own on useCollections.
|
|
3
3
|
import type { ComponentType, ReactNode } from "react";
|
|
4
4
|
import { useCollections } from "../../hooks/portfolio/useCollections";
|
|
5
|
+
import { imgAttrs } from "../../wix/media";
|
|
5
6
|
import type { CollectionSummary } from "../../wix/portfolio/types";
|
|
6
7
|
|
|
7
8
|
export interface LinkLikeProps {
|
|
@@ -32,9 +33,8 @@ export function CollectionCard({
|
|
|
32
33
|
<div className="relative aspect-[4/3] overflow-hidden rounded-lg bg-secondary">
|
|
33
34
|
{collection.imageUrl && (
|
|
34
35
|
<img
|
|
35
|
-
|
|
36
|
+
{...imgAttrs(collection.imageUrl, "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw", 0.75)}
|
|
36
37
|
alt={collection.title}
|
|
37
|
-
loading="lazy"
|
|
38
38
|
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
|
|
39
39
|
/>
|
|
40
40
|
)}
|
package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SHIPPED — wire as-is. One gallery item as its one element: a <video> with poster for a video
|
|
2
|
+
// item, an <img> for an image item, null when nothing is renderable (never a broken tag, never
|
|
3
|
+
// an <img> for a video). The branching rule is `mediaElement` in wix/portfolio/portfolio-core.ts,
|
|
4
|
+
// shared with every other stack; this is its React spelling. Wrapped in a link when the owner
|
|
5
|
+
// attached one. `className` styles the media element itself.
|
|
6
|
+
import type { ReactNode } from "react";
|
|
7
|
+
import { mediaElement } from "../../wix/portfolio/portfolio-core";
|
|
8
|
+
import type { GalleryItem } from "../../wix/portfolio/types";
|
|
9
|
+
|
|
10
|
+
export interface GalleryMediaProps {
|
|
11
|
+
item: GalleryItem;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function GalleryMedia({ item, className }: GalleryMediaProps) {
|
|
16
|
+
const el = mediaElement(item);
|
|
17
|
+
if (!el) return null;
|
|
18
|
+
if (el.tag === "video") {
|
|
19
|
+
return <video src={el.attrs.src} poster={el.attrs.poster} controls playsInline className={className} />;
|
|
20
|
+
}
|
|
21
|
+
return <img src={el.attrs.src} alt={el.attrs.alt} loading="lazy" decoding="async" className={className} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The item's outbound link when the owner set one, else the children as they are. */
|
|
25
|
+
export function GalleryLink({ item, children }: { item: GalleryItem; children: ReactNode }) {
|
|
26
|
+
return item.linkUrl ? (
|
|
27
|
+
<a href={item.linkUrl} target={item.linkTarget ?? undefined} rel="noopener">
|
|
28
|
+
{children}
|
|
29
|
+
</a>
|
|
30
|
+
) : (
|
|
31
|
+
<>{children}</>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default GalleryMedia;
|
package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx
CHANGED
|
@@ -1,41 +1,11 @@
|
|
|
1
1
|
// REFERENCE project detail: header, details[] rows (text or link), media gallery (image AND
|
|
2
2
|
// video items) on the @theme tokens. Correct and complete; per the skill's model you design
|
|
3
|
-
// and build your own on useProjectDetail.
|
|
4
|
-
//
|
|
5
|
-
import type { ReactNode } from "react";
|
|
3
|
+
// and build your own on useProjectDetail. The gallery items render through the SHIPPED
|
|
4
|
+
// GalleryMedia (kind branching, poster, link wrap) — keep that in whatever you build.
|
|
6
5
|
import { useProjectDetail } from "../../hooks/portfolio/useProjectDetail";
|
|
6
|
+
import { imgAttrs } from "../../wix/media";
|
|
7
7
|
import type { GalleryItem, ProjectDetail } from "../../wix/portfolio/types";
|
|
8
|
-
|
|
9
|
-
/** One gallery item: image, or video with poster. Nothing renderable → null (never a broken tag). */
|
|
10
|
-
export function GalleryMedia({ item }: { item: GalleryItem }) {
|
|
11
|
-
if (item.kind === "video" && item.videoUrl) {
|
|
12
|
-
return (
|
|
13
|
-
<video
|
|
14
|
-
src={item.videoUrl}
|
|
15
|
-
poster={item.imageUrl || undefined}
|
|
16
|
-
controls
|
|
17
|
-
playsInline
|
|
18
|
-
className="block w-full rounded-lg bg-secondary"
|
|
19
|
-
/>
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
if (item.imageUrl) {
|
|
23
|
-
return (
|
|
24
|
-
<img src={item.imageUrl} alt={item.title} loading="lazy" className="block w-full rounded-lg" />
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function MaybeLink({ item, children }: { item: GalleryItem; children: ReactNode }) {
|
|
31
|
-
return item.linkUrl ? (
|
|
32
|
-
<a href={item.linkUrl} target={item.linkTarget ?? undefined} rel="noopener">
|
|
33
|
-
{children}
|
|
34
|
-
</a>
|
|
35
|
-
) : (
|
|
36
|
-
<>{children}</>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
8
|
+
import { GalleryLink, GalleryMedia } from "./GalleryMedia";
|
|
39
9
|
|
|
40
10
|
export interface ProjectDetailViewProps {
|
|
41
11
|
slug: string;
|
|
@@ -92,7 +62,7 @@ export default function ProjectDetailView({ slug, initialProject, initialItems }
|
|
|
92
62
|
) : items.length === 0 ? (
|
|
93
63
|
// No gallery items — the cover is the project's only real media; text-only otherwise.
|
|
94
64
|
project.imageUrl ? (
|
|
95
|
-
<img
|
|
65
|
+
<img {...imgAttrs(project.imageUrl, "(min-width: 1024px) 64rem, 100vw", 0.75)} alt={project.title} className="block w-full rounded-lg" />
|
|
96
66
|
) : (
|
|
97
67
|
<p className="py-16 text-center text-muted-foreground">No media in this project yet.</p>
|
|
98
68
|
)
|
|
@@ -100,9 +70,9 @@ export default function ProjectDetailView({ slug, initialProject, initialItems }
|
|
|
100
70
|
<div className="flex flex-col gap-6">
|
|
101
71
|
{items.map((item) => (
|
|
102
72
|
<figure key={item.id} className="m-0">
|
|
103
|
-
<
|
|
104
|
-
<GalleryMedia item={item} />
|
|
105
|
-
</
|
|
73
|
+
<GalleryLink item={item}>
|
|
74
|
+
<GalleryMedia item={item} className="block w-full rounded-lg bg-secondary" />
|
|
75
|
+
</GalleryLink>
|
|
106
76
|
{item.title && (
|
|
107
77
|
<figcaption className="mt-1.5 text-sm text-muted-foreground">{item.title}</figcaption>
|
|
108
78
|
)}
|
package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts
CHANGED
|
@@ -1,59 +1,32 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
1
|
+
// React binding of the collection-projects store (wix/portfolio/collection-projects-store.ts) —
|
|
2
|
+
// slug → collection header + its projects lives there, framework-free; this hook subscribes to
|
|
3
|
+
// one instance per (slug, mount) and recreates it when the slug changes. SSR-friendly: pass both
|
|
4
|
+
// `initial*` and no client fetch happens. `notFound` is the real not-found signal (collection
|
|
5
|
+
// stays null while loading too) — route a 404 off it, never off a transient null.
|
|
6
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
7
|
+
import {
|
|
8
|
+
createCollectionProjectsStore,
|
|
9
|
+
type CollectionProjectsState,
|
|
10
|
+
type CollectionProjectsStore,
|
|
11
|
+
type CollectionProjectsStoreOptions,
|
|
12
|
+
} from "../../wix/portfolio/collection-projects-store";
|
|
7
13
|
|
|
8
|
-
export
|
|
9
|
-
initialCollection?: CollectionSummary;
|
|
10
|
-
initialProjects?: ProjectSummary[];
|
|
11
|
-
}
|
|
14
|
+
export type UseCollectionProjectsOptions = CollectionProjectsStoreOptions;
|
|
12
15
|
|
|
13
|
-
export
|
|
14
|
-
/** null while loading AND when not found — check `notFound` to tell them apart. */
|
|
15
|
-
collection: CollectionSummary | null;
|
|
16
|
-
notFound: boolean;
|
|
17
|
-
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
18
|
-
projects: ProjectSummary[] | null;
|
|
19
|
-
error: string | null;
|
|
20
|
-
}
|
|
16
|
+
export type UseCollectionProjects = CollectionProjectsState;
|
|
21
17
|
|
|
22
18
|
export function useCollectionProjects(
|
|
23
19
|
slug: string,
|
|
24
20
|
{ initialCollection, initialProjects }: UseCollectionProjectsOptions = {},
|
|
25
21
|
): UseCollectionProjects {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const ref = useRef<{ slug: string; store: CollectionProjectsStore } | null>(null);
|
|
23
|
+
if (!ref.current || ref.current.slug !== slug) {
|
|
24
|
+
ref.current = { slug, store: createCollectionProjectsStore(slug, { initialCollection, initialProjects }) };
|
|
25
|
+
}
|
|
26
|
+
const store = ref.current.store;
|
|
31
27
|
useEffect(() => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const col = initialCollection ?? (await fetchCollectionBySlug(slug));
|
|
37
|
-
if (!alive) return;
|
|
38
|
-
if (!col) {
|
|
39
|
-
setNotFound(true);
|
|
40
|
-
setProjects([]);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
setCollection(col);
|
|
44
|
-
const projs = await fetchProjects({ collectionId: col.id });
|
|
45
|
-
if (alive) setProjects(projs);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
if (!alive) return;
|
|
48
|
-
setProjects([]);
|
|
49
|
-
setError(e instanceof Error ? e.message : String(e));
|
|
50
|
-
}
|
|
51
|
-
})();
|
|
52
|
-
return () => {
|
|
53
|
-
alive = false;
|
|
54
|
-
};
|
|
55
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
-
}, [slug]);
|
|
57
|
-
|
|
58
|
-
return { collection, notFound, projects, error };
|
|
28
|
+
store.start();
|
|
29
|
+
return () => store.stop();
|
|
30
|
+
}, [store]);
|
|
31
|
+
return useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
59
32
|
}
|