@jay-framework/wix-stores 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # @jay-framework/wix-stores
2
+
3
+ Wix Stores integration for Jay Framework using the Catalog V3 API. Provides headless full-stack components for product pages, search/listing, and category navigation with server-side rendering.
4
+
5
+ ## Features
6
+
7
+ - **Catalog V3 API** - Uses `productsV3` and `@wix/categories` for product and category data
8
+ - **Three-phase rendering** - Slow (build/SSG), Fast (request/SSR), Interactive (client)
9
+ - **Category-prefixed routes** - Optional URL prefixes per product line (e.g., `/products/polgat/shirt-name`)
10
+ - **Unified search + category** - Single `product-search` component handles both search pages and category listings
11
+ - **Shared cart** - Delegates cart operations to `@jay-framework/wix-cart`
12
+
13
+ ## Headless Components
14
+
15
+ ### Product Page (`product-page`)
16
+
17
+ Complete product detail page with variant selection and add-to-cart.
18
+
19
+ - **Slow phase**: Product details, media gallery, options, SEO data
20
+ - **Fast phase**: Inventory status, pricing, variant availability
21
+ - **Interactive**: Option/modifier selection, quantity, add to cart
22
+
23
+ Route: `/products/[slug]` or `/products/[category]/[slug]` (when category prefixes are configured)
24
+
25
+ ### Product Search (`product-search`)
26
+
27
+ Unified search and category listing component. Replaces the previous separate `category-page` component.
28
+
29
+ - **Slow phase**: Available categories for filtering
30
+ - **Fast phase**: Initial product results with price aggregations
31
+ - **Interactive**: Search input, category/price/stock filters, sorting, load more
32
+
33
+ When a `category` URL parameter is provided (e.g., from a route like `/products/polgat/`):
34
+
35
+ - Scopes all searches to products within that category hierarchy
36
+ - Shows only child categories of the root as filter options (root category is hidden)
37
+ - Enables per-category template designs via separate jay-html files
38
+
39
+ ### Category List (`category-list`)
40
+
41
+ Lists all store categories for navigation.
42
+
43
+ ## Configuration
44
+
45
+ ### Basic Setup
46
+
47
+ The plugin requires `@jay-framework/wix-server-client` to be configured with Wix API credentials.
48
+
49
+ Run setup to create the config template:
50
+
51
+ ```bash
52
+ jay-stack setup wix-stores
53
+ ```
54
+
55
+ ### Category Prefixes (Optional)
56
+
57
+ To enable category-prefixed product routes, create `config/.wix-stores.yaml`:
58
+
59
+ ```yaml
60
+ categoryPrefixes:
61
+ - categoryId: '024a9fff-77de-4508-b82c-5fce24f74757'
62
+ prefix: 'polgat'
63
+ name: 'פולגת'
64
+ - categoryId: 'eac4db24-04cc-4f36-86cf-c9da6e873421'
65
+ prefix: 'kitan'
66
+ name: 'כיתן'
67
+ ```
68
+
69
+ Each entry maps a root Wix category to a URL prefix. Products belonging to any child of the root category get URLs like `/products/{prefix}/{product-slug}`.
70
+
71
+ To find category IDs, run setup to generate the category tree reference:
72
+
73
+ ```bash
74
+ jay-stack setup wix-stores
75
+ ```
76
+
77
+ This creates `agent-kit/references/wix-stores/categories.yaml` with all category IDs, names, product counts, and parent-child relationships as a tree.
78
+
79
+ ### Route Structure with Category Prefixes
80
+
81
+ ```
82
+ src/pages/products/
83
+ ├── page.jay-html # /products (default search, optional)
84
+ ├── [slug]/page.jay-html # /products/:slug (default product page, optional)
85
+ ├── polgat/
86
+ │ ├── page.jay-html # /products/polgat (polgat-design search)
87
+ │ └── [slug]/page.jay-html # /products/polgat/:slug (polgat product page)
88
+ └── kitan/
89
+ ├── page.jay-html # /products/kitan (kitan-design search)
90
+ └── [slug]/page.jay-html # /products/kitan/:slug (kitan product page)
91
+ ```
92
+
93
+ Each prefix directory gets its own jay-html templates, enabling different visual designs per product line. Jay's routing precedence ensures static segments (`polgat`, `kitan`) take priority over the dynamic `[slug]` parameter.
94
+
95
+ Products without a matching category prefix fall back to `/products/[slug]` if that route exists, or return 404 if it doesn't.
96
+
97
+ ## Server Actions
98
+
99
+ ### `searchProducts`
100
+
101
+ Search products with filtering, sorting, and price aggregations.
102
+
103
+ ```typescript
104
+ const results = await searchProducts({
105
+ query: 'shoes',
106
+ filters: { inStockOnly: true, categoryIds: ['cat-123'] },
107
+ sortBy: 'price_asc',
108
+ pageSize: 12,
109
+ });
110
+ ```
111
+
112
+ CLI:
113
+
114
+ ```bash
115
+ jay-stack action wix-stores/searchProducts
116
+ jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "pageSize": 5}'
117
+ ```
118
+
119
+ ### `getProductBySlug`
120
+
121
+ Fetch a single product by its URL slug.
122
+
123
+ ```typescript
124
+ const product = await getProductBySlug({ slug: 'blue-sneakers' });
125
+ ```
126
+
127
+ CLI:
128
+
129
+ ```bash
130
+ jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-sneakers"}'
131
+ ```
132
+
133
+ ### `getCategories`
134
+
135
+ Get all visible categories for filtering.
136
+
137
+ ```typescript
138
+ const categories = await getCategories();
139
+ ```
140
+
141
+ CLI:
142
+
143
+ ```bash
144
+ jay-stack action wix-stores/getCategories
145
+ ```
146
+
147
+ ## Agent Kit References
148
+
149
+ Running `jay-stack setup wix-stores` or `jay-stack agent-kit` generates a category tree reference at `agent-kit/references/wix-stores/categories.yaml`. This file contains:
150
+
151
+ - All visible categories with IDs, names, slugs, and product counts
152
+ - Full parent-child hierarchy as a nested tree
153
+ - Configured category prefixes (if any) with their mapped category names
154
+
155
+ ## Architecture
156
+
157
+ ### Plugin Initialization
158
+
159
+ ```
160
+ wix-server-client (init first)
161
+ └── Registers WIX_CLIENT_SERVICE (API key auth)
162
+
163
+ wix-stores (init second)
164
+ ├── Loads config/.wix-stores.yaml (category prefixes)
165
+ ├── Registers WIX_STORES_SERVICE_MARKER (products, categories, inventory)
166
+ └── Client: Registers WIX_STORES_CONTEXT (delegates cart to wix-cart)
167
+ ```
168
+
169
+ ### Category Prefix Resolution
170
+
171
+ When category prefixes are configured:
172
+
173
+ 1. **`loadProductParams`** fetches all products with `ALL_CATEGORIES_INFO` and resolves each product's prefix
174
+ 2. **`searchProducts`** includes `ALL_CATEGORIES_INFO` to generate correct prefixed URLs in results
175
+ 3. **`mapProductToCard`** generates URLs like `/products/polgat/shirt-name` based on the product's category ancestry
176
+ 4. **Product page** validates that the URL's category prefix matches the product's actual category
177
+
178
+ ## Design Log
179
+
180
+ See [Design Log 10 - Category-Prefixed Product Routes](../../design-log/10%20-%20category-prefixed-product-routes.md) for the full design rationale.
181
+
182
+ ## License
183
+
184
+ Apache-2.0
@@ -0,0 +1,8 @@
1
+ name: getCategories
2
+ description: Get available store categories for filtering products. Returns category IDs and names.
3
+
4
+ inputSchema: {}
5
+
6
+ outputSchema:
7
+ - categoryId: string
8
+ categoryName: string
@@ -0,0 +1,10 @@
1
+ name: getProductBySlug
2
+ description: Get a single product by its URL slug. Returns full ProductCardViewState or null if not found.
3
+
4
+ import:
5
+ productCard: product-card.jay-contract
6
+
7
+ inputSchema:
8
+ slug: string
9
+
10
+ outputSchema: productCard?
@@ -0,0 +1,33 @@
1
+ name: searchProducts
2
+ description: Search products in the Wix Store catalog using Catalog V3. Supports text search, price/stock/category filtering, sorting, and cursor-based pagination. Returns matching products with price aggregation data.
3
+
4
+ import:
5
+ productCard: product-card.jay-contract
6
+
7
+ inputSchema:
8
+ query: string
9
+ filters?:
10
+ inStockOnly?: boolean
11
+ minPrice?: number
12
+ maxPrice?: number
13
+ categoryIds?: string[]
14
+ sortBy?: enum(relevance | price_asc | price_desc | name_asc | name_desc | newest)
15
+ cursor?: string
16
+ pageSize?: number
17
+
18
+ outputSchema:
19
+ products:
20
+ - productCard
21
+ totalCount: number
22
+ nextCursor?: string
23
+ hasMore: boolean
24
+ priceAggregation?:
25
+ minBound: number
26
+ maxBound: number
27
+ ranges:
28
+ - rangeId: string
29
+ label: string
30
+ minValue?: number
31
+ maxValue?: number
32
+ productCount: number
33
+ isSelected: boolean
@@ -0,0 +1,50 @@
1
+ name: category-list
2
+ tags:
3
+ # List of visible categories
4
+ - tag: categories
5
+ type: sub-contract
6
+ repeated: true
7
+ trackBy: _id
8
+ description: List of visible store categories
9
+ tags:
10
+ - tag: _id
11
+ type: data
12
+ dataType: string
13
+ description: Category GUID
14
+
15
+ - tag: name
16
+ type: data
17
+ dataType: string
18
+ required: true
19
+ description: Category name
20
+
21
+ - tag: slug
22
+ type: data
23
+ dataType: string
24
+ description: Category slug for URL
25
+
26
+ - tag: description
27
+ type: data
28
+ dataType: string
29
+ description: Category description
30
+
31
+ - tag: productCount
32
+ type: data
33
+ dataType: number
34
+ description: Number of products in category
35
+
36
+ - tag: imageUrl
37
+ type: data
38
+ dataType: string
39
+ description: Category image URL
40
+
41
+ - tag: categoryLink
42
+ type: interactive
43
+ elementType: HTMLAnchorElement
44
+ description: Link to category page
45
+
46
+ # Empty state
47
+ - tag: hasCategories
48
+ type: variant
49
+ dataType: boolean
50
+ description: Whether there are any categories
@@ -0,0 +1,40 @@
1
+ import {HTMLElementCollectionProxy, JayContract} from "@jay-framework/runtime";
2
+
3
+
4
+ export interface CategoryOfCategoryListViewState {
5
+ _id: string,
6
+ name: string,
7
+ slug: string,
8
+ description: string,
9
+ productCount: number,
10
+ imageUrl: string
11
+ }
12
+
13
+ export interface CategoryListViewState {
14
+ categories: Array<CategoryOfCategoryListViewState>,
15
+ hasCategories: boolean
16
+ }
17
+
18
+ export type CategoryListSlowViewState = Pick<CategoryListViewState, 'hasCategories'> & {
19
+ categories: Array<CategoryListViewState['categories'][number]>;
20
+ };
21
+
22
+ export type CategoryListFastViewState = {};
23
+
24
+ export type CategoryListInteractiveViewState = {};
25
+
26
+
27
+ export interface CategoryListRefs {
28
+ categories: {
29
+ categoryLink: HTMLElementCollectionProxy<CategoryOfCategoryListViewState, HTMLAnchorElement>
30
+ }
31
+ }
32
+
33
+
34
+ export interface CategoryListRepeatedRefs {
35
+ categories: {
36
+ categoryLink: HTMLElementCollectionProxy<CategoryOfCategoryListViewState, HTMLAnchorElement>
37
+ }
38
+ }
39
+
40
+ export type CategoryListContract = JayContract<CategoryListViewState, CategoryListRefs, CategoryListSlowViewState, CategoryListFastViewState, CategoryListInteractiveViewState>
@@ -0,0 +1,193 @@
1
+ name: category-page
2
+ tags:
3
+ # Category/Collection information (from Wix Stores Collection API)
4
+ - tag: _id
5
+ type: data
6
+ dataType: string
7
+ description: Collection GUID
8
+
9
+ - tag: name
10
+ type: data
11
+ dataType: string
12
+ required: true
13
+ description: Collection name
14
+
15
+ - tag: description
16
+ type: data
17
+ dataType: string
18
+ description: Collection description
19
+
20
+ - tag: slug
21
+ type: data
22
+ dataType: string
23
+ description: Collection slug
24
+
25
+ - tag: visible
26
+ type: data
27
+ dataType: boolean
28
+ description: Collection visibility (impacts dynamic pages only)
29
+
30
+ - tag: numberOfProducts
31
+ type: data
32
+ dataType: number
33
+ description: Number of products in the collection
34
+
35
+ # Media (from Wix Stores Collection API)
36
+ - tag: media
37
+ type: sub-contract
38
+ description: Media items associated with this collection
39
+ tags:
40
+ - tag: mainMedia
41
+ type: sub-contract
42
+ description: Primary media for the collection
43
+ tags:
44
+ - tag: _id
45
+ type: data
46
+ dataType: string
47
+ description: Media GUID
48
+
49
+ - tag: url
50
+ type: data
51
+ dataType: string
52
+ description: Media URL
53
+
54
+ - tag: altText
55
+ type: data
56
+ dataType: string
57
+ description: Media alt text
58
+
59
+ - tag: mediaType
60
+ type: data
61
+ dataType: enum (IMAGE | VIDEO | AUDIO | DOCUMENT | ZIP)
62
+ description: Media item type
63
+
64
+ - tag: items
65
+ type: sub-contract
66
+ repeated: true
67
+ trackBy: _id
68
+ description: All media items
69
+ tags:
70
+ - tag: _id
71
+ type: data
72
+ dataType: string
73
+ description: Media GUID
74
+
75
+ - tag: url
76
+ type: data
77
+ dataType: string
78
+ description: Media URL
79
+
80
+ - tag: altText
81
+ type: data
82
+ dataType: string
83
+ description: Media alt text
84
+
85
+ - tag: title
86
+ type: data
87
+ dataType: string
88
+ description: Media item title
89
+
90
+ - tag: mediaType
91
+ type: data
92
+ dataType: enum (IMAGE | VIDEO | AUDIO | DOCUMENT | ZIP)
93
+ description: Media item type
94
+
95
+ - tag: thumbnail
96
+ type: sub-contract
97
+ description: Media thumbnail
98
+ tags:
99
+ - tag: url
100
+ type: data
101
+ dataType: string
102
+ description: Thumbnail URL
103
+
104
+ - tag: width
105
+ type: data
106
+ dataType: number
107
+ description: Thumbnail width in pixels
108
+
109
+ - tag: height
110
+ type: data
111
+ dataType: number
112
+ description: Thumbnail height in pixels
113
+
114
+ - tag: format
115
+ type: data
116
+ dataType: string
117
+ description: Media format (mp4, png, etc.)
118
+
119
+ # Breadcrumbs navigation
120
+ - tag: breadcrumbs
121
+ type: sub-contract
122
+ repeated: true
123
+ trackBy: categoryId
124
+ description: Breadcrumb navigation path
125
+ tags:
126
+ - tag: categoryId
127
+ type: data
128
+ dataType: string
129
+ description: Category GUID
130
+
131
+ - tag: categoryName
132
+ type: data
133
+ dataType: string
134
+ description: Category name in breadcrumb
135
+
136
+ - tag: categorySlug
137
+ type: data
138
+ dataType: string
139
+ description: Category slug for link
140
+
141
+ - tag: categoryLink
142
+ type: interactive
143
+ elementType: HTMLAnchorElement
144
+ description: Link to category
145
+
146
+ # Initial products (SSR - loaded in slow phase)
147
+ - tag: products
148
+ type: sub-contract
149
+ repeated: true
150
+ trackBy: _id
151
+ description: Initial products in this category (rendered server-side)
152
+ link: ./product-card
153
+
154
+ # Additional products (loaded on client via "load more")
155
+ - tag: loadedProducts
156
+ type: sub-contract
157
+ repeated: true
158
+ trackBy: _id
159
+ phase: fast+interactive
160
+ description: Additional products loaded on the client
161
+ link: ./product-card
162
+
163
+ # Load more functionality
164
+ - tag: hasMore
165
+ type: variant
166
+ dataType: boolean
167
+ phase: fast+interactive
168
+ description: Whether there are more products to load
169
+
170
+ - tag: loadMoreButton
171
+ type: interactive
172
+ elementType: HTMLButtonElement
173
+ description: Button to load more products
174
+
175
+ - tag: loadedCount
176
+ type: data
177
+ dataType: number
178
+ phase: fast+interactive
179
+ description: Number of products currently loaded
180
+
181
+ # Loading state
182
+ - tag: isLoading
183
+ type: variant
184
+ dataType: boolean
185
+ phase: fast+interactive
186
+ description: Whether products are currently loading
187
+
188
+ # Empty state
189
+ - tag: hasProducts
190
+ type: variant
191
+ dataType: boolean
192
+ phase: fast+interactive
193
+ description: Whether category has any products
@@ -0,0 +1,124 @@
1
+ import {HTMLElementCollectionProxy, HTMLElementProxy, JayContract} from "@jay-framework/runtime";
2
+ import {ProductCardViewState, ProductCardRefs, ProductCardRepeatedRefs} from "./product-card.jay-contract";
3
+
4
+ export enum MediaType {
5
+ IMAGE,
6
+ VIDEO,
7
+ AUDIO,
8
+ DOCUMENT,
9
+ ZIP
10
+ }
11
+
12
+ export interface MainMediaOfMediaOfCategoryPageViewState {
13
+ _id: string,
14
+ url: string,
15
+ altText: string,
16
+ mediaType: MediaType
17
+ }
18
+
19
+ export enum MediaType {
20
+ IMAGE,
21
+ VIDEO,
22
+ AUDIO,
23
+ DOCUMENT,
24
+ ZIP
25
+ }
26
+
27
+ export interface ThumbnailOfItemOfMediaOfCategoryPageViewState {
28
+ url: string,
29
+ width: number,
30
+ height: number,
31
+ format: string
32
+ }
33
+
34
+ export interface ItemOfMediaOfCategoryPageViewState {
35
+ _id: string,
36
+ url: string,
37
+ altText: string,
38
+ title: string,
39
+ mediaType: MediaType,
40
+ thumbnail: ThumbnailOfItemOfMediaOfCategoryPageViewState
41
+ }
42
+
43
+ export interface MediaOfCategoryPageViewState {
44
+ mainMedia: MainMediaOfMediaOfCategoryPageViewState,
45
+ items: Array<ItemOfMediaOfCategoryPageViewState>
46
+ }
47
+
48
+ export interface BreadcrumbOfCategoryPageViewState {
49
+ categoryId: string,
50
+ categoryName: string,
51
+ categorySlug: string
52
+ }
53
+
54
+ export interface CategoryPageViewState {
55
+ _id: string,
56
+ name: string,
57
+ description: string,
58
+ slug: string,
59
+ visible: boolean,
60
+ numberOfProducts: number,
61
+ media: MediaOfCategoryPageViewState,
62
+ breadcrumbs: Array<BreadcrumbOfCategoryPageViewState>,
63
+ products: Array<ProductCardViewState>,
64
+ loadedProducts: Array<ProductCardViewState>,
65
+ hasMore: boolean,
66
+ loadedCount: number,
67
+ isLoading: boolean,
68
+ hasProducts: boolean
69
+ }
70
+
71
+ export type CategoryPageSlowViewState = Pick<CategoryPageViewState, '_id' | 'name' | 'description' | 'slug' | 'visible' | 'numberOfProducts'> & {
72
+ media: CategoryPageViewState['media'];
73
+ breadcrumbs: Array<CategoryPageViewState['breadcrumbs'][number]>;
74
+ products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'name' | 'slug' | 'productUrl' | 'categoryPrefix' | 'hasDiscount' | 'hasRibbon' | 'productType' | 'quickAddType'> & {
75
+ mainMedia: CategoryPageViewState['products'][number]['mainMedia'];
76
+ thumbnail: CategoryPageViewState['products'][number]['thumbnail'];
77
+ inventory: CategoryPageViewState['products'][number]['inventory'];
78
+ ribbon: CategoryPageViewState['products'][number]['ribbon'];
79
+ brand: CategoryPageViewState['products'][number]['brand'];
80
+ quickOption: Pick<CategoryPageViewState['products'][number]['quickOption'], '_id' | 'name' | 'optionRenderType'> & {
81
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode' | 'variantId'>>;
82
+ };
83
+ }>;
84
+ };
85
+
86
+ export type CategoryPageFastViewState = Pick<CategoryPageViewState, 'hasMore' | 'loadedCount' | 'isLoading' | 'hasProducts'> & {
87
+ products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
88
+ quickOption: {
89
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
90
+ };
91
+ }>;
92
+ loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
93
+ };
94
+
95
+ export type CategoryPageInteractiveViewState = Pick<CategoryPageViewState, 'hasMore' | 'loadedCount' | 'isLoading' | 'hasProducts'> & {
96
+ products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
97
+ quickOption: {
98
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
99
+ };
100
+ }>;
101
+ loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
102
+ };
103
+
104
+
105
+ export interface CategoryPageRefs {
106
+ loadMoreButton: HTMLElementProxy<CategoryPageViewState, HTMLButtonElement>,
107
+ breadcrumbs: {
108
+ categoryLink: HTMLElementCollectionProxy<BreadcrumbOfCategoryPageViewState, HTMLAnchorElement>
109
+ },
110
+ products: ProductCardRepeatedRefs,
111
+ loadedProducts: ProductCardRepeatedRefs
112
+ }
113
+
114
+
115
+ export interface CategoryPageRepeatedRefs {
116
+ loadMoreButton: HTMLElementCollectionProxy<CategoryPageViewState, HTMLButtonElement>,
117
+ breadcrumbs: {
118
+ categoryLink: HTMLElementCollectionProxy<BreadcrumbOfCategoryPageViewState, HTMLAnchorElement>
119
+ },
120
+ products: ProductCardRepeatedRefs,
121
+ loadedProducts: ProductCardRepeatedRefs
122
+ }
123
+
124
+ export type CategoryPageContract = JayContract<CategoryPageViewState, CategoryPageRefs, CategoryPageSlowViewState, CategoryPageFastViewState, CategoryPageInteractiveViewState>
@@ -0,0 +1,22 @@
1
+ name: mediaGallery
2
+ tags:
3
+ - tag: selectedMedia
4
+ type: sub-contract
5
+ description: Selected Media (image, video) to display with selection based galleries
6
+ link: ./media
7
+
8
+ - tag: availableMedia
9
+ type: sub-contract
10
+ repeated: true
11
+ trackBy: mediaId
12
+ description: Available media for display or use as thumbnails
13
+ tags:
14
+ - tag: mediaId
15
+ type: data
16
+ - tag: media
17
+ type: sub-contract
18
+ link: ./media
19
+ - tag: selected
20
+ type: [variant, interactive]
21
+ dataType: enum(selected | notSelected)
22
+ elementType: HTMLImageElement | HTMLDivElement