@jay-framework/wix-stores-v1 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,150 @@
1
+ # @jay-framework/wix-stores-v1
2
+
3
+ Wix Stores **Catalog V1** API client and headless components for the Jay Framework.
4
+
5
+ ## Overview
6
+
7
+ This package provides integration with the Wix Stores Catalog V1 API. If your Wix store uses the newer Catalog V3 API, use `@jay-framework/wix-stores` instead.
8
+
9
+ ### V1 vs V3
10
+
11
+ | Feature | V1 (this package) | V3 (@jay-framework/wix-stores) |
12
+ | ------------ | ----------------- | ------------------------------ |
13
+ | Products API | `products` | `productsV3` |
14
+ | Collections | `collections` | `@wix/categories` |
15
+ | Pagination | Skip-based | Cursor-based |
16
+ | Price format | Numbers (289) | Strings ("289") |
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @jay-framework/wix-stores-v1
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Configure Wix
27
+
28
+ Set up your Wix configuration in `config/.wix.yaml`:
29
+
30
+ ```yaml
31
+ apiKeyStrategy:
32
+ apiKey: 'your-api-key'
33
+ siteId: 'your-site-id'
34
+
35
+ oauthStrategy:
36
+ clientId: 'your-oauth-client-id'
37
+ ```
38
+
39
+ ### 2. Use Server Actions
40
+
41
+ ```typescript
42
+ import { searchProducts, getProductBySlug, getCollections } from '@jay-framework/wix-stores-v1';
43
+
44
+ // Search products
45
+ const results = await searchProducts({
46
+ query: 'whisky',
47
+ filters: { minPrice: 50, maxPrice: 200 },
48
+ sortBy: 'price_asc',
49
+ pageSize: 12,
50
+ page: 1,
51
+ });
52
+ // results.priceAggregation contains { minBound, maxBound, ranges }
53
+
54
+ // Get a product by slug
55
+ const product = await getProductBySlug({ slug: 'peat-s-beast-px-finish-54-1' });
56
+
57
+ // Get collections (V1 equivalent of categories)
58
+ const collections = await getCollections();
59
+ ```
60
+
61
+ ### 3. Use Client Context
62
+
63
+ ```typescript
64
+ import { useContext } from '@jay-framework/runtime';
65
+ import { WIX_STORES_V1_CONTEXT } from '@jay-framework/wix-stores-v1';
66
+
67
+ function MyComponent(props, refs) {
68
+ const stores = useContext(WIX_STORES_V1_CONTEXT);
69
+
70
+ // Reactive cart indicator
71
+ const itemCount = stores.cartIndicator.itemCount();
72
+
73
+ // Add to cart
74
+ refs.addToCart.onclick(async () => {
75
+ await stores.addToCart('product-id', 1);
76
+ });
77
+ }
78
+ ```
79
+
80
+ ## API
81
+
82
+ ### Server Actions
83
+
84
+ | Action | Description |
85
+ | ------------------------- | --------------------------------------- |
86
+ | `searchProducts(input)` | Search/filter products with pagination |
87
+ | `getProductBySlug(input)` | Get a single product by URL slug |
88
+ | `getCollections()` | Get available collections for filtering |
89
+
90
+ ### Client Context Methods
91
+
92
+ | Method | Description |
93
+ | --------------------------------------- | ------------------------------- |
94
+ | `cartIndicator.itemCount()` | Reactive cart item count |
95
+ | `cartIndicator.hasItems()` | Reactive boolean for cart state |
96
+ | `addToCart(productId, qty, variantId?)` | Add product to cart |
97
+ | `removeLineItems(ids)` | Remove items from cart |
98
+ | `updateLineItemQuantity(id, qty)` | Update item quantity |
99
+ | `clearCart()` | Remove all items |
100
+ | `applyCoupon(code)` | Apply coupon code |
101
+ | `removeCoupon()` | Remove applied coupon |
102
+ | `getCollections()` | Get all collections |
103
+
104
+ ## Differences from V3
105
+
106
+ ### Price Handling
107
+
108
+ V1 prices are numbers, V3 are strings. This package handles the conversion:
109
+
110
+ ```typescript
111
+ // V1 API response
112
+ { price: { price: 289, formatted: { price: "€289.00" } } }
113
+
114
+ // Mapped to ViewState (same format as V3)
115
+ { actualPriceRange: { minValue: { amount: "289", formattedAmount: "€289.00" } } }
116
+ ```
117
+
118
+ ### Collections vs Categories
119
+
120
+ V1 uses "collections" (`@wix/stores/collections`), V3 uses "categories" (`@wix/categories`):
121
+
122
+ ```typescript
123
+ // V1 - this package
124
+ const collections = await getCollections();
125
+
126
+ // V3 - @jay-framework/wix-stores
127
+ const categories = await getCategories();
128
+ ```
129
+
130
+ ### Pagination
131
+
132
+ V1 uses skip-based pagination:
133
+
134
+ ```typescript
135
+ // V1
136
+ const results = await searchProducts({ page: 2, pageSize: 12 });
137
+
138
+ // V3 uses cursor-based
139
+ const results = await searchProducts({ cursor: 'abc123', pageSize: 12 });
140
+ ```
141
+
142
+ ## Reference
143
+
144
+ - [Wix Stores Products V1 API](https://dev.wix.com/docs/sdk/backend-modules/stores/products/introduction)
145
+ - [Wix Stores Collections API](https://dev.wix.com/docs/sdk/backend-modules/stores/collections/introduction)
146
+ - Design Log: `wix/design-log/06 - wix-stores-v1 package.md`
147
+
148
+ ## License
149
+
150
+ Apache-2.0
@@ -0,0 +1,12 @@
1
+ name: getCollections
2
+ description: Get available store collections for filtering products. V1 uses collections instead of categories. Returns collection details including name, slug, description, image, and product count.
3
+
4
+ inputSchema: {}
5
+
6
+ outputSchema:
7
+ - _id: string
8
+ name: string
9
+ slug: string
10
+ description: string
11
+ imageUrl: string
12
+ productCount: number
@@ -0,0 +1,10 @@
1
+ name: getProductBySlug
2
+ description: Get a single product by its URL slug using Catalog V1. 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,32 @@
1
+ name: searchProducts
2
+ description: Search products using Wix Stores Catalog V1 with page-based pagination. Supports text search, price and collection filtering, sorting, and returns price aggregation data.
3
+
4
+ import:
5
+ productCard: product-card.jay-contract
6
+
7
+ inputSchema:
8
+ query: string
9
+ filters?:
10
+ minPrice?: number
11
+ maxPrice?: number
12
+ collectionIds?: string[]
13
+ sortBy?: enum(relevance | price_asc | price_desc | name_asc | name_desc | newest)
14
+ page?: number
15
+ pageSize?: number
16
+
17
+ outputSchema:
18
+ products:
19
+ - productCard
20
+ totalCount: number
21
+ currentPage: number
22
+ totalPages: number
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
+ isSelected: boolean
@@ -0,0 +1,55 @@
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: hasImage
42
+ type: variant
43
+ dataType: boolean
44
+ description: Whether the category has an image
45
+
46
+ - tag: categoryLink
47
+ type: interactive
48
+ elementType: HTMLAnchorElement
49
+ description: Link to category page
50
+
51
+ # Empty state
52
+ - tag: hasCategories
53
+ type: variant
54
+ dataType: boolean
55
+ description: Whether there are any categories
@@ -0,0 +1,41 @@
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
+ hasImage: boolean
12
+ }
13
+
14
+ export interface CategoryListViewState {
15
+ categories: Array<CategoryOfCategoryListViewState>,
16
+ hasCategories: boolean
17
+ }
18
+
19
+ export type CategoryListSlowViewState = Pick<CategoryListViewState, 'hasCategories'> & {
20
+ categories: Array<CategoryListViewState['categories'][number]>;
21
+ };
22
+
23
+ export type CategoryListFastViewState = {};
24
+
25
+ export type CategoryListInteractiveViewState = {};
26
+
27
+
28
+ export interface CategoryListRefs {
29
+ categories: {
30
+ categoryLink: HTMLElementCollectionProxy<CategoryOfCategoryListViewState, HTMLAnchorElement>
31
+ }
32
+ }
33
+
34
+
35
+ export interface CategoryListRepeatedRefs {
36
+ categories: {
37
+ categoryLink: HTMLElementCollectionProxy<CategoryOfCategoryListViewState, HTMLAnchorElement>
38
+ }
39
+ }
40
+
41
+ export type CategoryListContract = JayContract<CategoryListViewState, CategoryListRefs, CategoryListSlowViewState, CategoryListFastViewState, CategoryListInteractiveViewState>
@@ -0,0 +1,199 @@
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
+ # Whether the collection has media
36
+ - tag: hasImage
37
+ type: variant
38
+ dataType: boolean
39
+ description: Whether the collection has a main image
40
+
41
+ # Media (from Wix Stores Collection API)
42
+ - tag: media
43
+ type: sub-contract
44
+ description: Media items associated with this collection
45
+ tags:
46
+ - tag: mainMedia
47
+ type: sub-contract
48
+ description: Primary media for the collection
49
+ tags:
50
+ - tag: _id
51
+ type: data
52
+ dataType: string
53
+ description: Media GUID
54
+
55
+ - tag: url
56
+ type: data
57
+ dataType: string
58
+ description: Media URL
59
+
60
+ - tag: altText
61
+ type: data
62
+ dataType: string
63
+ description: Media alt text
64
+
65
+ - tag: mediaType
66
+ type: data
67
+ dataType: enum (IMAGE | VIDEO | AUDIO | DOCUMENT | ZIP)
68
+ description: Media item type
69
+
70
+ - tag: items
71
+ type: sub-contract
72
+ repeated: true
73
+ trackBy: _id
74
+ description: All media items
75
+ tags:
76
+ - tag: _id
77
+ type: data
78
+ dataType: string
79
+ description: Media GUID
80
+
81
+ - tag: url
82
+ type: data
83
+ dataType: string
84
+ description: Media URL
85
+
86
+ - tag: altText
87
+ type: data
88
+ dataType: string
89
+ description: Media alt text
90
+
91
+ - tag: title
92
+ type: data
93
+ dataType: string
94
+ description: Media item title
95
+
96
+ - tag: mediaType
97
+ type: data
98
+ dataType: enum (IMAGE | VIDEO | AUDIO | DOCUMENT | ZIP)
99
+ description: Media item type
100
+
101
+ - tag: thumbnail
102
+ type: sub-contract
103
+ description: Media thumbnail
104
+ tags:
105
+ - tag: url
106
+ type: data
107
+ dataType: string
108
+ description: Thumbnail URL
109
+
110
+ - tag: width
111
+ type: data
112
+ dataType: number
113
+ description: Thumbnail width in pixels
114
+
115
+ - tag: height
116
+ type: data
117
+ dataType: number
118
+ description: Thumbnail height in pixels
119
+
120
+ - tag: format
121
+ type: data
122
+ dataType: string
123
+ description: Media format (mp4, png, etc.)
124
+
125
+ # Breadcrumbs navigation
126
+ - tag: breadcrumbs
127
+ type: sub-contract
128
+ repeated: true
129
+ trackBy: categoryId
130
+ description: Breadcrumb navigation path
131
+ tags:
132
+ - tag: categoryId
133
+ type: data
134
+ dataType: string
135
+ description: Category GUID
136
+
137
+ - tag: categoryName
138
+ type: data
139
+ dataType: string
140
+ description: Category name in breadcrumb
141
+
142
+ - tag: categorySlug
143
+ type: data
144
+ dataType: string
145
+ description: Category slug for link
146
+
147
+ - tag: categoryLink
148
+ type: interactive
149
+ elementType: HTMLAnchorElement
150
+ description: Link to category
151
+
152
+ # Initial products (SSR - loaded in slow phase)
153
+ - tag: products
154
+ type: sub-contract
155
+ repeated: true
156
+ trackBy: _id
157
+ description: Initial products in this category (rendered server-side)
158
+ link: ./product-card
159
+
160
+ # Additional products (loaded on client via "load more")
161
+ - tag: loadedProducts
162
+ type: sub-contract
163
+ repeated: true
164
+ trackBy: _id
165
+ phase: fast+interactive
166
+ description: Additional products loaded on the client
167
+ link: ./product-card
168
+
169
+ # Load more functionality
170
+ - tag: hasMore
171
+ type: variant
172
+ dataType: boolean
173
+ phase: fast+interactive
174
+ description: Whether there are more products to load
175
+
176
+ - tag: loadMoreButton
177
+ type: interactive
178
+ elementType: HTMLButtonElement
179
+ description: Button to load more products
180
+
181
+ - tag: loadedCount
182
+ type: data
183
+ dataType: number
184
+ phase: fast+interactive
185
+ description: Number of products currently loaded
186
+
187
+ # Loading state
188
+ - tag: isLoading
189
+ type: variant
190
+ dataType: boolean
191
+ phase: fast+interactive
192
+ description: Whether products are currently loading
193
+
194
+ # Empty state
195
+ - tag: hasProducts
196
+ type: variant
197
+ dataType: boolean
198
+ phase: fast+interactive
199
+ description: Whether category has any products
@@ -0,0 +1,125 @@
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
+ hasImage: boolean,
62
+ media: MediaOfCategoryPageViewState,
63
+ breadcrumbs: Array<BreadcrumbOfCategoryPageViewState>,
64
+ products: Array<ProductCardViewState>,
65
+ loadedProducts: Array<ProductCardViewState>,
66
+ hasMore: boolean,
67
+ loadedCount: number,
68
+ isLoading: boolean,
69
+ hasProducts: boolean
70
+ }
71
+
72
+ export type CategoryPageSlowViewState = Pick<CategoryPageViewState, '_id' | 'name' | 'description' | 'slug' | 'visible' | 'numberOfProducts' | 'hasImage'> & {
73
+ media: CategoryPageViewState['media'];
74
+ breadcrumbs: Array<CategoryPageViewState['breadcrumbs'][number]>;
75
+ products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'name' | 'slug' | 'productUrl' | 'hasDiscount' | 'hasRibbon' | 'productType' | 'quickAddType'> & {
76
+ mainMedia: CategoryPageViewState['products'][number]['mainMedia'];
77
+ thumbnail: CategoryPageViewState['products'][number]['thumbnail'];
78
+ inventory: CategoryPageViewState['products'][number]['inventory'];
79
+ ribbon: CategoryPageViewState['products'][number]['ribbon'];
80
+ brand: CategoryPageViewState['products'][number]['brand'];
81
+ quickOption: Pick<CategoryPageViewState['products'][number]['quickOption'], '_id' | 'name' | 'optionRenderType'> & {
82
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode' | 'variantId'>>;
83
+ };
84
+ }>;
85
+ };
86
+
87
+ export type CategoryPageFastViewState = Pick<CategoryPageViewState, 'hasMore' | 'loadedCount' | 'isLoading' | 'hasProducts'> & {
88
+ products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
89
+ quickOption: {
90
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
91
+ };
92
+ }>;
93
+ loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
94
+ };
95
+
96
+ export type CategoryPageInteractiveViewState = Pick<CategoryPageViewState, 'hasMore' | 'loadedCount' | 'isLoading' | 'hasProducts'> & {
97
+ products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
98
+ quickOption: {
99
+ choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
100
+ };
101
+ }>;
102
+ loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
103
+ };
104
+
105
+
106
+ export interface CategoryPageRefs {
107
+ loadMoreButton: HTMLElementProxy<CategoryPageViewState, HTMLButtonElement>,
108
+ breadcrumbs: {
109
+ categoryLink: HTMLElementCollectionProxy<BreadcrumbOfCategoryPageViewState, HTMLAnchorElement>
110
+ },
111
+ products: ProductCardRepeatedRefs,
112
+ loadedProducts: ProductCardRepeatedRefs
113
+ }
114
+
115
+
116
+ export interface CategoryPageRepeatedRefs {
117
+ loadMoreButton: HTMLElementCollectionProxy<CategoryPageViewState, HTMLButtonElement>,
118
+ breadcrumbs: {
119
+ categoryLink: HTMLElementCollectionProxy<BreadcrumbOfCategoryPageViewState, HTMLAnchorElement>
120
+ },
121
+ products: ProductCardRepeatedRefs,
122
+ loadedProducts: ProductCardRepeatedRefs
123
+ }
124
+
125
+ 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