@wix/agent-skills 1.0.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.
Files changed (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +29 -0
  4. package/skills/wix-app/SKILL.md +344 -0
  5. package/skills/wix-app/references/APP_IDENTIFIERS.md +35 -0
  6. package/skills/wix-app/references/APP_MARKET_REVIEW.md +311 -0
  7. package/skills/wix-app/references/APP_VALIDATION.md +171 -0
  8. package/skills/wix-app/references/BACKEND_API.md +233 -0
  9. package/skills/wix-app/references/BACKEND_EVENT.md +116 -0
  10. package/skills/wix-app/references/CODE_QUALITY.md +33 -0
  11. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +587 -0
  12. package/skills/wix-app/references/DASHBOARD_MENU_PLUGIN.md +157 -0
  13. package/skills/wix-app/references/DASHBOARD_MODAL.md +283 -0
  14. package/skills/wix-app/references/DASHBOARD_PAGE.md +403 -0
  15. package/skills/wix-app/references/DASHBOARD_PLUGIN.md +205 -0
  16. package/skills/wix-app/references/DATA_COLLECTION.md +451 -0
  17. package/skills/wix-app/references/DOCUMENTATION.md +42 -0
  18. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +65 -0
  19. package/skills/wix-app/references/EMBEDDED_SCRIPT.md +468 -0
  20. package/skills/wix-app/references/EXTENSION_REGISTRATION.md +96 -0
  21. package/skills/wix-app/references/SERVICE_PLUGIN.md +235 -0
  22. package/skills/wix-app/references/SITE_PLUGIN.md +436 -0
  23. package/skills/wix-app/references/STORES_VERSIONING.md +334 -0
  24. package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +84 -0
  25. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +345 -0
  26. package/skills/wix-app/references/dashboard-menu-plugin/blog-slots.md +71 -0
  27. package/skills/wix-app/references/dashboard-menu-plugin/bookings-slots.md +58 -0
  28. package/skills/wix-app/references/dashboard-menu-plugin/crm-slots.md +8 -0
  29. package/skills/wix-app/references/dashboard-menu-plugin/ecommerce-slots.md +17 -0
  30. package/skills/wix-app/references/dashboard-menu-plugin/events-slots.md +29 -0
  31. package/skills/wix-app/references/dashboard-menu-plugin/restaurants-slots.md +27 -0
  32. package/skills/wix-app/references/dashboard-menu-plugin/stores-slots.md +35 -0
  33. package/skills/wix-app/references/dashboard-page/API_SPEC.md +138 -0
  34. package/skills/wix-app/references/dashboard-page/DASHBOARD_API.md +573 -0
  35. package/skills/wix-app/references/dashboard-page/DYNAMIC_PARAMETERS.md +269 -0
  36. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +164 -0
  37. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +294 -0
  38. package/skills/wix-app/references/data-collection/WIX_DATA.md +270 -0
  39. package/skills/wix-app/references/data-collection/extension-template.ts +107 -0
  40. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +138 -0
  41. package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +454 -0
  42. package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +165 -0
  43. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +576 -0
  44. package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +170 -0
  45. package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +6 -0
  46. package/skills/wix-app/references/editor-react-component/PARTS.md +25 -0
  47. package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +203 -0
  48. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +167 -0
  49. package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +137 -0
  50. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +112 -0
  51. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +118 -0
  52. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +87 -0
  53. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +86 -0
  54. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +135 -0
  55. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +79 -0
  56. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +68 -0
  57. package/skills/wix-app/references/site-plugin/EXAMPLES.md +311 -0
  58. package/skills/wix-app/references/site-plugin/SLOTS.md +263 -0
  59. package/skills/wix-design-system/SKILL.md +129 -0
  60. package/skills/wix-design-system/references/file-structure.md +56 -0
  61. package/skills/wix-design-system/scripts/wds.cjs +531 -0
  62. package/skills/wix-headless/SKILL.md +418 -0
  63. package/skills/wix-headless/references/DISCOVERY.md +332 -0
  64. package/skills/wix-headless/references/ORCHESTRATION.md +269 -0
  65. package/skills/wix-headless/references/SETUP.md +154 -0
  66. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +152 -0
  67. package/skills/wix-headless/references/blog/BLOG_PAGES.md +299 -0
  68. package/skills/wix-headless/references/blog/BLOG_SETUP.md +218 -0
  69. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +32 -0
  70. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +373 -0
  71. package/skills/wix-headless/references/cms/FAQ_KNOWLEDGE_BASE.md +229 -0
  72. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +40 -0
  73. package/skills/wix-headless/references/cms/PORTFOLIO.md +454 -0
  74. package/skills/wix-headless/references/cms/RESOURCE_LIBRARY.md +356 -0
  75. package/skills/wix-headless/references/cms/TEAM_DIRECTORY.md +299 -0
  76. package/skills/wix-headless/references/commands/install-app.md +42 -0
  77. package/skills/wix-headless/references/commands/known-apps.json +5 -0
  78. package/skills/wix-headless/references/commands/mcp-bootstrap.md +28 -0
  79. package/skills/wix-headless/references/designer/INSTRUCTIONS.md +828 -0
  80. package/skills/wix-headless/references/ecom/CART_PAGES.md +120 -0
  81. package/skills/wix-headless/references/ecom/CART_WIRING.md +103 -0
  82. package/skills/wix-headless/references/ecom/COMPONENTS_CSS.md +132 -0
  83. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +59 -0
  84. package/skills/wix-headless/references/forms/CONTACT_FORM.md +477 -0
  85. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +43 -0
  86. package/skills/wix-headless/references/gift-cards/COMPONENTS.md +119 -0
  87. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +51 -0
  88. package/skills/wix-headless/references/gift-cards/PAGES.md +129 -0
  89. package/skills/wix-headless/references/images/INSTRUCTIONS.md +359 -0
  90. package/skills/wix-headless/references/shared/BUILD_NOISE.md +8 -0
  91. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +185 -0
  92. package/skills/wix-headless/references/shared/IMPLEMENTER.md +155 -0
  93. package/skills/wix-headless/references/shared/MCP_PREFIX.md +105 -0
  94. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +97 -0
  95. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +423 -0
  96. package/skills/wix-headless/references/shared/STYLING.md +117 -0
  97. package/skills/wix-headless/references/stores/BACK_IN_STOCK.md +178 -0
  98. package/skills/wix-headless/references/stores/CATEGORY_PAGES.md +86 -0
  99. package/skills/wix-headless/references/stores/COMPONENTS_CSS.md +129 -0
  100. package/skills/wix-headless/references/stores/HOME_AND_NAV.md +227 -0
  101. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +96 -0
  102. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +249 -0
  103. package/skills/wix-headless/references/stores/PRODUCT_PAGES.md +213 -0
  104. package/skills/wix-headless/references/stores/SHARED_WIRING.md +164 -0
  105. package/skills/wix-headless/references/verticals/_schema.json +160 -0
  106. package/skills/wix-headless/references/verticals/_schema.md +185 -0
  107. package/skills/wix-headless/references/verticals/blog.md +125 -0
  108. package/skills/wix-headless/references/verticals/cms.md +121 -0
  109. package/skills/wix-headless/references/verticals/ecom.md +96 -0
  110. package/skills/wix-headless/references/verticals/forms.md +106 -0
  111. package/skills/wix-headless/references/verticals/gift-cards.md +121 -0
  112. package/skills/wix-headless/references/verticals/stores.md +167 -0
  113. package/skills/wix-headless/scripts/check-manifest.mjs +198 -0
  114. package/skills/wix-headless/scripts/copy-utility-templates.mjs +153 -0
  115. package/skills/wix-headless/scripts/emit-design-tokens.mjs +77 -0
  116. package/skills/wix-headless/scripts/finalize-run-json.mjs +141 -0
  117. package/skills/wix-headless/scripts/init-site-json.mjs +63 -0
  118. package/skills/wix-headless/scripts/patch-decorative-slots.mjs +124 -0
  119. package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +190 -0
  120. package/skills/wix-headless/scripts/preview.sh +45 -0
  121. package/skills/wix-headless/scripts/release.sh +73 -0
  122. package/skills/wix-headless/scripts/scaffold.sh +53 -0
  123. package/skills/wix-headless/scripts/seed-utilities.sh +55 -0
  124. package/skills/wix-headless/shared-utilities/analytics.ts +40 -0
  125. package/skills/wix-headless/shared-utilities/ricos.ts +179 -0
  126. package/skills/wix-headless/shared-utilities/wix-image.ts +72 -0
  127. package/skills/wix-headless/templates/ecom/CartBadge.tsx +84 -0
  128. package/skills/wix-headless/templates/ecom/CartView.tsx +543 -0
  129. package/skills/wix-headless/templates/ecom/cart.astro +13 -0
  130. package/skills/wix-headless/templates/ecom/components-ecom.css +215 -0
  131. package/skills/wix-headless/templates/ecom/discounts.ts +71 -0
  132. package/skills/wix-headless/templates/ecom/thank-you.astro +24 -0
  133. package/skills/wix-headless/templates/gift-cards/GiftCardPurchase.tsx +172 -0
  134. package/skills/wix-headless/templates/gift-cards/_home-teaser-snippet.astro +42 -0
  135. package/skills/wix-headless/templates/gift-cards/_nav-snippet.astro +11 -0
  136. package/skills/wix-headless/templates/gift-cards/components-gift-cards.css +219 -0
  137. package/skills/wix-headless/templates/gift-cards/gift-cards.astro +51 -0
  138. package/skills/wix-headless/templates/gift-cards/gift-cards.ts +75 -0
  139. package/skills/wix-headless/templates/stores/AddToCartButton.tsx +77 -0
  140. package/skills/wix-headless/templates/stores/BackInStockForm.tsx +176 -0
  141. package/skills/wix-headless/templates/stores/CategoryRail.astro +69 -0
  142. package/skills/wix-headless/templates/stores/ProductCard.astro +79 -0
  143. package/skills/wix-headless/templates/stores/ProductPurchase.tsx +397 -0
  144. package/skills/wix-headless/templates/stores/SeoTags.astro +21 -0
  145. package/skills/wix-headless/templates/stores/back-in-stock.ts +67 -0
  146. package/skills/wix-headless/templates/stores/categories.ts +155 -0
  147. package/skills/wix-headless/templates/stores/category/[slug].astro +130 -0
  148. package/skills/wix-headless/templates/stores/components-stores.css +295 -0
  149. package/skills/wix-headless/templates/stores/products/[slug].astro +128 -0
  150. package/skills/wix-headless/templates/stores/products/index.astro +101 -0
  151. package/skills/wix-manage/SKILL.md +280 -0
  152. package/skills/wix-manage/references/app-installation/install-wix-apps.md +155 -0
  153. package/skills/wix-manage/references/app-installation/list-installed-apps.md +124 -0
  154. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +226 -0
  155. package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +95 -0
  156. package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +416 -0
  157. package/skills/wix-manage/references/bookings/bookings-staff-setup.md +255 -0
  158. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +462 -0
  159. package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +278 -0
  160. package/skills/wix-manage/references/bookings/external-calendar-integration.md +258 -0
  161. package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +231 -0
  162. package/skills/wix-manage/references/calendar/configure-default-business-hours.md +226 -0
  163. package/skills/wix-manage/references/cms/cms-data-items-crud.md +422 -0
  164. package/skills/wix-manage/references/cms/cms-data-operations-extended.md +263 -0
  165. package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +136 -0
  166. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +160 -0
  167. package/skills/wix-manage/references/cms/cms-schema-management.md +143 -0
  168. package/skills/wix-manage/references/contacts/bulk-delete-contacts.md +73 -0
  169. package/skills/wix-manage/references/contacts/bulk-label-and-unlabel-contacts.md +67 -0
  170. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +434 -0
  171. package/skills/wix-manage/references/ecommerce/api-recommendation-tracking.md +420 -0
  172. package/skills/wix-manage/references/ecommerce/flow-add-free-shipping.md +267 -0
  173. package/skills/wix-manage/references/ecommerce/flow-bundle-and-save.md +290 -0
  174. package/skills/wix-manage/references/ecommerce/flow-fix-coverage-gaps.md +300 -0
  175. package/skills/wix-manage/references/ecommerce/flow-optimize-shipping-rates.md +364 -0
  176. package/skills/wix-manage/references/ecommerce/flow-seasonal-promotion.md +315 -0
  177. package/skills/wix-manage/references/ecommerce/flow-stock-mover.md +308 -0
  178. package/skills/wix-manage/references/ecommerce/flow-upsell-boost.md +271 -0
  179. package/skills/wix-manage/references/ecommerce/goal-clear-inventory.md +118 -0
  180. package/skills/wix-manage/references/ecommerce/goal-drive-cross-sells.md +122 -0
  181. package/skills/wix-manage/references/ecommerce/goal-increase-aov.md +132 -0
  182. package/skills/wix-manage/references/ecommerce/goal-reduce-cart-abandonment.md +148 -0
  183. package/skills/wix-manage/references/ecommerce/goal-seasonal-revenue.md +146 -0
  184. package/skills/wix-manage/references/ecommerce/guardrail-discount-conflicts.md +118 -0
  185. package/skills/wix-manage/references/ecommerce/guardrail-margin-protection.md +88 -0
  186. package/skills/wix-manage/references/ecommerce/guardrail-rate-pricing-sanity.md +108 -0
  187. package/skills/wix-manage/references/ecommerce/guardrail-shipping-health.md +124 -0
  188. package/skills/wix-manage/references/ecommerce/recipe-apply-shipping-recommendations.md +299 -0
  189. package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +582 -0
  190. package/skills/wix-manage/references/ecommerce/setup-coupons.md +338 -0
  191. package/skills/wix-manage/references/ecommerce/setup-discount-rules.md +444 -0
  192. package/skills/wix-manage/references/ecommerce/setup-shipping-rates.md +86 -0
  193. package/skills/wix-manage/references/ecommerce/setup-shipping-regions.md +56 -0
  194. package/skills/wix-manage/references/ecommerce/setup-store-pickup-location.md +209 -0
  195. package/skills/wix-manage/references/ecommerce/skill-graph.md +139 -0
  196. package/skills/wix-manage/references/ecommerce/troubleshoot-checkout-delivery-dropoff.md +124 -0
  197. package/skills/wix-manage/references/ecommerce/troubleshoot-discount-not-applying.md +95 -0
  198. package/skills/wix-manage/references/events/list-events.md +203 -0
  199. package/skills/wix-manage/references/forms/create-form.md +260 -0
  200. package/skills/wix-manage/references/get-paid/create-payment-links.md +212 -0
  201. package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +57 -0
  202. package/skills/wix-manage/references/get-paid/payment-links-for-bookings.md +101 -0
  203. package/skills/wix-manage/references/media/upload-media-to-wix.md +216 -0
  204. package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +77 -0
  205. package/skills/wix-manage/references/pricing-plans/pricing-plans-bookings-integration.md +166 -0
  206. package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +336 -0
  207. package/skills/wix-manage/references/rich-content/ricos-converter-service.md +295 -0
  208. package/skills/wix-manage/references/site-properties/change-payment-currency-site-properties.md +59 -0
  209. package/skills/wix-manage/references/sites/create-site-from-template.md +183 -0
  210. package/skills/wix-manage/references/sites/query-sites.md +125 -0
  211. package/skills/wix-manage/references/stores/add-store-pages-to-site.md +57 -0
  212. package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1041 -0
  213. package/skills/wix-manage/references/stores/create-product-catalog-v1.md +125 -0
  214. package/skills/wix-manage/references/stores/create-product-from-image.md +824 -0
  215. package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +566 -0
  216. package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +194 -0
  217. package/skills/wix-manage/references/stores/query-products-catalog-v1.md +80 -0
  218. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +99 -0
  219. package/skills/wix-manage/references/stores/update-product-pre-order.md +213 -0
  220. package/skills/wix-manage/references/stores/update-product-with-options.md +318 -0
@@ -0,0 +1,155 @@
1
+ // Wix Stores Categories — visible, in-use categories under the @wix/stores
2
+ // tree. Drops the auto-provisioned "All Products" root and any category
3
+ // with zero items so the storefront only surfaces curator-meaningful
4
+ // buckets. Returns [] on any failure so consumers can render gracefully.
5
+ //
6
+ // Only one category-tree is in scope here: @wix/stores. Sites with multiple
7
+ // stores trees (B2B, marketplaces) should pass an explicit treeKey.
8
+
9
+ // The official `@wix/categories` package re-exports these functions; the
10
+ // auto_sdk module is already on disk via every other @wix/* package's
11
+ // transitive deps, so no extra `npm install` is needed.
12
+ import * as categories from "@wix/auto_sdk_categories_categories";
13
+ import { productsV3 } from "@wix/stores";
14
+
15
+ const STORES_NAMESPACE = "@wix/stores";
16
+ const STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
17
+ const ALL_PRODUCTS_HANDLE = "online_stores_all_products";
18
+
19
+ // Module-level TTL cache — opportunistic across requests on warm worker
20
+ // isolates, harmless on cold starts. Categories rarely change; 5 min is fine.
21
+ // Errors are NOT cached so a transient failure doesn't lock out the listing.
22
+ const CATEGORIES_TTL_MS = 5 * 60 * 1000;
23
+ let categoriesCache: { at: number; data: StoreCategory[]; bySlug: Map<string, StoreCategory> } | null = null;
24
+ let inflightCategories: Promise<StoreCategory[]> | null = null;
25
+
26
+ // Per-category product-ID list — cached under the same TTL so the
27
+ // 2-call listProductsInCategory pipeline doesn't re-fetch IDs on every page.
28
+ const categoryItemIdsCache = new Map<string, { at: number; ids: string[] }>();
29
+
30
+ export interface StoreCategory {
31
+ id: string;
32
+ name: string;
33
+ slug: string;
34
+ description?: string;
35
+ itemCounter: number;
36
+ imageUrl?: string;
37
+ }
38
+
39
+ function toStoreCategory(c: any): StoreCategory | null {
40
+ if (!c?._id || !c?.slug || !c?.name) return null;
41
+ return {
42
+ id: c._id,
43
+ name: c.name,
44
+ slug: c.slug,
45
+ description: c.description ?? undefined,
46
+ itemCounter: typeof c.itemCounter === "number" ? c.itemCounter : 0,
47
+ imageUrl: c.image?.url ?? undefined,
48
+ };
49
+ }
50
+
51
+ async function fetchCategories(): Promise<StoreCategory[]> {
52
+ // The SDK builder rejects empty filter expressions with INVALID_FILTER.
53
+ // `.eq("visible", true)` is the constraint we want anyway and satisfies
54
+ // the validator.
55
+ const res = await categories
56
+ .queryCategories({
57
+ treeReference: { appNamespace: STORES_NAMESPACE },
58
+ })
59
+ .eq("visible", true)
60
+ .ascending("name")
61
+ .limit(100)
62
+ .find();
63
+ return (res.items ?? [])
64
+ .filter((c: any) => c?.handle !== ALL_PRODUCTS_HANDLE)
65
+ .filter((c: any) => typeof c?.itemCounter === "number" && c.itemCounter > 0)
66
+ .map(toStoreCategory)
67
+ .filter((c): c is StoreCategory => c !== null);
68
+ }
69
+
70
+ export async function listStoreCategories(): Promise<StoreCategory[]> {
71
+ if (categoriesCache && Date.now() - categoriesCache.at < CATEGORIES_TTL_MS) {
72
+ return categoriesCache.data;
73
+ }
74
+ if (inflightCategories) return inflightCategories;
75
+ inflightCategories = (async () => {
76
+ try {
77
+ const data = await fetchCategories();
78
+ const bySlug = new Map(data.map((c) => [c.slug, c]));
79
+ categoriesCache = { at: Date.now(), data, bySlug };
80
+ return data;
81
+ } catch (err) {
82
+ console.error("[categories] list failed:", err);
83
+ return [];
84
+ } finally {
85
+ inflightCategories = null;
86
+ }
87
+ })();
88
+ return inflightCategories;
89
+ }
90
+
91
+ export async function getCategoryBySlug(slug: string): Promise<StoreCategory | null> {
92
+ const all = await listStoreCategories();
93
+ return categoriesCache?.bySlug.get(slug) ?? all.find((c) => c.slug === slug) ?? null;
94
+ }
95
+
96
+ async function fetchCategoryItemIds(categoryId: string): Promise<string[]> {
97
+ try {
98
+ const res = await categories.listItemsInCategory(
99
+ categoryId,
100
+ { appNamespace: STORES_NAMESPACE },
101
+ );
102
+ return (res.items ?? [])
103
+ .map((it: any) => it?.catalogItemId)
104
+ .filter((id: any): id is string => typeof id === "string" && id.length > 0);
105
+ } catch (err) {
106
+ console.error("[categories] listItemsInCategory failed:", err);
107
+ return [];
108
+ }
109
+ }
110
+
111
+ async function getCategoryItemIds(categoryId: string): Promise<string[]> {
112
+ const cached = categoryItemIdsCache.get(categoryId);
113
+ if (cached && Date.now() - cached.at < CATEGORIES_TTL_MS) return cached.ids;
114
+ const ids = await fetchCategoryItemIds(categoryId);
115
+ categoryItemIdsCache.set(categoryId, { at: Date.now(), ids });
116
+ return ids;
117
+ }
118
+
119
+ export interface ProductPage {
120
+ items: any[];
121
+ nextCursor: string | null;
122
+ prevCursor: string | null;
123
+ }
124
+
125
+ // Cursor-paginated product list for a single category. Two calls under the
126
+ // hood — there is no Wix endpoint that does category filter + cursor paging
127
+ // in one shot. The cursor we surface is the productsV3 cursor, so paging is
128
+ // stable as long as the category membership doesn't change mid-session.
129
+ export async function listProductsInCategory(
130
+ categoryId: string,
131
+ opts: { limit: number; cursor?: string },
132
+ ): Promise<ProductPage> {
133
+ try {
134
+ const ids = await getCategoryItemIds(categoryId);
135
+ if (ids.length === 0) {
136
+ return { items: [], nextCursor: null, prevCursor: null };
137
+ }
138
+ let builder = productsV3
139
+ .queryProducts({ fields: ["CURRENCY"] })
140
+ .in("_id", ids)
141
+ .limit(opts.limit);
142
+ if (opts.cursor) builder = builder.skipTo(opts.cursor);
143
+ const result = await builder.find();
144
+ return {
145
+ items: result.items ?? [],
146
+ nextCursor: result.cursors?.next ?? null,
147
+ prevCursor: result.cursors?.prev ?? null,
148
+ };
149
+ } catch (err) {
150
+ console.error(`[categories] listProductsInCategory(${categoryId}) failed:`, err);
151
+ return { items: [], nextCursor: null, prevCursor: null };
152
+ }
153
+ }
154
+
155
+ export { STORES_APP_ID };
@@ -0,0 +1,130 @@
1
+ ---
2
+ // /category/[slug] — server-side filtered, cursor-paginated category
3
+ // landing. Renders the same product grid as /products, but constrained to
4
+ // one category, with breadcrumbs and a category-specific page header.
5
+ //
6
+ // The CategoryRail is shared with /products via transition:persist, so
7
+ // switching categories swaps only the grid and header.
8
+ import Layout from "../../layouts/Layout.astro";
9
+ import ProductCard from "../../components/ProductCard.astro";
10
+ import CategoryRail from "../../components/CategoryRail.astro";
11
+ import {
12
+ getCategoryBySlug,
13
+ listProductsInCategory,
14
+ listStoreCategories,
15
+ } from "../../utils/categories";
16
+
17
+ const PAGE_SIZE = 24;
18
+ const { slug } = Astro.params;
19
+
20
+ if (!slug) {
21
+ return Astro.redirect("/products");
22
+ }
23
+
24
+ const category = await getCategoryBySlug(slug);
25
+ if (!category) {
26
+ return new Response("Category not found", { status: 404 });
27
+ }
28
+
29
+ const cursor = Astro.url.searchParams.get("cursor") ?? undefined;
30
+ const page = await listProductsInCategory(category.id, {
31
+ limit: PAGE_SIZE,
32
+ cursor,
33
+ });
34
+
35
+ // Drop the Wix Gift Card app's auto-mirror products (DIGITAL stand-ins
36
+ // tagged "Gift Card") — real purchases happen via /gift-cards.
37
+ const productList = page.items.filter((p: any) => p.ribbon?.name !== "Gift Card");
38
+
39
+ const allCategories = await listStoreCategories();
40
+
41
+ const impressionContents = productList.map((p, index) => ({
42
+ id: p._id,
43
+ name: p.name,
44
+ price: Number(p.actualPriceRange?.minValue?.amount ?? 0),
45
+ currency: p.actualPriceRange?.minValue?.currency ?? undefined,
46
+ position: index + 1,
47
+ }));
48
+
49
+ function pageHref(c: string | null): string | null {
50
+ if (!c) return null;
51
+ const params = new URLSearchParams();
52
+ params.set("cursor", c);
53
+ return `/category/${category!.slug}?${params.toString()}`;
54
+ }
55
+ ---
56
+
57
+ <Layout
58
+ title={`${category.name}`}
59
+ description={category.description ?? `Browse our ${category.name.toLowerCase()} collection.`}
60
+ >
61
+ <section class="page-header">
62
+ <div class="container-page">
63
+ <nav class="breadcrumbs" aria-label="Breadcrumb">
64
+ <a href="/" data-astro-prefetch="hover">Home</a>
65
+ <span aria-hidden="true">&middot;</span>
66
+ <a href="/products" data-astro-prefetch="hover">Shop</a>
67
+ <span aria-hidden="true">&middot;</span>
68
+ <span aria-current="page">{category.name}</span>
69
+ </nav>
70
+ <h1 class="page-header-title">{category.name}</h1>
71
+ {category.description && (
72
+ <p class="page-header-lede">{category.description}</p>
73
+ )}
74
+ </div>
75
+ {category.imageUrl && (
76
+ <div class="page-header-image" aria-hidden="true">
77
+ <img
78
+ src={category.imageUrl}
79
+ alt=""
80
+ loading="lazy"
81
+ decoding="async"
82
+ />
83
+ </div>
84
+ )}
85
+ </section>
86
+
87
+ <CategoryRail
88
+ categories={allCategories}
89
+ activeSlug={category.slug}
90
+ prevHref={pageHref(page.prevCursor)}
91
+ nextHref={pageHref(page.nextCursor)}
92
+ />
93
+
94
+ <section class="section-pad">
95
+ <div class="container-page">
96
+ {productList.length === 0 ? (
97
+ <p class="products-empty">
98
+ Nothing in {category.name} just now &mdash; please check back shortly.
99
+ </p>
100
+ ) : (
101
+ <div class="product-grid">
102
+ {productList.map((product, idx) => (
103
+ <ProductCard product={product} index={idx} />
104
+ ))}
105
+ </div>
106
+ )}
107
+ </div>
108
+ </section>
109
+ </Layout>
110
+
111
+ <script
112
+ type="application/json"
113
+ id="analytics-impressions"
114
+ is:inline
115
+ set:html={JSON.stringify(impressionContents)}
116
+ />
117
+ <script>
118
+ import { trackEvent } from "../../utils/analytics";
119
+ function fireImpressions() {
120
+ const el = document.getElementById("analytics-impressions");
121
+ if (!el?.textContent) return;
122
+ trackEvent("AddProductImpression", {
123
+ contents: JSON.parse(el.textContent),
124
+ origin: "Category Page",
125
+ });
126
+ }
127
+ // astro:page-load fires on initial load AND after every <ClientRouter />
128
+ // navigation, so a single listener covers both fresh hits and swaps.
129
+ document.addEventListener("astro:page-load", fireImpressions);
130
+ </script>
@@ -0,0 +1,295 @@
1
+ /* src/styles/components-stores.css
2
+ *
3
+ * Stores-pack component CSS. Owned by the stores Phase 3 components agent.
4
+ *
5
+ * This file is the SINGLE source of truth for stores-component classes.
6
+ * The designer's global.css does NOT publish .product-card, .product-grid,
7
+ * .product-card-media, .product-card-ribbon, .product-card-index, or the
8
+ * .offer-callout family — those all live here. See references/shared/STYLING.md
9
+ * § "Component-specific CSS is owned by the component, not the designer".
10
+ *
11
+ * Class groups in this file:
12
+ * - .product-card / .product-grid / .product-card-* → product cards (ProductCard.astro), product listings (products/index.astro, category/[slug].astro), home grid (index.astro)
13
+ * - .offer-callout family → discount callout above ProductPurchase on /products/[slug]
14
+ * - .option-* / .stock-status / .modifier-text → ProductPurchase.tsx (variant pills, stock label, free-text modifier)
15
+ * - .quantity-* → ProductPurchase.tsx + CartView.tsx (quantity stepper)
16
+ * - .back-in-stock-* → BackInStockForm.tsx (out-of-stock subscribe)
17
+ *
18
+ * Uses the palette + typography custom properties declared in global.css.
19
+ */
20
+
21
+ /* =========================================================================
22
+ Product card + grid — owned here, NOT in global.css.
23
+ The .product-card uses overflow: hidden + border-radius, so inner padding
24
+ is the responsibility of THIS file (whoever sets the radius owns the
25
+ inset). Don't ship a stub rule and rely on the template to add padding
26
+ utilities — that's the leak this file was created to prevent.
27
+ ========================================================================= */
28
+ .product-grid {
29
+ display: grid;
30
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
31
+ gap: var(--spacing-xl);
32
+ transition: opacity 200ms ease;
33
+ }
34
+ body[data-navigating="true"] .product-grid {
35
+ opacity: 0.55;
36
+ pointer-events: none;
37
+ }
38
+
39
+ .product-card {
40
+ position: relative;
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--spacing-md);
44
+ padding-bottom: var(--spacing-md);
45
+ background: var(--color-paper, var(--color-cream));
46
+ border-radius: var(--radius-xl);
47
+ overflow: hidden;
48
+ text-decoration: none;
49
+ color: inherit;
50
+ transition: transform 280ms ease, box-shadow 280ms ease;
51
+ box-shadow: var(--shadow-card, 0 4px 16px -8px rgba(0, 0, 0, 0.15));
52
+ }
53
+ .product-card:hover {
54
+ transform: translateY(-4px);
55
+ }
56
+ .product-card:hover .product-card-media > img {
57
+ transform: scale(1.04);
58
+ }
59
+
60
+ .product-card-media {
61
+ position: relative;
62
+ margin: var(--spacing-md);
63
+ aspect-ratio: 4 / 5;
64
+ background: var(--color-paper-warm, var(--color-cream));
65
+ border-radius: var(--radius-md);
66
+ overflow: hidden;
67
+ }
68
+ .product-card-media > img {
69
+ width: 100%;
70
+ height: 100%;
71
+ object-fit: cover;
72
+ transition: transform 600ms ease;
73
+ }
74
+
75
+ .product-card-body {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: var(--spacing-xs);
79
+ padding-inline: var(--spacing-md);
80
+ }
81
+ .product-card-name {
82
+ margin: 0;
83
+ font-family: var(--font-display);
84
+ font-size: 1.125rem;
85
+ line-height: 1.25;
86
+ color: var(--color-ink);
87
+ }
88
+ .product-card-price {
89
+ margin: 0;
90
+ font-family: var(--font-body);
91
+ font-size: 0.875rem;
92
+ letter-spacing: 0.02em;
93
+ color: var(--color-mute);
94
+ }
95
+
96
+ .product-card-ribbon {
97
+ position: absolute;
98
+ top: var(--spacing-md);
99
+ left: var(--spacing-md);
100
+ padding: 0.375rem 0.625rem;
101
+ background: var(--color-accent);
102
+ color: var(--color-ink);
103
+ font-family: var(--font-body);
104
+ font-size: 0.6875rem;
105
+ font-weight: 600;
106
+ letter-spacing: 0.08em;
107
+ text-transform: uppercase;
108
+ border-radius: var(--radius-sm);
109
+ z-index: 2;
110
+ }
111
+ .product-card-index {
112
+ position: absolute;
113
+ top: var(--spacing-md);
114
+ right: var(--spacing-md);
115
+ padding: 0.375rem 0.5rem;
116
+ background: rgba(255, 255, 255, 0.85);
117
+ color: var(--color-ink);
118
+ font-family: var(--font-display);
119
+ font-size: 0.75rem;
120
+ font-weight: 700;
121
+ letter-spacing: 0.04em;
122
+ border-radius: var(--radius-pill, 9999px);
123
+ z-index: 2;
124
+ }
125
+
126
+ /* =========================================================================
127
+ Offer callout — bordered panel above ProductPurchase on /products/[slug].
128
+ Compound multi-element pattern. The dashboard-authored offer string can
129
+ contain `\n` line breaks; -detail must preserve them via white-space.
130
+ ========================================================================= */
131
+ .offer-callout {
132
+ background: var(--color-paper-warm, var(--color-cream));
133
+ border: 1px solid var(--color-rule);
134
+ border-left: 2px solid var(--color-accent);
135
+ padding: var(--spacing-lg);
136
+ border-radius: var(--radius-md);
137
+ display: flex;
138
+ flex-direction: column;
139
+ gap: var(--spacing-md);
140
+ }
141
+ .offer-callout-item {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: var(--spacing-xs);
145
+ }
146
+ .offer-callout-item + .offer-callout-item {
147
+ padding-top: var(--spacing-md);
148
+ border-top: 1px solid var(--color-rule);
149
+ }
150
+ .offer-callout-badge {
151
+ align-self: flex-start;
152
+ padding: 0.25rem 0.5rem;
153
+ background: var(--color-ink);
154
+ color: var(--color-paper);
155
+ font-family: var(--font-body);
156
+ font-size: 0.625rem;
157
+ font-weight: 700;
158
+ letter-spacing: 0.1em;
159
+ text-transform: uppercase;
160
+ border-radius: var(--radius-sm);
161
+ }
162
+ .offer-callout-name {
163
+ font-family: var(--font-display);
164
+ font-size: 1rem;
165
+ color: var(--color-ink);
166
+ margin: 0;
167
+ }
168
+ .offer-callout-detail {
169
+ font-family: var(--font-body);
170
+ font-size: 0.875rem;
171
+ color: var(--color-ink-soft, var(--color-mute));
172
+ white-space: pre-line;
173
+ margin: 0;
174
+ }
175
+ .offer-callout-foot {
176
+ font-family: var(--font-body);
177
+ font-size: 0.75rem;
178
+ color: var(--color-mute);
179
+ text-transform: uppercase;
180
+ letter-spacing: 0.06em;
181
+ margin: 0;
182
+ }
183
+
184
+
185
+ /* --- Variant / option pills (ProductPurchase.tsx) --- */
186
+ .option-group {
187
+ @apply flex flex-col gap-sm mb-lg;
188
+ }
189
+ .option-label {
190
+ @apply font-heading text-sm uppercase tracking-wider text-bark/70;
191
+ }
192
+ .option-choices {
193
+ @apply flex flex-wrap gap-sm;
194
+ }
195
+ .option-pill {
196
+ @apply px-md py-sm border border-bark bg-transparent text-bark font-body rounded-full cursor-pointer transition-colors;
197
+ }
198
+ .option-pill.selected {
199
+ @apply bg-bark text-cream;
200
+ }
201
+ .option-pill:disabled {
202
+ @apply opacity-40 cursor-not-allowed;
203
+ }
204
+
205
+ /* --- Quantity stepper (ProductPurchase.tsx, CartView.tsx qty editing) --- */
206
+ .quantity-selector {
207
+ @apply inline-flex items-center border border-bark rounded-md overflow-hidden;
208
+ }
209
+ .quantity-btn {
210
+ @apply px-3 py-2 bg-transparent border-none cursor-pointer text-bark font-body;
211
+ }
212
+ .quantity-btn:disabled {
213
+ @apply opacity-40 cursor-not-allowed;
214
+ }
215
+ .quantity-value {
216
+ @apply px-md py-sm tabular-nums;
217
+ }
218
+
219
+ /* --- Modifier free-text input (ProductPurchase.tsx, FREE_TEXT render type) --- */
220
+ .modifier-text {
221
+ @apply w-full px-sm py-sm font-body text-sm border border-rule rounded bg-parchment text-ink;
222
+ resize: vertical;
223
+ }
224
+ .modifier-text:focus {
225
+ @apply outline-none border-ink bg-cream;
226
+ }
227
+
228
+ /* --- Stock status (ProductPurchase.tsx) --- */
229
+ .stock-status {
230
+ @apply text-sm text-bark/70 italic;
231
+ }
232
+
233
+ /* --- Back-in-stock subscribe form (BackInStockForm.tsx) ---
234
+ * Rendered inside .purchase-area in three OOS branches when
235
+ * backInStockEnabled=true (probe in [slug].astro). */
236
+ .back-in-stock-form {
237
+ @apply flex flex-col gap-md mt-md p-lg border border-rule rounded;
238
+ background-color: var(--color-cream);
239
+ }
240
+ .back-in-stock-headline {
241
+ @apply flex flex-col gap-sm;
242
+ }
243
+ .back-in-stock-eyebrow {
244
+ @apply font-heading text-xs uppercase tracking-wider text-bark/70;
245
+ }
246
+ .back-in-stock-lede {
247
+ @apply font-body text-sm text-ink;
248
+ line-height: 1.5;
249
+ }
250
+ .back-in-stock-label {
251
+ @apply font-heading text-xs uppercase tracking-wider text-bark/70;
252
+ }
253
+ .back-in-stock-row {
254
+ @apply flex flex-row flex-wrap gap-sm;
255
+ }
256
+ .back-in-stock-input {
257
+ @apply flex-1 px-md py-sm font-body text-sm border border-rule rounded bg-parchment text-ink;
258
+ min-width: 0;
259
+ transition: border-color 0.15s ease;
260
+ }
261
+ .back-in-stock-input:focus {
262
+ @apply outline-none border-ink;
263
+ }
264
+ .back-in-stock-submit {
265
+ @apply px-lg py-sm font-heading text-xs uppercase tracking-wider bg-bark text-cream rounded cursor-pointer transition-colors;
266
+ border: 1px solid var(--color-bark);
267
+ }
268
+ .back-in-stock-submit:hover:not(:disabled) {
269
+ @apply bg-ink;
270
+ }
271
+ .back-in-stock-submit:disabled {
272
+ @apply opacity-50 cursor-not-allowed;
273
+ }
274
+ .back-in-stock-error {
275
+ @apply font-body text-sm;
276
+ color: var(--color-rust, var(--color-ink));
277
+ }
278
+ .back-in-stock-success {
279
+ @apply mt-md p-lg border border-rule rounded;
280
+ background-color: var(--color-cream);
281
+ }
282
+ .back-in-stock-success-headline {
283
+ @apply font-heading text-base text-ink;
284
+ margin: 0 0 0.5rem 0;
285
+ }
286
+ .back-in-stock-success-detail {
287
+ @apply font-body text-sm text-ink;
288
+ line-height: 1.55;
289
+ margin: 0;
290
+ }
291
+ .stock-status.back-in-stock-alt {
292
+ @apply mt-sm font-body text-sm italic text-bark/70;
293
+ }
294
+
295
+ /* Cart CSS (.cart-item-*, .qty-*) lives in components-ecom.css — not in this file. */