@wix/agent-skills 1.19.0 → 1.19.2

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 (80) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +33 -31
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +6 -10
  4. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +17 -8
  5. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +53 -131
  6. package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +1 -2
  7. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +88 -73
  8. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +46 -22
  9. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +20 -6
  10. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +9 -0
  11. package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +71 -31
  12. package/skills/wix-app/references/editor-react-component/PARTS.md +5 -2
  13. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +4 -5
  14. package/skills/wix-app/scripts/pkg-root.cjs +73 -0
  15. package/skills/wix-app/scripts/scan-a11y-code.cjs +310 -40
  16. package/skills/wix-app/scripts/scan-a11y-eslint.cjs +118 -24
  17. package/skills/wix-app/scripts/scan-a11y-render.cjs +551 -0
  18. package/skills/wix-app/scripts/scan-a11y-review.cjs +373 -0
  19. package/skills/wix-base44-connector/SKILL.md +67 -43
  20. package/skills/wix-base44-connector/scripts/utils.cjs +39 -3
  21. package/skills/wix-docs/SKILL.md +47 -20
  22. package/skills/wix-headless/SKILL.md +1 -0
  23. package/skills/wix-headless/entry/bootstrap.mjs +106 -50
  24. package/skills/wix-headless/entry/skill.md +5 -3
  25. package/skills/wix-headless/references/CAPABILITIES.md +7 -2
  26. package/skills/wix-headless/references/SDK_HANDOFF.md +2 -1
  27. package/skills/wix-headless/references/inline-recipes/experience-store.md +48 -0
  28. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +350 -114
  29. package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +1 -1
  30. package/skills/wix-headless/references/managed/AUTHENTICATION.md +25 -5
  31. package/skills/wix-headless/references/managed/CREATE.md +5 -0
  32. package/skills/wix-headless-fast/SKILL.md +25 -7
  33. package/skills/wix-headless-fast/entry/skill.md +23 -14
  34. package/skills/wix-headless-fast/install/fast-path.mjs +48 -5
  35. package/skills/wix-headless-fast/install/pin-agents-md.mjs +57 -0
  36. package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +3 -3
  37. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +204 -35
  38. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +146 -36
  39. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +8 -2
  40. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +95 -20
  41. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +21 -2
  42. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +104 -18
  43. package/skills/wix-manage/SKILL.md +16 -10
  44. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +4 -1
  45. package/skills/wix-manage/references/bookings/create-appointment-service.md +3 -1
  46. package/skills/wix-manage/references/bookings/create-class-service.md +3 -1
  47. package/skills/wix-manage/references/bookings/create-course-service.md +4 -2
  48. package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +68 -3
  49. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-seasonal-promotion.md +1 -1
  50. package/skills/wix-manage/references/google-ads/billing-and-payment.md +3 -3
  51. package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +8 -8
  52. package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +4 -4
  53. package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +1 -1
  54. package/skills/wix-manage/references/google-ads/install-and-create-account.md +9 -9
  55. package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +108 -14
  56. package/skills/wix-manage/references/google-ads/query-campaign-analytics.md +7 -7
  57. package/skills/wix-manage/references/seo/manage-content-plan.md +182 -136
  58. package/skills/wix-manage/references/seo/manage-google-search-console.md +272 -0
  59. package/skills/wix-manage/references/sites/upload-static-site.md +283 -0
  60. package/skills/wix-manage/references/stores/change-store-currency.md +1 -1
  61. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +3 -1
  62. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +3 -1
  63. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
  64. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +69 -27
  65. package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +32 -14
  66. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +14 -3
  67. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +41 -35
  68. package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +77 -30
  69. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +31 -2
  70. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +31 -6
  71. package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +0 -80
  72. package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +0 -140
  73. package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +0 -112
  74. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +0 -100
  75. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +0 -130
  76. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +0 -48
  77. package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +0 -66
  78. package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +0 -103
  79. package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +0 -56
  80. package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +0 -23
@@ -40,17 +40,24 @@ const STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
40
40
  * Throws on out-of-stock so the buyer can't reach checkout with an unbuyable line.
41
41
  * Full catalogReference reference: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md
42
42
  *
43
+ * A pre-order variant (inventoryStatus.preorderEnabled, out of stock) is added with `preorder: true`;
44
+ * a recurring subscription plan with its `subscriptionOptionId` (omit it for a one-time purchase).
45
+ * The two may coexist.
46
+ *
43
47
  * @param {string} catalogItemId Product GUID (product.id).
44
48
  * @param {string} [variantId] variantsInfo.variants[].id — required for products with variants.
45
49
  * @param {number} [quantity]
46
- * @param {{ modifierChoices?: Record<string,string>, customTextFields?: Record<string,string> }} [extras]
50
+ * @param {{ modifierChoices?: Record<string,string>, customTextFields?: Record<string,string>,
51
+ * subscriptionOptionId?: string, preorder?: boolean }} [extras]
47
52
  * @returns {Promise<object>} Updated cart.
48
53
  */
