@jay-framework/wix-stores 0.15.0 → 0.15.4
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 +157 -96
- package/dist/contracts/category-page.jay-contract.d.ts +10 -1
- package/dist/contracts/product-card.jay-contract +8 -2
- package/dist/contracts/product-card.jay-contract.d.ts +17 -4
- package/dist/contracts/product-options.jay-contract +0 -5
- package/dist/contracts/product-options.jay-contract.d.ts +1 -2
- package/dist/contracts/product-search.jay-contract +71 -0
- package/dist/contracts/product-search.jay-contract.d.ts +57 -3
- package/dist/index.client.js +180 -21
- package/dist/index.d.ts +142 -75
- package/dist/index.js +429 -137
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@ Wix Stores integration for Jay Framework using the Catalog V3 API. Provides head
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Catalog V3 API**
|
|
8
|
-
- **Three-phase rendering**
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
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
|
+
- **Flexible URL patterns** — Configurable URL templates with `{prefix}`, `{category}`, `{slug}` placeholders
|
|
10
|
+
- **Category header** — Automatic category metadata (name, description, image, breadcrumbs, SEO)
|
|
11
|
+
- **Filter URL persistence** — Filters saved to query params for sharable, bookmarkable URLs
|
|
12
|
+
- **Canonical redirects** — 301 redirects for non-canonical product/category URLs
|
|
13
|
+
- **Shared cart** — Delegates cart operations to `@jay-framework/wix-cart`
|
|
12
14
|
|
|
13
15
|
## Headless Components
|
|
14
16
|
|
|
@@ -20,21 +22,19 @@ Complete product detail page with variant selection and add-to-cart.
|
|
|
20
22
|
- **Fast phase**: Inventory status, pricing, variant availability
|
|
21
23
|
- **Interactive**: Option/modifier selection, quantity, add to cart
|
|
22
24
|
|
|
23
|
-
Route: `/products/[slug]` or `/products/[category]/[slug]` (when category prefixes are configured)
|
|
24
|
-
|
|
25
25
|
### Product Search (`product-search`)
|
|
26
26
|
|
|
27
|
-
Unified search
|
|
27
|
+
Unified search, category listing, and product browsing component.
|
|
28
28
|
|
|
29
|
-
- **Slow phase**:
|
|
30
|
-
- **Fast phase**: Initial product results with price aggregations
|
|
31
|
-
- **Interactive**: Search input, category/price/stock filters, sorting, load more
|
|
29
|
+
- **Slow phase**: Category header (name, description, image, breadcrumbs, SEO), available categories for filtering
|
|
30
|
+
- **Fast phase**: Initial product results with price aggregations, filters restored from URL query params
|
|
31
|
+
- **Interactive**: Search input, category/price/stock filters, sorting, load more, filter URL persistence
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Supports three roles depending on route params:
|
|
34
34
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
35
|
+
- **Search page** — no category context, shows all products
|
|
36
|
+
- **Top-level category page** — scoped to a root category, shows child categories as filters
|
|
37
|
+
- **Sub-category page** — scoped to a sub-category, shows header with breadcrumbs
|
|
38
38
|
|
|
39
39
|
### Category List (`category-list`)
|
|
40
40
|
|
|
@@ -42,142 +42,203 @@ Lists all store categories for navigation.
|
|
|
42
42
|
|
|
43
43
|
## Configuration
|
|
44
44
|
|
|
45
|
-
###
|
|
45
|
+
### Setup
|
|
46
46
|
|
|
47
47
|
The plugin requires `@jay-framework/wix-server-client` to be configured with Wix API credentials.
|
|
48
48
|
|
|
49
|
-
Run setup to create the config template:
|
|
50
|
-
|
|
51
49
|
```bash
|
|
52
50
|
jay-stack setup wix-stores
|
|
53
51
|
```
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
This creates `config/.wix-stores.yaml` and generates `agent-kit/references/wix-stores/categories.yaml` with the full category tree.
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
### Config File (`config/.wix-stores.yaml`)
|
|
58
56
|
|
|
59
57
|
```yaml
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
# URL templates — how the component builds canonical links
|
|
59
|
+
urls:
|
|
60
|
+
product: '/products/{slug}' # default
|
|
61
|
+
category: null # no category pages by default
|
|
62
|
+
|
|
63
|
+
# Fallback category for pages without category context
|
|
64
|
+
defaultCategory: 'all-products' # slug of the fallback category
|
|
67
65
|
```
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
URL templates use three placeholders:
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
| Placeholder | Source | Description |
|
|
70
|
+
| ------------ | ------------------------------------------ | ------------------------- |
|
|
71
|
+
| `{slug}` | Wix product slug | Product identifier in URL |
|
|
72
|
+
| `{category}` | Wix category slug (from `mainCategoryId`) | Sub-category in URL |
|
|
73
|
+
| `{prefix}` | Wix root category slug (from parent chain) | Top-level category in URL |
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
## URL Patterns
|
|
76
|
+
|
|
77
|
+
### Pattern 1: Simple Store (No Categories)
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
urls:
|
|
81
|
+
product: '/products/{slug}'
|
|
75
82
|
```
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
```
|
|
85
|
+
src/pages/products/
|
|
86
|
+
├── page.jay-html → /products (search page)
|
|
87
|
+
└── [slug]/page.jay-html → /products/:slug (product page)
|
|
88
|
+
```
|
|
78
89
|
|
|
79
|
-
###
|
|
90
|
+
### Pattern 2: Categories (No Top-Level Prefixes)
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
urls:
|
|
94
|
+
product: '/products/{category}/{slug}'
|
|
95
|
+
category: '/products/{category}'
|
|
96
|
+
```
|
|
80
97
|
|
|
81
98
|
```
|
|
82
99
|
src/pages/products/
|
|
83
|
-
├── page.jay-html
|
|
84
|
-
|
|
100
|
+
├── page.jay-html → /products (all products)
|
|
101
|
+
└── [category]/
|
|
102
|
+
├── page.jay-html → /products/:category (category listing)
|
|
103
|
+
└── [slug]/page.jay-html → /products/:category/:slug (product)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Pattern 3: Top-Level Prefixes + Sub-Categories
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
urls:
|
|
110
|
+
product: '/products/{prefix}/{category}/{slug}'
|
|
111
|
+
category: '/products/{prefix}/{category}'
|
|
112
|
+
defaultCategory: 'all-products'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
src/pages/products/
|
|
117
|
+
├── page.jay-html → /products (all products)
|
|
118
|
+
├── [prefix]/
|
|
119
|
+
│ ├── page.jay-html → /products/:prefix (generic prefix page)
|
|
120
|
+
│ └── [category]/
|
|
121
|
+
│ ├── page.jay-html → /products/:prefix/:category (sub-category)
|
|
122
|
+
│ └── [slug]/page.jay-html → /products/:prefix/:category/:slug (product)
|
|
85
123
|
├── polgat/
|
|
86
|
-
│ ├── page.jay-html
|
|
87
|
-
│ └── [
|
|
124
|
+
│ ├── page.jay-html → /products/polgat (custom design)
|
|
125
|
+
│ └── [category]/
|
|
126
|
+
│ ├── page.jay-html → /products/polgat/:category
|
|
127
|
+
│ └── [slug]/page.jay-html → /products/polgat/:category/:slug
|
|
88
128
|
└── kitan/
|
|
89
|
-
├── page.jay-html
|
|
90
|
-
└── [
|
|
129
|
+
├── page.jay-html → /products/kitan (custom design)
|
|
130
|
+
└── [category]/
|
|
131
|
+
├── page.jay-html → /products/kitan/:category
|
|
132
|
+
└── [slug]/page.jay-html → /products/kitan/:category/:slug
|
|
91
133
|
```
|
|
92
134
|
|
|
93
|
-
|
|
135
|
+
Static directories (`polgat/`, `kitan/`) override the dynamic `[prefix]/` route, enabling different template designs per category. Any other prefix falls through to `[prefix]/`.
|
|
94
136
|
|
|
95
|
-
|
|
137
|
+
### Static Overrides
|
|
96
138
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### `searchProducts`
|
|
139
|
+
Jay's routing supports static segments overriding dynamic params at any level:
|
|
100
140
|
|
|
101
|
-
|
|
141
|
+
```
|
|
142
|
+
src/pages/products/polgat/
|
|
143
|
+
├── [category]/page.jay-html → default sub-category design
|
|
144
|
+
├── sale/page.jay-html → custom design for "sale" sub-category
|
|
145
|
+
└── [category]/
|
|
146
|
+
├── [slug]/page.jay-html → default product page
|
|
147
|
+
└── premium-oxford/page.jay-html → custom page for one specific product
|
|
148
|
+
```
|
|
102
149
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
150
|
+
### Setting Params for Static Routes
|
|
151
|
+
|
|
152
|
+
Static route directories use `jay-params` to tell the component which category they represent:
|
|
153
|
+
|
|
154
|
+
```html
|
|
155
|
+
<!-- src/pages/products/polgat/page.jay-html -->
|
|
156
|
+
<script type="application/jay-params">
|
|
157
|
+
category: polgat
|
|
158
|
+
</script>
|
|
159
|
+
<script
|
|
160
|
+
type="application/jay-headless"
|
|
161
|
+
plugin="@jay-framework/wix-stores"
|
|
162
|
+
contract="product-search"
|
|
163
|
+
key="search"
|
|
164
|
+
></script>
|
|
110
165
|
```
|
|
111
166
|
|
|
112
|
-
|
|
167
|
+
## Category Header
|
|
113
168
|
|
|
114
|
-
|
|
115
|
-
jay-stack action wix-stores/searchProducts
|
|
116
|
-
jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "pageSize": 5}'
|
|
117
|
-
```
|
|
169
|
+
The category header is always loaded. The component resolves category metadata using a fallback chain:
|
|
118
170
|
|
|
119
|
-
|
|
171
|
+
1. **`subcategory` param** → load sub-category
|
|
172
|
+
2. **`category` / `prefix` param** → load root category
|
|
173
|
+
3. **Neither** → load `defaultCategory` from config
|
|
120
174
|
|
|
121
|
-
|
|
175
|
+
If the resolved category is missing image, description, or SEO data, the component walks up the parent chain until it finds the data. Each field inherits independently.
|
|
122
176
|
|
|
123
|
-
|
|
124
|
-
const product = await getProductBySlug({ slug: 'blue-sneakers' });
|
|
125
|
-
```
|
|
177
|
+
The header includes:
|
|
126
178
|
|
|
127
|
-
|
|
179
|
+
- Category name and description
|
|
180
|
+
- Category image
|
|
181
|
+
- Product count
|
|
182
|
+
- Breadcrumb trail with navigation URLs
|
|
183
|
+
- SEO metadata (title tags, meta description, keywords)
|
|
128
184
|
|
|
129
|
-
|
|
130
|
-
jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-sneakers"}'
|
|
131
|
-
```
|
|
185
|
+
## Filter URL Persistence
|
|
132
186
|
|
|
133
|
-
|
|
187
|
+
Filters are persisted in URL query parameters for sharable, bookmarkable URLs:
|
|
134
188
|
|
|
135
|
-
|
|
189
|
+
| Filter | Query Param | Example |
|
|
190
|
+
| --------------- | ----------- | ------------------- |
|
|
191
|
+
| Search term | `q` | `?q=cotton` |
|
|
192
|
+
| Category filter | `cat` | `?cat=shirts,pants` |
|
|
193
|
+
| Min price | `min` | `?min=50` |
|
|
194
|
+
| Max price | `max` | `?max=200` |
|
|
195
|
+
| In stock only | `inStock` | `?inStock=1` |
|
|
196
|
+
| Sort | `sort` | `?sort=priceAsc` |
|
|
136
197
|
|
|
137
|
-
|
|
138
|
-
const categories = await getCategories();
|
|
139
|
-
```
|
|
198
|
+
All values use slugs/display values, not internal IDs.
|
|
140
199
|
|
|
141
|
-
|
|
200
|
+
Example: `/products/polgat?q=cotton&cat=shirts&min=50&sort=priceAsc`
|
|
142
201
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
```
|
|
202
|
+
- **Saving**: On filter change, the URL is updated via `replaceState` (no page reload)
|
|
203
|
+
- **Restoring**: On page load (SSR), query params are parsed and applied as the initial filter state. The server-rendered page shows filtered results immediately.
|
|
146
204
|
|
|
147
|
-
##
|
|
205
|
+
## Canonical URLs & Redirects
|
|
148
206
|
|
|
149
|
-
|
|
207
|
+
The `urls` config defines canonical URLs. If a page is accessed at a non-canonical path, the component issues a 301 redirect.
|
|
150
208
|
|
|
151
|
-
|
|
152
|
-
- Full parent-child hierarchy as a nested tree
|
|
153
|
-
- Configured category prefixes (if any) with their mapped category names
|
|
209
|
+
**Products:** Identified by slug. The canonical `{category}` comes from `mainCategoryId`, and `{prefix}` from the root category. Wrong category or prefix in the URL → 301 redirect.
|
|
154
210
|
|
|
155
|
-
|
|
211
|
+
**Categories:** Identified by slug. The canonical `{prefix}` comes from the parent chain. Wrong prefix in the URL → 301 redirect.
|
|
156
212
|
|
|
157
|
-
|
|
213
|
+
## Server Actions
|
|
158
214
|
|
|
215
|
+
### `searchProducts`
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
jay-stack action wix-stores/searchProducts
|
|
219
|
+
jay-stack action wix-stores/searchProducts --input '{"query": "shoes", "pageSize": 5}'
|
|
159
220
|
```
|
|
160
|
-
wix-server-client (init first)
|
|
161
|
-
└── Registers WIX_CLIENT_SERVICE (API key auth)
|
|
162
221
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
222
|
+
### `getProductBySlug`
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
jay-stack action wix-stores/getProductBySlug --input '{"slug": "blue-sneakers"}'
|
|
167
226
|
```
|
|
168
227
|
|
|
169
|
-
###
|
|
228
|
+
### `getCategories`
|
|
170
229
|
|
|
171
|
-
|
|
230
|
+
```bash
|
|
231
|
+
jay-stack action wix-stores/getCategories
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Agent Kit References
|
|
172
235
|
|
|
173
|
-
|
|
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
|
|
236
|
+
Running `jay-stack setup wix-stores` generates `agent-kit/references/wix-stores/categories.yaml` — the full category tree with IDs, names, slugs, product counts, and parent-child hierarchy.
|
|
177
237
|
|
|
178
|
-
## Design
|
|
238
|
+
## Design Logs
|
|
179
239
|
|
|
180
|
-
|
|
240
|
+
- [Design Log 10 - Category-Prefixed Product Routes](../../design-log/10%20-%20category-prefixed-product-routes.md)
|
|
241
|
+
- [Design Log 11 - Category Deep-Linking & Header](../../design-log/11%20-%20category-deep-linking-and-header.md)
|
|
181
242
|
|
|
182
243
|
## License
|
|
183
244
|
|
|
@@ -78,7 +78,10 @@ export type CategoryPageSlowViewState = Pick<CategoryPageViewState, '_id' | 'nam
|
|
|
78
78
|
ribbon: CategoryPageViewState['products'][number]['ribbon'];
|
|
79
79
|
brand: CategoryPageViewState['products'][number]['brand'];
|
|
80
80
|
quickOption: Pick<CategoryPageViewState['products'][number]['quickOption'], '_id' | 'name' | 'optionRenderType'> & {
|
|
81
|
-
choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'
|
|
81
|
+
choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
|
|
82
|
+
};
|
|
83
|
+
secondQuickOption: Pick<CategoryPageViewState['products'][number]['secondQuickOption'], '_id' | 'name' | 'optionRenderType'> & {
|
|
84
|
+
choices: Array<Pick<CategoryPageViewState['products'][number]['secondQuickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
|
|
82
85
|
};
|
|
83
86
|
}>;
|
|
84
87
|
};
|
|
@@ -87,6 +90,9 @@ export type CategoryPageFastViewState = Pick<CategoryPageViewState, 'hasMore' |
|
|
|
87
90
|
products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
|
|
88
91
|
quickOption: {
|
|
89
92
|
choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
93
|
+
};
|
|
94
|
+
secondQuickOption: {
|
|
95
|
+
choices: Array<Pick<CategoryPageViewState['products'][number]['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
90
96
|
};
|
|
91
97
|
}>;
|
|
92
98
|
loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
|
|
@@ -96,6 +102,9 @@ export type CategoryPageInteractiveViewState = Pick<CategoryPageViewState, 'hasM
|
|
|
96
102
|
products: Array<Pick<CategoryPageViewState['products'][number], '_id' | 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
|
|
97
103
|
quickOption: {
|
|
98
104
|
choices: Array<Pick<CategoryPageViewState['products'][number]['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
105
|
+
};
|
|
106
|
+
secondQuickOption: {
|
|
107
|
+
choices: Array<Pick<CategoryPageViewState['products'][number]['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
99
108
|
};
|
|
100
109
|
}>;
|
|
101
110
|
loadedProducts: Array<CategoryPageViewState['loadedProducts'][number]>;
|
|
@@ -165,15 +165,21 @@ tags:
|
|
|
165
165
|
# Quick-add behavior
|
|
166
166
|
- tag: quickAddType
|
|
167
167
|
type: variant
|
|
168
|
-
dataType: enum (SIMPLE | SINGLE_OPTION | NEEDS_CONFIGURATION)
|
|
168
|
+
dataType: enum (SIMPLE | SINGLE_OPTION | COLOR_AND_TEXT_OPTIONS | NEEDS_CONFIGURATION)
|
|
169
169
|
description: |
|
|
170
170
|
SIMPLE = no options, show Add to Cart button
|
|
171
171
|
SINGLE_OPTION = one option, show choices on hover (click = add to cart)
|
|
172
|
+
COLOR_AND_TEXT_OPTIONS = two options (color + text), color pre-selected, text selection adds to cart
|
|
172
173
|
NEEDS_CONFIGURATION = multiple options or modifiers, link to product page
|
|
173
174
|
|
|
174
175
|
- tag: quickOption
|
|
175
176
|
type: sub-contract
|
|
176
|
-
description: Primary option for quick selection (
|
|
177
|
+
description: Primary option for quick selection (color swatches for COLOR_AND_TEXT_OPTIONS, or the single option for SINGLE_OPTION)
|
|
178
|
+
link: ./product-options
|
|
179
|
+
|
|
180
|
+
- tag: secondQuickOption
|
|
181
|
+
type: sub-contract
|
|
182
|
+
description: Secondary option for quick selection (text choices for COLOR_AND_TEXT_OPTIONS). Rendered as buttons or dropdown. Selection adds to cart.
|
|
177
183
|
link: ./product-options
|
|
178
184
|
|
|
179
185
|
- tag: viewOptionsButton
|
|
@@ -54,6 +54,7 @@ export enum ProductType {
|
|
|
54
54
|
export enum QuickAddType {
|
|
55
55
|
SIMPLE,
|
|
56
56
|
SINGLE_OPTION,
|
|
57
|
+
COLOR_AND_TEXT_OPTIONS,
|
|
57
58
|
NEEDS_CONFIGURATION
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -75,7 +76,8 @@ export interface ProductCardViewState {
|
|
|
75
76
|
productType: ProductType,
|
|
76
77
|
isAddingToCart: boolean,
|
|
77
78
|
quickAddType: QuickAddType,
|
|
78
|
-
quickOption: ProductOptionsViewState
|
|
79
|
+
quickOption: ProductOptionsViewState,
|
|
80
|
+
secondQuickOption: ProductOptionsViewState
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
export type ProductCardSlowViewState = Pick<ProductCardViewState, '_id' | 'name' | 'slug' | 'productUrl' | 'categoryPrefix' | 'hasDiscount' | 'hasRibbon' | 'productType' | 'quickAddType'> & {
|
|
@@ -85,19 +87,28 @@ export type ProductCardSlowViewState = Pick<ProductCardViewState, '_id' | 'name'
|
|
|
85
87
|
ribbon: ProductCardViewState['ribbon'];
|
|
86
88
|
brand: ProductCardViewState['brand'];
|
|
87
89
|
quickOption: Pick<ProductCardViewState['quickOption'], '_id' | 'name' | 'optionRenderType'> & {
|
|
88
|
-
choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'
|
|
90
|
+
choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
|
|
91
|
+
};
|
|
92
|
+
secondQuickOption: Pick<ProductCardViewState['secondQuickOption'], '_id' | 'name' | 'optionRenderType'> & {
|
|
93
|
+
choices: Array<Pick<ProductCardViewState['secondQuickOption']['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
|
|
89
94
|
};
|
|
90
95
|
};
|
|
91
96
|
|
|
92
97
|
export type ProductCardFastViewState = Pick<ProductCardViewState, 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
|
|
93
98
|
quickOption: {
|
|
94
99
|
choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
100
|
+
};
|
|
101
|
+
secondQuickOption: {
|
|
102
|
+
choices: Array<Pick<ProductCardViewState['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
95
103
|
};
|
|
96
104
|
};
|
|
97
105
|
|
|
98
106
|
export type ProductCardInteractiveViewState = Pick<ProductCardViewState, 'price' | 'strikethroughPrice' | 'isAddingToCart'> & {
|
|
99
107
|
quickOption: {
|
|
100
108
|
choices: Array<Pick<ProductCardViewState['quickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
109
|
+
};
|
|
110
|
+
secondQuickOption: {
|
|
111
|
+
choices: Array<Pick<ProductCardViewState['secondQuickOption']['choices'][number], 'choiceId' | 'inStock' | 'isSelected'>>;
|
|
101
112
|
};
|
|
102
113
|
};
|
|
103
114
|
|
|
@@ -106,7 +117,8 @@ export interface ProductCardRefs {
|
|
|
106
117
|
productLink: HTMLElementProxy<ProductCardViewState, HTMLAnchorElement>,
|
|
107
118
|
addToCartButton: HTMLElementProxy<ProductCardViewState, HTMLButtonElement>,
|
|
108
119
|
viewOptionsButton: HTMLElementProxy<ProductCardViewState, HTMLButtonElement>,
|
|
109
|
-
quickOption: ProductOptionsRefs
|
|
120
|
+
quickOption: ProductOptionsRefs,
|
|
121
|
+
secondQuickOption: ProductOptionsRefs
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
|
|
@@ -114,7 +126,8 @@ export interface ProductCardRepeatedRefs {
|
|
|
114
126
|
productLink: HTMLElementCollectionProxy<ProductCardViewState, HTMLAnchorElement>,
|
|
115
127
|
addToCartButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
|
|
116
128
|
viewOptionsButton: HTMLElementCollectionProxy<ProductCardViewState, HTMLButtonElement>,
|
|
117
|
-
quickOption: ProductOptionsRepeatedRefs
|
|
129
|
+
quickOption: ProductOptionsRepeatedRefs,
|
|
130
|
+
secondQuickOption: ProductOptionsRepeatedRefs
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
export type ProductCardContract = JayContract<ProductCardViewState, ProductCardRefs, ProductCardSlowViewState, ProductCardFastViewState, ProductCardInteractiveViewState>
|
|
@@ -49,11 +49,6 @@ tags:
|
|
|
49
49
|
phase: fast+interactive
|
|
50
50
|
description: Whether this choice has available stock
|
|
51
51
|
|
|
52
|
-
- tag: variantId
|
|
53
|
-
type: data
|
|
54
|
-
dataType: string
|
|
55
|
-
description: The variant ID to use when adding to cart (for single-option products)
|
|
56
|
-
|
|
57
52
|
- tag: isSelected
|
|
58
53
|
type: variant
|
|
59
54
|
dataType: boolean
|
|
@@ -17,7 +17,6 @@ export interface ChoiceOfProductOptionsViewState {
|
|
|
17
17
|
choiceType: ChoiceType,
|
|
18
18
|
colorCode: string,
|
|
19
19
|
inStock: boolean,
|
|
20
|
-
variantId: string,
|
|
21
20
|
isSelected: boolean
|
|
22
21
|
}
|
|
23
22
|
|
|
@@ -29,7 +28,7 @@ export interface ProductOptionsViewState {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
export type ProductOptionsSlowViewState = Pick<ProductOptionsViewState, '_id' | 'name' | 'optionRenderType'> & {
|
|
32
|
-
choices: Array<Pick<ProductOptionsViewState['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'
|
|
31
|
+
choices: Array<Pick<ProductOptionsViewState['choices'][number], 'choiceId' | 'name' | 'choiceType' | 'colorCode'>>;
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
export type ProductOptionsFastViewState = {
|
|
@@ -168,6 +168,11 @@ tags:
|
|
|
168
168
|
dataType: boolean
|
|
169
169
|
elementType: HTMLInputElement
|
|
170
170
|
description: Category checkbox
|
|
171
|
+
|
|
172
|
+
- tag: categoryUrl
|
|
173
|
+
type: data
|
|
174
|
+
dataType: string
|
|
175
|
+
description: Deep-link URL to this category's dedicated page
|
|
171
176
|
|
|
172
177
|
# Availability filter (fast+interactive)
|
|
173
178
|
- tag: inStockOnly
|
|
@@ -250,3 +255,69 @@ tags:
|
|
|
250
255
|
elementType: HTMLButtonElement
|
|
251
256
|
description: Button to use this suggestion
|
|
252
257
|
|
|
258
|
+
# Category header — always populated via fallback chain (subcategory → category → defaultCategory)
|
|
259
|
+
- tag: categoryHeader
|
|
260
|
+
type: sub-contract
|
|
261
|
+
description: Active category information for the page header
|
|
262
|
+
tags:
|
|
263
|
+
- {tag: name, type: data, dataType: string, description: Category display name}
|
|
264
|
+
- {tag: description, type: data, dataType: string, description: Category plain text description}
|
|
265
|
+
- {tag: imageUrl, type: data, dataType: string, description: Category main image URL}
|
|
266
|
+
- {tag: hasImage, type: variant, dataType: boolean, description: Whether category has an image}
|
|
267
|
+
- {tag: productCount, type: data, dataType: number, description: Number of products in category}
|
|
268
|
+
|
|
269
|
+
- tag: breadcrumbs
|
|
270
|
+
type: sub-contract
|
|
271
|
+
repeated: true
|
|
272
|
+
trackBy: categoryId
|
|
273
|
+
description: Breadcrumb trail from root to current category
|
|
274
|
+
tags:
|
|
275
|
+
- {tag: categoryId, type: data, dataType: string, description: Category GUID}
|
|
276
|
+
- {tag: name, type: data, dataType: string, description: Category name}
|
|
277
|
+
- {tag: slug, type: data, dataType: string, description: Category slug}
|
|
278
|
+
- {tag: url, type: data, dataType: string, description: Full URL path for breadcrumb navigation}
|
|
279
|
+
|
|
280
|
+
- tag: seoData
|
|
281
|
+
type: sub-contract
|
|
282
|
+
description: Category SEO metadata
|
|
283
|
+
tags:
|
|
284
|
+
- tag: tags
|
|
285
|
+
type: sub-contract
|
|
286
|
+
repeated: true
|
|
287
|
+
trackBy: position
|
|
288
|
+
description: SEO tag information
|
|
289
|
+
tags:
|
|
290
|
+
- {tag: position, type: data, dataType: string, description: Tag position as two digit string}
|
|
291
|
+
- {tag: type, type: data, dataType: string, description: SEO tag type}
|
|
292
|
+
- tag: props
|
|
293
|
+
type: sub-contract
|
|
294
|
+
repeated: true
|
|
295
|
+
trackBy: key
|
|
296
|
+
description: Key-value pair of SEO properties
|
|
297
|
+
tags:
|
|
298
|
+
- {tag: key, type: data, dataType: string}
|
|
299
|
+
- {tag: value, type: data, dataType: string}
|
|
300
|
+
- tag: meta
|
|
301
|
+
type: sub-contract
|
|
302
|
+
repeated: true
|
|
303
|
+
trackBy: key
|
|
304
|
+
description: SEO tag metadata
|
|
305
|
+
tags:
|
|
306
|
+
- {tag: key, type: data, dataType: string}
|
|
307
|
+
- {tag: value, type: data, dataType: string}
|
|
308
|
+
- {tag: children, type: data, dataType: string, description: SEO tag inner content}
|
|
309
|
+
- tag: settings
|
|
310
|
+
type: sub-contract
|
|
311
|
+
description: SEO general settings
|
|
312
|
+
tags:
|
|
313
|
+
- {tag: preventAutoRedirect, type: data, dataType: boolean, description: Whether auto-redirect from old URL is enabled}
|
|
314
|
+
- tag: keywords
|
|
315
|
+
type: sub-contract
|
|
316
|
+
repeated: true
|
|
317
|
+
trackBy: term
|
|
318
|
+
description: User-selected keyword terms
|
|
319
|
+
tags:
|
|
320
|
+
- {tag: term, type: data, dataType: string, description: Keyword value}
|
|
321
|
+
- {tag: isMain, type: data, dataType: boolean, description: Whether this is the main focus keyword}
|
|
322
|
+
- {tag: origin, type: data, dataType: string, description: Source that added the keyword}
|
|
323
|
+
|