49
- export async function addToCart(catalogItemId, variantId, quantity = 1, { modifierChoices, customTextFields } = {}) {
54
+ export async function addToCart(catalogItemId, variantId, quantity = 1, { modifierChoices, customTextFields, subscriptionOptionId, preorder } = {}) {
50
55
  const catalogReferenceOptions = {};
51
56
  if (variantId) catalogReferenceOptions.variantId = variantId;
52
57
  if (modifierChoices && Object.keys(modifierChoices).length) catalogReferenceOptions.options = modifierChoices;
53
58
  if (customTextFields && Object.keys(customTextFields).length) catalogReferenceOptions.customTextFields = customTextFields;
59
+ if (subscriptionOptionId) catalogReferenceOptions.subscriptionOptionId = subscriptionOptionId;
60
+ if (preorder) catalogReferenceOptions.preOrderRequested = true;
54
61
 
55
62
  const catalogReference = { appId: STORES_APP_ID, catalogItemId };
56
63
  if (Object.keys(catalogReferenceOptions).length) catalogReference.options = catalogReferenceOptions;
@@ -92,8 +99,30 @@ export async function getCurrentCart() {
92
99
  }
93
100
  }
94
101
 
102
+ /**
103
+ * Estimate the current cart's totals — the ONLY source of the after-discount subtotal and the
104
+ * cart-level discount. Line prices already carry item-level discounts; delivery and tax resolve at
105
+ * checkout unless asked for, so never invent them. Returns null when there is no cart or the
106
+ * estimate fails (a display nicety — the cart itself stays valid).
107
+ * https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/estimate-current-cart.md
108
+ * @returns {Promise<{ priceSummary: { subtotal: object, discount: object, total: object } }|null>}
109
+ * priceSummary amounts are ConvertedMoney { amount, convertedAmount } — format like line prices.
110
+ */
111
+ export async function estimateCurrentCart() {
112
+ try {
113
+ const res = await wixApiRequest("/ecom/v2/carts/current/estimate", { method: "POST", body: {} });
114
+ return res?.summary ?? null;
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+
95
120
  /**
96
121
  * Start the hosted checkout for the current cart and return its URL.
122
+ * This is the shopper's CURRENT cart. A "Buy now" that skips the cart needs a standalone cart
123
+ * (POST /ecom/v2/carts) handed to the redirect session — it must never append to or replace the
124
+ * current cart. Not shipped here; verify the redirect session accepts a standalone cart id under
125
+ * the visitor token before adding it.
97
126
  * In Cart V2 the cart id IS the checkout id — there is no separate checkout-creation call; the
98
127
  * redirect session is created straight from the current cart's id.
99
128
  * Throws on empty cart, unavailable lines, or a missing redirect URL.
@@ -13,8 +13,13 @@ import { wixApiRequest } from "./wix-client.js";
13
13
  * actualPriceRange.minValue.formattedAmount {string} — lowest price with currency symbol,
14
14
  * actualPriceRange.maxValue.formattedAmount {string} — highest price with currency symbol,
15
15
  * compareAtPriceRange.minValue.formattedAmount {string} — strikethrough price (present when on sale),
16
- * actualPriceRange/compareAtPriceRange .minValue.amount {string} — the same figures unformatted
17
- * ("129.00"). Use these for arithmetic: a percent-off badge can't be computed from "€129.00",
16
+ * actualPriceRange/compareAtPriceRange .minValue.amount {string} — the same figures unformatted.
17
+ * Never compute a percent-off or "you save" claim from them: a range's minimum says nothing about
18
+ * the variant the buyer picks. The price the buyer pays comes from sellingPrice() below.
19
+ * variantSummary.minPriceVariant {object} — the lowest-priced visible variant (MIN_PRICE_VARIANT):
20
+ * { id, price: { actualPrice, compareAtPrice?, priceAfterDiscount? } } — a card's price, and the
21
+ * variantId for a direct add on a product with no options,
22
+ * discountInfo.discountRuleNames {string[]} — automatic-discount names (DISCOUNT_INFO), product level,
18
23
  * inventory.availabilityStatus {string} — "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK",
19
24
  * inventory.preorderStatus {string} — "ENABLED"|"DISABLED". ENABLED + OUT_OF_STOCK is a pre-order,
20
25
  * which is buyable — label it "Pre-order" rather than "Sold out",
@@ -23,8 +28,9 @@ import { wixApiRequest } from "./wix-client.js";
23
28
  * getProductBySlug call per product,
24
29
  * ribbon {object} — { id, name } merchant-set badge: "New", "Sale", "Best Seller". This is the
25
30
  * catalogue's own label, so prefer it over anything derived — a "new" badge computed from
26
- * createdDate flags every product at once right after seeding,
27
- * additionalRibbons {array} — further { id, name } badges,
31
+ * createdDate flags every product at once right after seeding. A ribbon is a LABEL: a "Sale"
32
+ * ribbon is not proof of a lower price, and a compareAtPrice does not earn a ribbon,
33
+ * additionalRibbons {array} — up to four further { id, name } badges. Render ALL of them,
28
34
  * createdDate / updatedDate {string} — ISO timestamps,
29
35
  * variantSummary.variantCount {number} — how many variants exist, without fetching them,
30
36
  * options {array} — product options e.g. Size, Color:
@@ -45,9 +51,11 @@ import { wixApiRequest } from "./wix-client.js";
45
51
  * dangerouslySetInnerHTML; strip tags only for plain-text contexts (meta description, teaser),
46
52
  * variantsInfo.variants {array} — returned only by getProductBySlug:
47
53
  * [{ id, visible, choices [{ optionChoiceIds: { optionId, choiceId } }],
48
- * price: { actualPrice, compareAtPrice }, media, inventoryStatus: { inStock } }]
54
+ * price: { actualPrice, compareAtPrice?, priceAfterDiscount? }, media,
55
+ * inventoryStatus: { inStock, preorderEnabled } }]
49
56
  * To resolve a buyer's option selections to a variantId: find the variant whose choices
50
57
  * match all selected { optionId, choiceId } pairs, then pass variant.id to addToCart.
58
+ * A variant is buyable when inStock OR preorderEnabled (a preorder add carries preOrderRequested).
51
59
  *
52
60
  * Category: { id, name, slug, visible, description, image, itemCounter, parentCategory.id }
53
61
  * NB: queryCategories includes the auto-created system category { slug: "all-products" } —
@@ -55,6 +63,20 @@ import { wixApiRequest } from "./wix-client.js";
55
63
  * Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories
56
64
  */
57
65
 
66
+ /**
67
+ * The price the buyer pays, and the price to strike — exact precedence, shared by cards and the PDP.
68
+ * An automatic discount (priceAfterDiscount, present only when DISCOUNT_INFO is requested) wins and
69
+ * strikes the regular actualPrice; otherwise actualPrice with the merchant's compareAtPrice as the
70
+ * "was". `!== undefined` on purpose: a discounted price of 0 is a real price.
71
+ * @param {{ actualPrice?: object, compareAtPrice?: object, priceAfterDiscount?: object }} [price]
72
+ * variant.price, or variantSummary.minPriceVariant.price. Each is { amount, formattedAmount }.
73
+ * @returns {{ current: object|undefined, original: object|undefined }} render .formattedAmount
74
+ */
75
+ export function sellingPrice(price) {
76
+ if (price?.priceAfterDiscount !== undefined) return { current: price.priceAfterDiscount, original: price.actualPrice };
77
+ return { current: price?.actualPrice, original: price?.compareAtPrice };
78
+ }
79
+
58
80
  // Search Products supports server-side sort/filter before cursor paging:
59
81
  // https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
60
82
  export const CATALOG_SORTS = {
@@ -108,7 +130,9 @@ export async function searchProducts({ limit = 100, cursor, categoryId, sort = "
108
130
  }
109
131
  const res = await wixApiRequest("/stores/v3/products/search", {
110
132
  method: "POST",
111
- body: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"], search: query },
133
+ // MIN_PRICE_VARIANT + DISCOUNT_INFO → variantSummary.minPriceVariant with its discounted price:
134
+ // the card's price and the direct-add variantId, without a per-product fetch.
135
+ body: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO", "MIN_PRICE_VARIANT", "DISCOUNT_INFO"], search: query },
112
136
  });
113
137
  return { products: res?.products ?? [], nextCursor: res?.pagingMetadata?.cursors?.next ?? null };
114
138
  }
@@ -137,6 +161,7 @@ export async function getProductBySlug(slug) {
137
161
  "MEDIA_ITEMS_INFO",
138
162
  "PRODUCT_CHOICES_MEDIA_REFERENCES",
139
163
  "VARIANT_OPTION_CHOICE_NAMES",
164
+ "DISCOUNT_INFO", // variant price.priceAfterDiscount + discountInfo.discountRuleNames
140
165
  ],
141
166
  },
142
167
  });
@@ -1,80 +0,0 @@
1
- # Collection Toolkit — what to reach for, per user need
2
-
3
- [UX_SUCCESS_MODEL.md](UX_SUCCESS_MODEL.md) says what a dashboard must do for the person using it. This file says which component does it, so the stage list resolves to real names instead of a hand-rolled approximation.
4
-
5
- Everything below is a real name in the installed `@wix/patterns`. Confirm the props before writing JSX by reading its doc from `dist/docs/index.json` — and if a name is missing from `dist/dts-bundle/index.json`, the installed version is older than this file; work from that inventory, not from memory.
6
-
7
- > **The two requirements below are not suggestions.** A page that renders a filtered table and nothing else is the single most common failure of a generated dashboard: it answers "what are all the records" and nothing about how many, which one needs attention, or why something happened. Measured runs produce exactly that page unless the requirement is stated.
8
- >
9
- > 1. **A dashboard that reports on records shows aggregate numbers**, not only rows.
10
- > 2. **A dashboard whose rows represent real business records lets the user open one**, unless the prompt is explicitly a report or an export.
11
-
12
- ## Understand — the aggregate
13
-
14
- | Need | Component |
15
- | --- | --- |
16
- | Totals, counts, status breakdown above the table | `SummaryBar` |
17
- | Which subset the numbers describe | wire each metric to the collection's filter state so the count follows the filters |
18
-
19
- `SummaryBar` sits inside the page shell, above the collection. Compute the values from the same query the table uses, or a count query alongside it — a metric that disagrees with the visible rows is worse than no metric.
20
-
21
- ## Focus — narrowing
22
-
23
- | Need | Component |
24
- | --- | --- |
25
- | Free-text search | `CollectionSearch` |
26
- | The filter bar itself | `CollectionToolbarFilters` |
27
- | Pick several values from a known list | `MultiSelectCheckboxFilter`, `MultiInlineCheckboxFilter`, `MultiAutoInlineCheckboxFilter` |
28
- | Pick one value | `SingleSelectFilter`, `RadioGroupFilter`, `TabsFilter` |
29
- | Values fetched from an API, not a fixed list | `MultiSelectCollectionFilter` + `useFilterCollection` |
30
- | Type-ahead over many values | `AutoCompleteFilter` |
31
- | Date or number ranges | `DateRangeFilter`, `NumberRangeFilter` |
32
- | Comparison operators (greater than, contains) | `OperatorFilterPicker` + `operatorFilter` |
33
- | The filter's state object | `idNameArrayFilter`, `stringFilter`, `stringsArrayFilter`, `arrayFilter`, `dateRangeFilter`, `customFilter` |
34
- | A fixed in-memory option list for a filter | `useStaticListFilterCollection` |
35
- | Sorting | `Sortable Columns`, `MultiLevelSorting` |
36
-
37
- **Read the index once, then go straight to files.** `dist/dts-bundle/index.json` answers every name in this table in a single read — resolve it once per session and keep it, rather than re-reading it per lookup. Each entry names the exact file to open next.
38
-
39
- **A factory or hook's doc is often empty where its signature should be** — the props table is generated for components, so `idNameArrayFilter`'s doc shows an `## API` heading with nothing under it. Its bundle has the signature: `<T extends { id: string; name: string }>(params?) => ArrayFilterState<T>`. That holds for every `use…` hook and every `…Filter` factory in the table above, and it is the difference between knowing a name and being able to call it — so for these, read the bundle the index names, not the doc.
40
-
41
- **A filter must narrow the result.** Declare it in the collection hook's `filters` map and read it inside `fetchData`, so the value reaches the query. Filter UI that renders but never changes the rows is a defect that looks like a feature — and it is the failure mode these components exist to prevent.
42
-
43
- ## Investigate — opening one record
44
-
45
- | Surface | Use when | Built from |
46
- | --- | --- | --- |
47
- | **Side panel** | Inspect or lightly edit one record while keeping the filtered list on screen. The V1 default for review dashboards. | WDS `SidePanel` — patterns has no side panel |
48
- | **Entity page** | Multi-section detail, editing, history, or a link someone can share. | `EntityPage` + `useEntityPage`, reached with `usePatternsNavigate().navigateToEntityPage`, form state from `@wix/patterns/form`. The call itself: [ENTITY_PAGE_TOOLKIT.md](ENTITY_PAGE_TOOLKIT.md) |
49
- | **Expanded row** | A couple of extra fields, no separate workspace needed. | The collection's own row expansion |
50
- | **Picker / bulk confirm** | Choosing records, or confirming an action on many. | `PickerModal` + `usePickerModal`, `bulkActionModal` |
51
-
52
- A dialog that creates, updates or displays one listed record is **not** a dashboard modal — a create / "add new" form included, since it writes the record. See [DASHBOARD_MODAL.md](../DASHBOARD_MODAL.md); for the create route itself — registering it, and the four params that differ from the edit call — [ENTITY_PAGE_TOOLKIT.md § Create route](ENTITY_PAGE_TOOLKIT.md#create-route). A row the user cannot open is the second most common failure after the missing aggregate.
53
-
54
- **Form state on an entity page** comes from `@wix/patterns/form` — `useForm` for the form, `useController` for a single field. That subpath re-exports `@wix/bex-core/form`, which wraps `react-hook-form`, so its API is react-hook-form's and only a handful of its names appear in the patterns docs: `FieldValues`, `ControllerProps` and most of the rest are documented by react-hook-form, not here. `Read <pkgRoot>/dist/dts-bundle/exports/form.d.ts` to see what the subpath actually gives you.
55
-
56
- ## Act and confirm
57
-
58
- | Need | Component |
59
- | --- | --- |
60
- | Page header actions | `PrimaryActions`, `SecondaryActions`, `More Actions` — passed to the page header's `primaryAction`, `secondaryActions` and `moreActions` slots |
61
- | Table toolbar button | `PrimaryActionButton` — the table's own `primaryActionButton` prop |
62
- | Row actions | `deleteSecondaryAction`, in the row's `actionCell` |
63
- | Acting on a multi-row selection | `MultiBulkActionToolbar`, `bulkActionModal` |
64
- | Immediate feedback, then reconcile | `useOptimisticActions(state.collection)` ✅ — not `useOptimisticActions(state)` ❌: `useTableCollection()` returns `TableState<T, F>`, and the hook takes the `CollectionState<T, F>` that state exposes as `state.collection`. Returns `CollectionOptimisticActions` |
65
- | A banner above the table | `TableTopNotification` |
66
-
67
- ## The states that are not the happy path
68
-
69
- | State | Component |
70
- | --- | --- |
71
- | Collection is genuinely empty | `CollectionEmptyState` |
72
- | Filters or search matched nothing | `CollectionNoResultsState` |
73
- | Feature needs a paid plan | `CollectionPremiumEmptyState` |
74
- | Load failed | the collection state's error handling — give the user a way to retry |
75
-
76
- Empty and no-results are different messages: one means "add your first record", the other means "loosen the filters". Shipping only the first makes a working filter look broken.
77
-
78
- ## Export
79
-
80
- `ExportButton` (its doc is `ExportTo`) for CSV and similar. Reach for it when the prompt mentions exporting, downloading, or sending records elsewhere.
@@ -1,140 +0,0 @@
1
- # Entity Page Toolkit — calling `useEntityPage`
2
-
3
- [COLLECTION_TOOLKIT.md](COLLECTION_TOOLKIT.md) says a row the user cannot open is a defect, and sends you to `EntityPage` + `useEntityPage` to fix it. This file is the call itself.
4
-
5
- ## The call
6
-
7
- ```tsx
8
- import { useEntityPage, EntityPage } from '@wix/patterns';
9
- import { useForm } from '@wix/patterns/form';
10
-
11
- interface Shift {
12
- id?: string;
13
- name?: string;
14
- }
15
-
16
- interface ShiftFormFields {
17
- name?: string;
18
- }
19
-
20
- const form = useForm<ShiftFormFields>({ defaultValues: { name: '' } });
21
-
22
- const state = useEntityPage<Shift, ShiftFormFields>({
23
- fetch: () => api.getShift(shiftId).then((entity) => ({ entity })),
24
- onSave: ({ widgetsFormData }) =>
25
- api
26
- .updateShift(shiftId, { ...form.getValues(), ...widgetsFormData })
27
- .then((updatedEntity) => ({ updatedEntity })),
28
- form,
29
- parentPath: '/',
30
- });
31
- ```
32
-
33
- **`parentPath` is relative to the router's own mount point — the dashboard page's
34
- own root, not the page's `route` value.** A dashboard page scaffolded with
35
- `{"route": "shifts"}` is served at `/dashboard/shifts`, but `PatternsReactRouter`
36
- still sees its own location start fresh at `/` inside that page — so `parentPath`,
37
- every `PatternsReactRoute`'s `path`, and the `path` passed to
38
- `navigateToEntityPage()` / `navigateToCollectionPage()` must never repeat the
39
- page's own `route`/name. `parentPath: '/shifts'` on a page whose `route` is
40
- `"shifts"` is the single most common way this gets copied wrong — it looks like a
41
- reasonable guess (name the parent after the page) and compiles, but the
42
- collection route it points back to is registered at `path="/"`, not
43
- `path="/shifts"`, so `parentPath` must match that: `'/'`. See [Create
44
- route](#create-route) below for the full route registration.
45
-
46
- From **1.465.0** the library's own docs state this rule — `PatternsReactRouter.md`,
47
- `usePatternsNavigate.md` and `useEntityPage.md`'s **Create route**. On an older
48
- install they don't, and their examples *demonstrate* the mistake
49
- (`navigateToEntityPage({ path: '/shifts/new' })`, `navigateToCollectionPage({ path:
50
- '/entities' })`); prefer this rule over what those examples show.
51
-
52
- `EntityPage` and `useEntityPage` are **root** exports. `@wix/patterns/page` holds `CollectionPage` and `WidgetsFormProvider` only, so importing the entity page from there is `TS2305: has no exported member` — the collection page and the entity page do not live in the same place. `Read <pkgRoot>/dist/dts-bundle/exports/page.d.ts` to see what that subpath actually gives you.
53
-
54
- ## Name both generics
55
-
56
- `useEntityPage<T, V extends FieldValues = FieldValues>` takes the entity type **and** the form-values type. `V` does not infer from the `form` argument — it falls back to `FieldValues`, and a `useForm<ShiftFormFields>()` then fails to assign with `Type 'UseFormReturn<ShiftFormFields, …>' is not assignable to type 'UseFormReturn<FieldValues>'`. Naming a single generic only works when the form is untyped.
57
-
58
- `useEntityPage<any, ShiftFormFields>` makes that error go away and takes the entity type with it — `state.entity` becomes `any`, and every field access below it stops being checked. Name the entity type.
59
-
60
- ## `onSave` does not hand you the form values
61
-
62
- `OnSaveParams` has exactly one key. `widgetsFormData` carries what the **widget** fields contributed — extended fields, tags — not what the user typed into your own fields. Those come from the form you already own:
63
-
64
- ```tsx
65
- onSave: ({ widgetsFormData }) => {
66
- const values = form.getValues(); // your fields
67
- const { extendedFields, tags } = widgetsFormData; // widget fields
68
- …
69
- };
70
- ```
71
-
72
- Reaching for a `values`-shaped key on the `onSave` argument is the usual first guess, and there isn't one.
73
-
74
- ## The params are a `Pick`
75
-
76
- `UseEntityPageParams` picks a fixed set off `EntityPageStateParams` — `fetch`, `onSave`, `saveSuccessToast`, `saveErrorToast`, `deleteAction`, `transformEntityToCollectionItem`, `isNewEntity` (create vs edit — [Create route](#create-route)), `form`, `parentPageId`, `parentPath`, `parentReferrer`, `schemaSource`. Anything outside that list is an excess-property error on the object literal. `container` is the common guess and is not one of them: the hook calls `useWixPatternsContainer()` itself.
77
-
78
- Confirm the shape rather than guessing — the hook's doc ends by pointing at the bundle rather than tabulating props, because props tables only exist for components. `Read <pkgRoot>/dist/dts-bundle/index.json`, then `Read` the bundled `.d.ts` for each of `useEntityPage`, `UseEntityPageParams`, and `OnSaveParams` at exactly the `file` path the index gives.
79
-
80
- ## Create route
81
-
82
- The example above is edit-only. "Add new" is an `EntityPage` too, and it is not the same call with the id left out. Four things differ, and three of them fail silently if you guess:
83
-
84
- - The route is its own `PatternsReactRoute type="createEntity"`. One component can serve both entity routes; the route is what tells them apart.
85
- - `navigateToEntityPage({ path })` — omit `entity`. It exists so the header can render a title before the fetch resolves, and a create route has no record to give it. A placeholder is worse than nothing: until `fetch` resolves it *is* `state.entity`.
86
- - `fetch` is required on the create route too, and `{ entity: undefined }` is the create case built into its return type — not a loading state, and there is no `fetch`-less variant.
87
- - `isNewEntity` is the only param that tells the page which route it is on. Omitted, a create page that seeds defaults through `fetch` is announced to the collection as an *update*: the collection changes a row it does not have, the new record never appears in the list, and the only trace is a `Fetched page info for updated entity not found` console error.
88
-
89
- `Read <pkgRoot>/dist/docs/useEntityPage.md` before writing one — its **Create route** section carries the worked component that serves both routes off one `useParams` answer, and the `boolean | (() => boolean)` getter form for `isNewEntity`.
90
-
91
- **If that file has no `Create route` heading, the installed `@wix/patterns` predates 1.464.0.** Upgrade and re-read rather than working around it: on those versions `navigateToEntityPage` types `entity` as required, so omitting it does not compile, and passing the placeholder that satisfies it is the failure the bullet above describes.
92
-
93
- ## Around the call
94
-
95
- | Step | What owns it |
96
- | --- | --- |
97
- | Getting here from the collection page | `usePatternsNavigate()` → `navigateToEntityPage({ path, entity })` |
98
- | Registering the route | `PatternsReactRoute` inside `PatternsReactRouter` |
99
- | Form state and field binding | `useForm` / `useController` from `@wix/patterns/form` — `useController`, never `register` |
100
- | Body layout | `EntityPage.Header`, `.MainContent`, `.AdditionalContent`, `.Card` |
101
- | The fields inside those cards | `@wix/design-system` (`FormField`, `Input`, `Text`) |
102
-
103
- Every `path` in that first row — the route's own `path`, `parentPath`, and the `path` argument to `navigateToEntityPage`/`navigateToCollectionPage` — is relative to the page's own root, never to its `route`/name. See the `parentPath` note under [The call](#the-call) above; it is the same rule everywhere a path appears in this table.
104
-
105
- `@wix/patterns/form` re-exports `@wix/bex-core/form`, which wraps `react-hook-form` — so `form.getValues()`, `form.reset()` and the rest are react-hook-form's API, documented there rather than in the patterns docs.
106
-
107
- ## Binding a field: `useController`, never `register`
108
-
109
- `register` returns props for a plain `<input>`, and WDS components are not one. Two ways it fails:
110
-
111
- `<Input {...register('qty')} />` does not compile — `register` always returns `min`/`max` as `string | number`, whatever options you passed, and WDS types them `number | undefined`. The error names `InputProps`, which reads like a WDS bug and isn't.
112
-
113
- `<Input ref={register('qty').ref} />` **does** compile and is wrong. `Input`'s `ref` is its imperative handle (`Ref<InputImperativeActions>`); `register`'s ref is `(instance: any) => void`, so it accepts that and binds the handle rather than the `<input>`. The field never registers, validation never sees it, and nothing reports it.
114
-
115
- Bind through value/onChange instead:
116
-
117
- ```tsx
118
- import { useController, useForm } from '@wix/patterns/form';
119
- import { FormField, Input } from '@wix/design-system';
120
-
121
- type ProductForm = { name: string; qty: number };
122
-
123
- function NameField({ form }: { form: ReturnType<typeof useForm<ProductForm>> }) {
124
- const { field, fieldState } = useController({
125
- name: 'name',
126
- control: form.control,
127
- rules: { required: 'Name is required' },
128
- });
129
-
130
- return (
131
- <FormField label="Name" required status={fieldState.error ? 'error' : undefined} statusMessage={fieldState.error?.message}>
132
- <Input value={field.value} onChange={field.onChange} onBlur={field.onBlur} />
133
- </FormField>
134
- );
135
- }
136
- ```
137
-
138
- Rules live in `rules`, not on the WDS component. If you need `register` for a DOM concern such as input masking, `Input` takes `inputRef?: React.Ref<HTMLInputElement>` — pass the ref there, never to `ref`.
139
-
140
- A dialog that creates, updates or displays one listed record is **not** a dashboard modal — a create / "add new" form included, since it writes the record. See [DASHBOARD_MODAL.md](../DASHBOARD_MODAL.md).
@@ -1,112 +0,0 @@
1
- # Reading `@wix/patterns` Bundles and Docs
2
-
3
- > **Precondition: an index entry.** Every path below is the `file` (or `bundle`) field of an entry
4
- > in `<pkgRoot>/dist/dts-bundle/index.json` or `<pkgRoot>/dist/docs/index.json`. If you haven't
5
- > read that index this session, you have no path to open — read it before anything else here.
6
- > Getting to it — resolving the package root, the version floor, the docs lookup, and the rule
7
- > against browsing `node_modules` by hand — is in
8
- > [WIX_PATTERNS_DOCS.md](../WIX_PATTERNS_DOCS.md). Nothing here replaces those steps.
9
-
10
- ## Read the index once, then open its files in one call
11
-
12
- One index read covers the whole page. So name every symbol you plan to write — components, hooks,
13
- state types, prop types — look them all up in the index you now hold, and open what it named in a
14
- single call with one `Read` per file:
15
-
16
- ```
17
- call 1 Read <pkgRoot>/dist/docs/index.json
18
- Read <pkgRoot>/dist/dts-bundle/index.json
19
-
20
- call 2 Read <pkgRoot>/dist/docs/Table.md <- docs entry's `file`
21
- Read <pkgRoot>/dist/docs/useTableCollection.md
22
- Read <pkgRoot>/dist/dts-bundle/components/Table.d.ts <- its `bundle`
23
- Read <pkgRoot>/dist/dts-bundle/hooks/useTableCollection.d.ts
24
- Read <pkgRoot>/dist/dts-bundle/types/TableState.d.ts <- bundle entry's `file`
25
- ```
26
-
27
- Two calls, not twenty-two — and nothing is lost by batching, because there is nothing to learn
28
- between the files: every path came out of the same index and `bytes` already told you each size.
29
- The same files opened one per call re-send the whole conversation once per file, which is where a
30
- lookup session's token cost actually goes. Batch the follow-ups the same way: when a doc names an
31
- example file, or a stub names another bundle (below), collect them and read them together.
32
-
33
- ## Where props live
34
-
35
- From `@wix/patterns` **1.460.0** onward a doc does not repeat props that the bundle already
36
- describes. Which one holds them is stated in the doc's own index entry:
37
-
38
- | `dist/docs/index.json` entry | where the props are |
39
- | --- | --- |
40
- | has a `bundle` field | that bundle — the doc's `### Props` is a pointer to it, on purpose |
41
- | no `bundle` field | the doc's own `### Props` table, as before |
42
-
43
- Roughly 74 of 167 docs point at a bundle and 62 still carry a table, so expect both. When a doc
44
- says `Read \`dist/dts-bundle/...\``, that **is** the props answer — read the named file rather
45
- than treating the doc as incomplete. The bundle is the better source anyway: it keeps the
46
- `extends` clause with its exclusions, so `Omit<PopoverMenuItemProps, 'text' | 'prefixIcon' | 'onClick'>` tells
47
- you what you do *not* inherit, which the old doc link did not.
48
-
49
- ## Types the docs don't cover
50
-
51
- The same bundles also hold the types those props use — `Filter<T>`, `RangeItem<T>`, `CursorQuery`, a `...Props` interface. `Read <pkgRoot>/dist/dts-bundle/index.json`, look up the type name, then `Read <pkgRoot>/dist/dts-bundle/<entry.file>` using exactly the `file` path the index gives — **never reconstruct the path from the name**; bundles are nested one directory per kind (`components/Table.d.ts`, `hooks/useForm.d.ts`, `types/RangeItem.d.ts`, …), so guessing `<Name>.d.ts` at the top level is wrong by construction.
52
-
53
- Several of these types (`RangeItem`, `Filter`, `CursorQuery`, the filter factory functions) actually live in `@wix/bex-core` — the bundle already resolves and inlines the real declaration, so you get the full shape with no deep, undeclared `@wix/bex-core/dist/types/...` path to chase. Still always import it from `@wix/patterns`, per the index's `importPath` field, never from wherever the bundle says it's really declared.
54
-
55
- That inlining applies to the data shapes you write. State objects you *receive* are a separate case — see the next section.
56
-
57
- ## A bundle stops where you stop writing code
58
-
59
- A bundle carries its own name's declaration plus every shape **you** would write by hand. It deliberately does *not* expand what the library hands you, so two kinds of one-line stub are normal and are answers, not truncation:
60
-
61
- ```ts
62
- /**
63
- * `TableState` — you receive this, you don't construct it.
64
- * Produced by `useTableCollection()` (also `useAmbassadorTable()`, `useTableContext()`).
65
- * Look `TableState` up in the bundle index for its own API.
66
- */
67
- declare class TableState<T, F> {
68
- }
69
- ```
70
-
71
- An empty body with a **"Produced by"** note means: call that hook or factory to get one. That is usually the whole answer — `<Table state={...}>` needs `useTableCollection()`, not `TableState`'s internals. If you do need its members (`state.toolbar`, `state.visibleColumns`), it has its own index entry — look the name up and read that bundle, where its body is complete.
72
-
73
- A stub saying **"cut here because it has its own bundle"** means the same thing for a shape you *do* write: it wasn't copied in twice, so look the name up and read its own file.
74
-
75
- Every bundle fits in a single read; the index's `bytes` field says how big before you open it. So a bundle is never partially shown — if something looks missing, it was cut on purpose. Both stubs above name where to find it.
76
-
77
- ### The one that doesn't: a bare `import`
78
-
79
- A plain `import { X } from '<module>'` at the top of a bundle, with no note attached, is the third shape you will meet — and unlike the two above it does **not** tell you where to look. What to do depends on the path, not the name:
80
-
81
- **A normal entry point** — `react`, `react-hook-form`, `history`, `@wix/design-system`. Resolve it the ordinary way. For `@wix/design-system` names use the `wix-design-system` skill; don't read WDS files directly.
82
-
83
- **A deep internal path** — `@wix/design-system/dist/types/DropdownLayout`, `@wix/bex-utils/@wix/bi-logger-os-data/v2/types`. This is an upstream defect: the bundle recorded where the type is *declared* instead of where it is exported. Two things follow, and the second is the one that saves you time:
84
-
85
- - Never import that path in your own code, and don't go read the file it points at. It is not a public entry point, and the name is often not re-exported from the package root either — `DropdownLayoutOption` is not exported from `@wix/design-system`, so there is no shorter import to substitute.
86
- - You usually don't need the declaration at all. Where the type is a **prop you fill in** — WDS option shapes like `SingleSelectFilter`'s and `AutoCompleteFilter`'s `items` — look the *component* up in the `wix-design-system` skill and use the shape its docs show. Where it is a **pass-through you never construct** — the BI logger params behind optional fields like `biAdditionalInfo` — pass what the patterns doc shows, or omit it; it is optional.
87
-
88
- If a name you genuinely need stays unresolved after that, stop and say so, and name the bundle and the exact import path that dead-ended. Do not guess a shape and do not go spelunking in `node_modules` — a wrong guess compiles here and breaks at runtime, which is worse than the missing type.
89
-
90
- A handful of names carry `"status": "unreachable"` — plus `"unexported": true` from **1.465.0** — with a message saying not to import them (the `...BaseProps` interfaces a component's props `extends`). Read those for the props they contribute; don't write an import for them.
91
-
92
- `status` also carries `"deprecated"`, and there the `statusMessage` names the replacement — `PrimaryPageButton` says *"Use `PrimaryActions` component instead."* Check it before you commit to a name — nothing else in the lookup path will stop you, since a deprecated component still compiles and still renders. If the index calls a name deprecated, use what its message names instead.
93
-
94
- ## Subpath entry points
95
-
96
- `@wix/patterns` is 31 entry points, not one namespace, and `/form` re-exports `@wix/bex-core/form`. To see what's importable from a specific one: `Read <pkgRoot>/dist/dts-bundle/exports/<subpath>.d.ts` directly (`.` is `exports/index.d.ts`; a nested one like `./testkit/backend` is `exports/testkit/backend.d.ts`). This only lists the curated names covered above — a file with nothing in it (a one-line comment) means no curated name lives on that subpath yet, not that the subpath doesn't exist.
97
-
98
- ## Following cross-references
99
-
100
- Docs link to related names as Storybook URLs (`[TableState](./?path=/story/...--tablestate)`) — resolve the link text back to a filename via `dist/docs/index.json`, the same way you found the first doc, and `Read` it if you actually need it. There's no automatic multi-level expansion here; follow only the links you need.
101
-
102
- Links to `https://www.docs.wixdesignsystem.com/` are external (Wix Design System) — not part of `@wix/patterns` docs. Likewise, a bundled `.d.ts` that references a deep `@wix/design-system/dist/...` path is pointing at that library's own internals — look the name up via the `wix-design-system` skill's own tool, not by opening the path.
103
-
104
- ## Tips
105
-
106
- - **Compound components** have separate docs per sub-part: `CollectionPage.md`, `CollectionPage.Header.md`, `CollectionPage.Content.md`.
107
- - **Hook docs** list configuration options as props — in the API table when the doc has no
108
- `bundle` field, otherwise in the bundle it points at.
109
- - **Heavy examples live beside the doc.** A variation whose code was long is written out
110
- separately, and the doc keeps the heading, the description and an exact path:
111
- `Example code: read \`dist/docs/ToolbarFilters/apply-changes.tsx\``. Read that path only if you
112
- need that particular variation — the heading and description are usually enough to choose.
@@ -1,100 +0,0 @@
1
- // Grid tile — a REFERENCE implementation over the ProductSummary DTO. Complete and correct
2
- // (badges, sale price, hover image, quick-add); styled entirely from the @theme tokens via
3
- // Tailwind utilities. Designing your own card on the same DTO is encouraged.
4
- import type { ComponentType, ReactNode } from "react";
5
- import type { ProductSummary } from "../../wix/storefront/types";
6
- import { useCart } from "../../hooks/storefront/useCart";
7
-
8
- export interface LinkLikeProps {
9
- href: string;
10
- className?: string;
11
- children?: ReactNode;
12
- }
13
-
14
- export interface ProductCardProps {
15
- product: ProductSummary;
16
- /** Route pattern for detail pages; default matches the shipped /products/[slug] page. */
17
- productHref?: (slug: string) => string;
18
- /** Router-specific link (react-router Link, next/link); defaults to a plain <a>. */
19
- LinkComponent?: ComponentType<LinkLikeProps>;
20
- }
21
-
22
- const PlainLink = ({ href, className, children }: LinkLikeProps) => (
23
- <a href={href} className={className}>
24
- {children}
25
- </a>
26
- );
27
-
28
- export default function ProductCard({
29
- product,
30
- productHref = (slug) => `/products/${slug}`,
31
- LinkComponent = PlainLink,
32
- }: ProductCardProps) {
33
- const { addToCart, busy } = useCart();
34
- const soldOut = product.availability === "OUT_OF_STOCK" && !product.preorder;
35
- const priceLabel =
36
- product.maxPrice && product.maxPrice !== product.price
37
- ? `${product.price} – ${product.maxPrice}`
38
- : product.price;
39
-
40
- return (
41
- <div className="group">
42
- <LinkComponent href={productHref(product.slug)} className="block no-underline">
43
- <div className="relative aspect-square overflow-hidden rounded-lg bg-secondary">
44
- {product.imageUrl && (
45
- <img
46
- src={product.imageUrl}
47
- alt={product.name}
48
- loading="lazy"
49
- className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
50
- />
51
- )}
52
- {product.hoverImageUrl && (
53
- <img
54
- src={product.hoverImageUrl}
55
- alt=""
56
- aria-hidden="true"
57
- loading="lazy"
58
- className="absolute inset-0 h-full w-full object-cover opacity-0 transition-opacity duration-300 group-hover:opacity-100"
59
- />
60
- )}
61
- {product.ribbon && (
62
- <span className="absolute left-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur">
63
- {product.ribbon}
64
- </span>
65
- )}
66
- {soldOut && (
67
- <span className="absolute right-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-muted-foreground backdrop-blur">
68
- Sold out
69
- </span>
70
- )}
71
- {product.preorder && (
72
- <span className="absolute right-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur">
73
- Pre-order
74
- </span>
75
- )}
76
- </div>
77
- <p className="mt-3 text-sm font-medium text-foreground">{product.name}</p>
78
- <p className="mt-0.5 flex items-baseline gap-2 text-sm">
79
- <span className="text-foreground">{priceLabel}</span>
80
- {product.compareAtPrice && (
81
- <span className="text-muted-foreground line-through">{product.compareAtPrice}</span>
82
- )}
83
- </p>
84
- {product.optionsSummary && (
85
- <p className="mt-0.5 text-xs text-muted-foreground">{product.optionsSummary}</p>
86
- )}
87
- </LinkComponent>
88
- {product.quickAddable && (
89
- <button
90
- type="button"
91
- disabled={busy}
92
- onClick={() => addToCart(product.id).catch(() => {})}
93
- className="mt-2 rounded-full border border-border px-4 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:opacity-50"
94
- >
95
- Add to cart
96
- </button>
97
- )}
98
- </div>
99
- );
100
- }