@shoppexio/storefront-react 0.1.0 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2
4
+
5
+ - Contact form: a message that is longer than the 2,000-character ticket
6
+ limit once the name line is added is refused with an error naming the
7
+ limit, instead of being cut off and reported as sent.
8
+ - Contact form: a subject over 30 characters is refused, not cut.
9
+ - `loadLatestWrittenReviews(limit, labels)`: the newest written reviews for a
10
+ sample, paging the feed only until it has enough. `getShopReviewsPage` is
11
+ typed on the SDK global.
12
+
13
+ ## 0.1.1
14
+
15
+ - README rewritten for theme developers: install, quick start, theming,
16
+ eject and links. Package metadata: support contact and keywords.
17
+
3
18
  ## 0.1.0
4
19
 
5
20
  - First release. The commerce layer of the Shoppex code templates (SDK access,
package/README.md CHANGED
@@ -1,22 +1,151 @@
1
1
  # @shoppexio/storefront-react
2
2
 
3
- The commerce layer and the customer account of a Shoppex code storefront
4
- (the Advanced lane). State, hooks, models and parsers from the package root;
5
- the account UI from `@shoppexio/storefront-react/account`. Decision record:
6
- ADR-0068.
3
+ React hooks, models and a ready-made customer account for Shoppex code
4
+ storefronts. A code storefront is a shop front you write yourself in React and
5
+ Vite; Shoppex builds it, serves it on the shop's domain and runs checkout. This
6
+ package handles the commerce part (catalog, product pages, cart, reviews, the
7
+ signed-in customer area), so your code is the design.
7
8
 
8
- It reads two things the Shoppex worker puts on the page before your bundle
9
- runs: `window.shoppex` (the Storefront SDK, loaded as a CDN script by your
10
- `index.html`) and `window.__SHOPPEX_INITIAL__` (the shop, the catalog, the
11
- merchant's saved content). It never bundles the SDK.
9
+ ## Install
12
10
 
13
11
  ```sh
12
+ npm install @shoppexio/storefront-react
14
13
  bun add @shoppexio/storefront-react
14
+ pnpm add @shoppexio/storefront-react
15
15
  ```
16
16
 
17
17
  Peer dependencies: `react` and `react-dom` 19.2, `react-router-dom` 7.18.
18
18
 
19
- ## Entries
19
+ The package reads what Shoppex puts on the page before your bundle runs: the
20
+ Storefront SDK as `window.shoppex` (a script tag in your `index.html`) and the
21
+ shop's catalog and settings as `window.__SHOPPEX_INITIAL__`. It does not bundle
22
+ the SDK, and it throws a clear error when either is missing.
23
+
24
+ ## Quick start
25
+
26
+ ```tsx
27
+ // main.tsx
28
+ import { createRoot } from 'react-dom/client';
29
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
30
+ import {
31
+ ACCOUNT_PATH,
32
+ CartProvider,
33
+ formatMoney,
34
+ readStorefrontData,
35
+ StorefrontProvider,
36
+ useCart,
37
+ useStorefront,
38
+ } from '@shoppexio/storefront-react';
39
+ import { AccountPage } from '@shoppexio/storefront-react/account';
40
+ import '@shoppexio/storefront-react/account.css';
41
+
42
+ function Shelf() {
43
+ const { catalog } = useStorefront();
44
+ const { add, count, busy } = useCart();
45
+ return (
46
+ <main>
47
+ <p>{count} in cart</p>
48
+ {catalog.products.map((product) => (
49
+ <button
50
+ key={product.id}
51
+ disabled={busy}
52
+ onClick={() => add(product, product.priceVariants[0] ?? null, product.quantityMin)}
53
+ >
54
+ {product.name}, {formatMoney(product.price, product.currency)}
55
+ </button>
56
+ ))}
57
+ </main>
58
+ );
59
+ }
60
+
61
+ createRoot(document.getElementById('root')!).render(
62
+ <StorefrontProvider data={readStorefrontData()}>
63
+ <CartProvider>
64
+ <BrowserRouter>
65
+ <Routes>
66
+ <Route path="/" element={<Shelf />} />
67
+ <Route path={`${ACCOUNT_PATH}/*`} element={<AccountPage frame={({ children }) => <>{children}</>} notFound={<p>Not found</p>} />} />
68
+ </Routes>
69
+ </BrowserRouter>
70
+ </CartProvider>
71
+ </StorefrontProvider>,
72
+ );
73
+ ```
74
+
75
+ `useCart()` also gives you line updates, coupons, the live quote and
76
+ `checkout()`, which sends the buyer to Shoppex checkout. Load the account with
77
+ `import()` in a real theme so it stays out of your main bundle.
78
+
79
+ ## Features
80
+
81
+ - Catalog: product and bundle cards, categories, sorting, filtering, paging.
82
+ - Product pages: options, pricing per quantity, stock, badges, videos, the
83
+ rating breakdown.
84
+ - Cart: add, update, remove, coupons, quotes from the server, checkout
85
+ hand-off, per-product checkout fields.
86
+ - Reviews, shop pages, navigation menus and the contact form, from the SDK.
87
+ - Merchant rich text, sanitized before you render it.
88
+ - The customer account: sign-in, orders, downloads and keys, subscriptions,
89
+ rewards, referrals, reseller, support tickets and settings, in sixteen
90
+ languages.
91
+ - Money formatting: `$49`, `$49.50`, `¥1,200`.
92
+
93
+ ## Theming the account
94
+
95
+ The account has no look of its own. `account.css` reads your theme's `--sx-*`
96
+ custom properties, so it takes your colours, type, radii and shadows in light
97
+ and dark mode. Define the tokens once:
98
+
99
+ ```css
100
+ :root {
101
+ --sx-bg: #0a0b0e;
102
+ --sx-surface: #111318;
103
+ --sx-accent: #3b5bf0;
104
+ --sx-on-accent: #ffffff;
105
+ --sx-radius-card: 16px;
106
+ /* the full list is below */
107
+ }
108
+ ```
109
+
110
+ To change one part, target its `sx-account-*` hook under `.sx-account`. Two
111
+ classes beat the account's one-class utilities:
112
+
113
+ ```css
114
+ .sx-account .sx-account-card { border-radius: 0; box-shadow: none; }
115
+ .sx-account .sx-account-nav-link[aria-current='page'] { text-decoration: underline; }
116
+ ```
117
+
118
+ The account's own classes carry an `sxa:` prefix, so they never collide with
119
+ yours, and nothing in the stylesheet selects your elements.
120
+
121
+ ## Eject
122
+
123
+ When tokens and hooks are not enough and you want to own the account's markup,
124
+ copy it into your project:
125
+
126
+ 1. Copy `node_modules/@shoppexio/storefront-react/eject/account/` to
127
+ `src/account/`. Its imports already point at `@shoppexio/storefront-react`.
128
+ 2. Import `AccountPage` from `./account/account-page` instead of
129
+ `@shoppexio/storefront-react/account`.
130
+ 3. Keep importing `@shoppexio/storefront-react/account.css`; it styles the
131
+ classes the copied markup carries.
132
+
133
+ From then on the account is yours, and package updates no longer change it.
134
+ Most themes never need this.
135
+
136
+ ## Links
137
+
138
+ - Docs: [Code storefront development](https://docs.shoppex.io/storefront/code-storefront-development),
139
+ [Customer accounts](https://docs.shoppex.io/storefront/code-storefront-accounts)
140
+ - Example: the ForkMe template, a complete storefront built on this package.
141
+ Create a storefront from it in the Shoppex dashboard under
142
+ **Store → Storefronts**; see [Code storefronts](https://docs.shoppex.io/storefront/code-storefronts).
143
+ - [Changelog](./CHANGELOG.md)
144
+ - License: MIT
145
+
146
+ ## Reference
147
+
148
+ ### Entries
20
149
 
21
150
  | Import | What it is |
22
151
  |---|---|
@@ -24,7 +153,7 @@ Peer dependencies: `react` and `react-dom` 19.2, `react-router-dom` 7.18.
24
153
  | `@shoppexio/storefront-react/account` | `AccountPage`, the whole customer account |
25
154
  | `@shoppexio/storefront-react/account.css` | The account's stylesheet, plain CSS |
26
155
 
27
- ### The root: where to start
156
+ #### The root: where to start
28
157
 
29
158
  | Module | Use it for | Start with |
30
159
  |---|---|---|
@@ -42,42 +171,17 @@ Peer dependencies: `react` and `react-dom` 19.2, `react-router-dom` 7.18.
42
171
  The `.d.ts` files next to the JS are the reference; every export carries its
43
172
  contract in its doc comment.
44
173
 
45
- ## Mounting the account
46
-
47
- ```tsx
48
- // src/pages/account-page.tsx, loaded with import() behind ACCOUNT_PATH
49
- import '@shoppexio/storefront-react/account.css';
50
- import { AccountPage as ShoppexAccount } from '@shoppexio/storefront-react/account';
51
- import { PageShell } from '../components/layout/page-shell';
52
- import { NotFoundPage } from './not-found-page';
53
-
54
- function Frame({ children }: { children: React.ReactNode }) {
55
- return <PageShell page="account">{children}</PageShell>;
56
- }
57
-
58
- export function AccountPage() {
59
- return <ShoppexAccount frame={Frame} notFound={<NotFoundPage />} />;
60
- }
61
- ```
174
+ ### Account routing
62
175
 
63
- - Mount it under `${ACCOUNT_PATH}/*` and `${ACCOUNT_PATH_ALIAS}/*`.
176
+ - Mount the account under `${ACCOUNT_PATH}/*` and `${ACCOUNT_PATH_ALIAS}/*`.
64
177
  - Route every path in `ACCOUNT_STOREFRONT_ROUTES`; the account links to them.
65
178
  - List every entry of `ACCOUNT_ARTIFACT_CAPABILITIES` in
66
179
  `public/shoppex.capabilities.json`.
67
- - The locale is read from `<html lang>`, which the Shoppex worker sets. All
68
- sixteen storefront locales ship with the account.
180
+ - The account reads its language from `<html lang>`, which Shoppex sets.
69
181
 
70
- ## Styling the account
182
+ ### Account hooks
71
183
 
72
- The account has no look of its own. Every value in `account.css` is a read of
73
- one of your theme's `--sx-*` custom properties, so it takes your palette, type,
74
- radii and shadows, light and dark. Its utilities use the package-private `sxa:`
75
- prefix, so they cannot collide with your classes, and nothing in it selects
76
- your elements. It uses no cascade layer: a one-class `sxa:` rule beats your
77
- element resets (`button { color: inherit }`), and a rule of yours scoped under
78
- the root hook beats it.
79
-
80
- To restyle a part, target its hook under `.sx-account`. Hooks carry no styles:
184
+ Hooks carry no styles:
81
185
 
82
186
  `sx-account` (root), `sx-account-header`, `sx-account-title`,
83
187
  `sx-account-signout`, `sx-account-nav`, `sx-account-nav-link`,
@@ -88,19 +192,14 @@ To restyle a part, target its hook under `.sx-account`. Hooks carry no styles:
88
192
  `sx-account-status`, `sx-account-field`, `sx-account-label`,
89
193
  `sx-account-field-error`.
90
194
 
91
- ```css
92
- /* two classes beat the account's one-class utilities */
93
- .sx-account .sx-account-card { border-radius: 0; box-shadow: none; }
94
- .sx-account .sx-account-nav-link[aria-current='page'] { text-decoration: underline; }
95
- ```
96
-
97
195
  `.sx-account` also scopes a zero-specificity baseline (box sizing, border
98
- reset, heading font) for themes without a CSS reset.
196
+ reset, heading font) for themes without a CSS reset. The stylesheet uses no
197
+ cascade layer: a one-class `sxa:` rule beats your element resets, and a rule of
198
+ yours scoped under `.sx-account` beats it.
99
199
 
100
- ### The CSS custom property contract
200
+ ### CSS custom properties
101
201
 
102
- A theme defines every one of these. A package test pins this list against the
103
- built stylesheet.
202
+ A theme defines every one of these.
104
203
 
105
204
  <!-- css-contract:start -->
106
205
 
@@ -114,24 +213,3 @@ built stylesheet.
114
213
  <!-- css-contract:end -->
115
214
 
116
215
  Body text inherits the page's own font and colour.
117
-
118
- ## Ejecting the account
119
-
120
- When restyling is not enough and you want to own the account's markup:
121
-
122
- 1. Copy `node_modules/@shoppexio/storefront-react/eject/account/` into your
123
- project as `src/account/`. Its imports of the package root already point
124
- at `@shoppexio/storefront-react`.
125
- 2. Import `AccountPage` from `./account/account-page` instead of
126
- `@shoppexio/storefront-react/account`.
127
- 3. Keep importing `@shoppexio/storefront-react/account.css`: it styles the
128
- `sxa:` classes the copied markup carries. New markup uses your own classes.
129
-
130
- From then on the account is yours and a package update no longer reaches it.
131
- A CLI command for this is planned.
132
-
133
- ## Versions
134
-
135
- Official templates pin an exact version. Monorepo maintainers move every pin
136
- at once with `bun run storefront-react:bump <version>` after publishing; see
137
- `docs/runbooks/theme-runtime-release.md`.
package/dist/index.d.ts CHANGED
@@ -892,6 +892,20 @@ interface ShoppexSdk {
892
892
  success: boolean;
893
893
  data?: SdkShopReview[];
894
894
  }>;
895
+ /**
896
+ * One page of the same feed, newest first. `cursor` is the previous page's
897
+ * `pagination.next_cursor`; omit it for the first page.
898
+ */
899
+ getShopReviewsPage(cursor?: string | null): Promise<{
900
+ success: boolean;
901
+ data?: {
902
+ feedback: SdkShopReview[];
903
+ pagination: {
904
+ next_cursor: string | null;
905
+ has_more: boolean;
906
+ };
907
+ };
908
+ }>;
895
909
  /** Starts an in-page sign-in. Sends the email and nothing else. */
896
910
  requestOtp(email: string): Promise<SdkResponse<unknown>>;
897
911
  /**
@@ -2894,6 +2908,14 @@ declare function toShopReviews(rows: readonly SdkShopReview[], labels: ShopRevie
2894
2908
  * derive a number from them.
2895
2909
  */
2896
2910
  declare function reviewsForProduct(reviews: readonly ShopReview[], productTitle: string): ShopReview[];
2911
+ /**
2912
+ * The newest WRITTEN reviews, at most `limit`, for a sample such as a home
2913
+ * section. Pages through the feed only until it has enough: a shop with a
2914
+ * thousand reviews costs one request, not ten. Automated entries and ratings
2915
+ * without words are skipped, since a sample of quotes needs quotes.
2916
+ * Resolves null when a page request fails.
2917
+ */
2918
+ declare function loadLatestWrittenReviews(limit: number, labels: ShopReviewLabels): Promise<ShopReview[] | null>;
2897
2919
  declare function averageRating(reviews: readonly ShopReview[]): number | null;
2898
2920
  declare function useShopReviews(labels: ShopReviewLabels): ShopReviewsState;
2899
2921
 
@@ -3084,4 +3106,4 @@ declare function useOnlineUserCount(): number | null;
3084
3106
  */
3085
3107
  declare function resolveProductVideoEmbedSrc(value: unknown): string | null;
3086
3108
 
3087
- export { ACCOUNT_ARTIFACT_CAPABILITIES, ACCOUNT_PATH, ACCOUNT_PATH_ALIAS, ACCOUNT_ROUTE_MESSAGE_KEYS, ACCOUNT_STOREFRONT_ROUTES, ACCOUNT_TABS, type AccountRouteMessageKey, type AccountRouteParts, type AccountTab, type AccountView, type BuyBoxVariantModel, CATALOG_PAGE_SIZE, CATALOG_SORTS, CATALOG_SORT_LABELS, type CartBoundsHeal, CartProvider, type CartQuoteLineBounds, type CartTotals, type CartView, type CartViewLine, type CatalogCardModel, type CatalogPage, type CatalogSort, type CheckoutFieldGroup, type CheckoutFieldValues, type CheckoutFields, type ContactFormValues, type ContactSubmissionContext, DEFAULT_ACCOUNT_TAB, DEFAULT_LOW_STOCK_THRESHOLD, DELIVERY_TIME_UNITS, type DeliveryPromise, type DeliveryTimeUnit, type GroupCardModel, IMPLEMENTED_ACCOUNT_TABS, type ImplementedAccountTab, type LicenseStatement, type LicenseTerm, type LinePricing, MAX_PAY_WHAT_YOU_WANT_PRICE, type MenuNavLink, type MenuSlot, type NavLink, type NavigationLinks, PRODUCT_BADGE_ICON_NAMES, type ProductAddon, type ProductAutomaticDeal, type ProductBadge, type ProductBadgeIconName, type ProductBundle, type ProductBundleItem, type ProductCardModel, type ProductCategoryRef, type ProductContentTab, type ProductCustomField, type ProductDetailModel, type ProductFaq, type ProductRatingModel, type ProductSubscription, type ProductType, QUOTE_UNAVAILABLE_CHECKOUT_ERROR, type RatingStars, type ResellerProgramSummary, type RewardProgramItem, type RewardProgramSummary, SERVING_CONTEXT_HINT, SHOP_REVIEW_PAGE_SIZE, STOREFRONT_SORT_KEYS, type SdkCartAddOptions, type SdkCartAddon, type SdkCartAppliedDiscount, type SdkCartCodeSource, type SdkCartItem, type SdkCartQuote, type SdkCartQuoteLine, type SdkCheckoutResult, type SdkCustomerAffiliateLink, type SdkCustomerAffiliateSummary, type SdkCustomerDashboard, type SdkCustomerDashboardOrder, type SdkCustomerDeliverable, type SdkCustomerDeliveryStatus, type SdkCustomerDeliverySummary, type SdkCustomerLicense, type SdkCustomerLoyalty, type SdkCustomerLoyaltyRedeem, type SdkCustomerLoyaltyTransactionType, type SdkCustomerOrderDetail, type SdkCustomerOrderPage, type SdkCustomerOrderSummary, type SdkCustomerRewardActivity, type SdkCustomerRewards, type SdkCustomerSubscription, type SdkCustomerTicketSummary, type SdkCustomerWarranty, type SdkCustomerWarrantyClaim, type SdkCustomerWarrantyClaimStatus, type SdkCustomerWarrantyList, type SdkCustomerWarrantyStatus, type SdkEligibleCartDeal, type SdkInitOptions, type SdkMenu, type SdkMenuItem, type SdkPage, type SdkRecentSaleEntry, type SdkResponse, type SdkShopReview, type ShopPageFaqItem, type ShopPageModel, type ShopPageState, type ShopReview, type ShopReviewLabels, type ShopReviewsState, type ShoppexSdk, type SocialLink, type SocialNetwork, type StockChip, type StockTone, type StorefrontAddon, type StorefrontCatalog, type StorefrontCategory, type StorefrontData, type StorefrontGroup, type StorefrontListingItem, type StorefrontPriceVariant, type StorefrontProduct, StorefrontProvider, type StorefrontSettings, type StorefrontShop, type StorefrontShopStats, type StorefrontSortKey, type SubscriptionIntervalUnit, type SupportChannel, type VolumeDiscountRule, accountOrderHref, accountRouteMessage, accountTabHref, accountTabLabelKey, averageRating, badgesForCard, badgesForProductPage, buildCartTotals, buildCartView, buildCustomFieldPayload, buildInitialCheckoutFieldValues, buildProductDetail, buildStorefrontCatalog, calculateDisplayLineTotal, calculateLinePricing, canonicalAccountPath, checkoutFieldKey, checkoutFieldWriteRepriced, checkoutNeedsDetails, clampQuantity, clearRecoveredQuoteCheckoutError, filterCatalogCards, findStorefrontProduct, firstOptionalString, flattenMerchantHtmlToText, formatCompactCount, formatDeliveryTime, formatLicenseTerm, formatMoney, formatSubscriptionCadence, formatSubscriptionTrial, getAuthoredContent, getBuyerCurrency, getProductGroups, getProducts, getShoppex, getStorefrontAddons, getStorefrontCategories, getStorefrontListing, getStorefrontSettings, getStorefrontShop, hasInjectedStorefrontData, isAccountTab, isBlankMerchantHtml, isCheckboxValueChecked, isImplementedAccountTab, isProductSoldOut, isRecord, isSelectionSoldOut, loadShopPage, missingRequiredCheckoutValues, optionalBoolean, optionalString, optionalStringWithDefault, paginateCatalogCards, parseAccountPath, parseCartQuoteLines, parseEnum, parseFiniteNumber, parseProductBadges, parseStorefrontSortKey, readCheckoutNeedsDetails, readStorefrontData, requireArray, requireBoolean, requireNumber, requireRecord, requireString, requireStringFromKeys, requireUnknownRecord, resolveAccountView, resolveBadgeForeground, resolveCartBoundsHeals, resolveCategorySelection, resolveCheckoutFields, resolveDeliveryPromise, resolveDisplayStock, resolveHideStockCounter, resolveLicenseTerm, resolveMenuLinksForSlot, resolvePayWhatYouWantMinimum, resolveProductVideoEmbedSrc, resolveQuantityBounds, resolveSupportChannels, resolveUnsellableLineIds, resolveVariantStock, reviewsForProduct, roundCurrency, sanitizeMerchantHtml, sortCatalogCards, startCartMutation, stripMerchantHtmlImages, submitContactTicket, toShopReviews, toStarCount, useCart, useNavigationLinks, useOnlineUserCount, useShopReviews, useStorefront, validateCheckoutFieldValue, validateCheckoutFields, validateContactSubmission };
3109
+ export { ACCOUNT_ARTIFACT_CAPABILITIES, ACCOUNT_PATH, ACCOUNT_PATH_ALIAS, ACCOUNT_ROUTE_MESSAGE_KEYS, ACCOUNT_STOREFRONT_ROUTES, ACCOUNT_TABS, type AccountRouteMessageKey, type AccountRouteParts, type AccountTab, type AccountView, type BuyBoxVariantModel, CATALOG_PAGE_SIZE, CATALOG_SORTS, CATALOG_SORT_LABELS, type CartBoundsHeal, CartProvider, type CartQuoteLineBounds, type CartTotals, type CartView, type CartViewLine, type CatalogCardModel, type CatalogPage, type CatalogSort, type CheckoutFieldGroup, type CheckoutFieldValues, type CheckoutFields, type ContactFormValues, type ContactSubmissionContext, DEFAULT_ACCOUNT_TAB, DEFAULT_LOW_STOCK_THRESHOLD, DELIVERY_TIME_UNITS, type DeliveryPromise, type DeliveryTimeUnit, type GroupCardModel, IMPLEMENTED_ACCOUNT_TABS, type ImplementedAccountTab, type LicenseStatement, type LicenseTerm, type LinePricing, MAX_PAY_WHAT_YOU_WANT_PRICE, type MenuNavLink, type MenuSlot, type NavLink, type NavigationLinks, PRODUCT_BADGE_ICON_NAMES, type ProductAddon, type ProductAutomaticDeal, type ProductBadge, type ProductBadgeIconName, type ProductBundle, type ProductBundleItem, type ProductCardModel, type ProductCategoryRef, type ProductContentTab, type ProductCustomField, type ProductDetailModel, type ProductFaq, type ProductRatingModel, type ProductSubscription, type ProductType, QUOTE_UNAVAILABLE_CHECKOUT_ERROR, type RatingStars, type ResellerProgramSummary, type RewardProgramItem, type RewardProgramSummary, SERVING_CONTEXT_HINT, SHOP_REVIEW_PAGE_SIZE, STOREFRONT_SORT_KEYS, type SdkCartAddOptions, type SdkCartAddon, type SdkCartAppliedDiscount, type SdkCartCodeSource, type SdkCartItem, type SdkCartQuote, type SdkCartQuoteLine, type SdkCheckoutResult, type SdkCustomerAffiliateLink, type SdkCustomerAffiliateSummary, type SdkCustomerDashboard, type SdkCustomerDashboardOrder, type SdkCustomerDeliverable, type SdkCustomerDeliveryStatus, type SdkCustomerDeliverySummary, type SdkCustomerLicense, type SdkCustomerLoyalty, type SdkCustomerLoyaltyRedeem, type SdkCustomerLoyaltyTransactionType, type SdkCustomerOrderDetail, type SdkCustomerOrderPage, type SdkCustomerOrderSummary, type SdkCustomerRewardActivity, type SdkCustomerRewards, type SdkCustomerSubscription, type SdkCustomerTicketSummary, type SdkCustomerWarranty, type SdkCustomerWarrantyClaim, type SdkCustomerWarrantyClaimStatus, type SdkCustomerWarrantyList, type SdkCustomerWarrantyStatus, type SdkEligibleCartDeal, type SdkInitOptions, type SdkMenu, type SdkMenuItem, type SdkPage, type SdkRecentSaleEntry, type SdkResponse, type SdkShopReview, type ShopPageFaqItem, type ShopPageModel, type ShopPageState, type ShopReview, type ShopReviewLabels, type ShopReviewsState, type ShoppexSdk, type SocialLink, type SocialNetwork, type StockChip, type StockTone, type StorefrontAddon, type StorefrontCatalog, type StorefrontCategory, type StorefrontData, type StorefrontGroup, type StorefrontListingItem, type StorefrontPriceVariant, type StorefrontProduct, StorefrontProvider, type StorefrontSettings, type StorefrontShop, type StorefrontShopStats, type StorefrontSortKey, type SubscriptionIntervalUnit, type SupportChannel, type VolumeDiscountRule, accountOrderHref, accountRouteMessage, accountTabHref, accountTabLabelKey, averageRating, badgesForCard, badgesForProductPage, buildCartTotals, buildCartView, buildCustomFieldPayload, buildInitialCheckoutFieldValues, buildProductDetail, buildStorefrontCatalog, calculateDisplayLineTotal, calculateLinePricing, canonicalAccountPath, checkoutFieldKey, checkoutFieldWriteRepriced, checkoutNeedsDetails, clampQuantity, clearRecoveredQuoteCheckoutError, filterCatalogCards, findStorefrontProduct, firstOptionalString, flattenMerchantHtmlToText, formatCompactCount, formatDeliveryTime, formatLicenseTerm, formatMoney, formatSubscriptionCadence, formatSubscriptionTrial, getAuthoredContent, getBuyerCurrency, getProductGroups, getProducts, getShoppex, getStorefrontAddons, getStorefrontCategories, getStorefrontListing, getStorefrontSettings, getStorefrontShop, hasInjectedStorefrontData, isAccountTab, isBlankMerchantHtml, isCheckboxValueChecked, isImplementedAccountTab, isProductSoldOut, isRecord, isSelectionSoldOut, loadLatestWrittenReviews, loadShopPage, missingRequiredCheckoutValues, optionalBoolean, optionalString, optionalStringWithDefault, paginateCatalogCards, parseAccountPath, parseCartQuoteLines, parseEnum, parseFiniteNumber, parseProductBadges, parseStorefrontSortKey, readCheckoutNeedsDetails, readStorefrontData, requireArray, requireBoolean, requireNumber, requireRecord, requireString, requireStringFromKeys, requireUnknownRecord, resolveAccountView, resolveBadgeForeground, resolveCartBoundsHeals, resolveCategorySelection, resolveCheckoutFields, resolveDeliveryPromise, resolveDisplayStock, resolveHideStockCounter, resolveLicenseTerm, resolveMenuLinksForSlot, resolvePayWhatYouWantMinimum, resolveProductVideoEmbedSrc, resolveQuantityBounds, resolveSupportChannels, resolveUnsellableLineIds, resolveVariantStock, reviewsForProduct, roundCurrency, sanitizeMerchantHtml, sortCatalogCards, startCartMutation, stripMerchantHtmlImages, submitContactTicket, toShopReviews, toStarCount, useCart, useNavigationLinks, useOnlineUserCount, useShopReviews, useStorefront, validateCheckoutFieldValue, validateCheckoutFields, validateContactSubmission };
package/dist/index.js CHANGED
@@ -2416,6 +2416,12 @@ function validateContactSubmission(values, context) {
2416
2416
  if (context.requireInvoice && !values.invoiceId.trim()) {
2417
2417
  return "An order or invoice ID is required.";
2418
2418
  }
2419
+ if (values.subject.trim().length > MAX_TITLE_LENGTH) {
2420
+ return `Keep the subject under ${MAX_TITLE_LENGTH} characters.`;
2421
+ }
2422
+ if (buildTicketMessage(values).length > MESSAGE_MAX_LENGTH) {
2423
+ return `Your message is too long. Keep it under ${MESSAGE_MAX_LENGTH.toLocaleString("en-US")} characters.`;
2424
+ }
2419
2425
  return null;
2420
2426
  }
2421
2427
  function requiredGlobal(name) {
@@ -2432,7 +2438,7 @@ function buildTicketMessage(values) {
2432
2438
  const body = values.message.trim();
2433
2439
  return `${name ? `Name: ${name}
2434
2440
 
2435
- ` : ""}${body}`.slice(0, MESSAGE_MAX_LENGTH);
2441
+ ` : ""}${body}`;
2436
2442
  }
2437
2443
  async function submitContactTicket(values, context) {
2438
2444
  const error = validateContactSubmission(values, context);
@@ -2448,7 +2454,7 @@ async function submitContactTicket(values, context) {
2448
2454
  headers: { "Content-Type": "application/json", Accept: "application/json" },
2449
2455
  body: JSON.stringify({
2450
2456
  email: values.email.trim().toLowerCase(),
2451
- title: subject.length >= MIN_SUBJECT_LENGTH ? subject.slice(0, MAX_TITLE_LENGTH) : DEFAULT_TICKET_TITLE,
2457
+ title: subject.length >= MIN_SUBJECT_LENGTH ? subject : DEFAULT_TICKET_TITLE,
2452
2458
  message: buildTicketMessage(values),
2453
2459
  ...invoiceId ? { invoice_id: invoiceId } : {}
2454
2460
  })
@@ -2571,6 +2577,21 @@ function reviewsForProduct(reviews, productTitle) {
2571
2577
  if (!title) return [];
2572
2578
  return reviews.filter((review) => review.productTitle === title);
2573
2579
  }
2580
+ async function loadLatestWrittenReviews(limit, labels) {
2581
+ const written = [];
2582
+ const seen = /* @__PURE__ */ new Set();
2583
+ let cursor = null;
2584
+ while (written.length < limit) {
2585
+ const result = await getShoppex().getShopReviewsPage(cursor);
2586
+ if (!result.success || !result.data) return null;
2587
+ written.push(...result.data.feedback.filter((row) => !row.is_automated && (row.comment ?? row.message ?? "").trim() !== ""));
2588
+ const next = result.data.pagination.has_more ? result.data.pagination.next_cursor : null;
2589
+ if (!next || seen.has(next)) break;
2590
+ seen.add(next);
2591
+ cursor = next;
2592
+ }
2593
+ return toShopReviews(written, labels).slice(0, limit);
2594
+ }
2574
2595
  function averageRating(reviews) {
2575
2596
  if (reviews.length === 0) return null;
2576
2597
  return reviews.reduce((total, review) => total + review.rating, 0) / reviews.length;
@@ -2743,6 +2764,7 @@ export {
2743
2764
  isProductSoldOut,
2744
2765
  isRecord,
2745
2766
  isSelectionSoldOut,
2767
+ loadLatestWrittenReviews,
2746
2768
  loadShopPage,
2747
2769
  missingRequiredCheckoutValues,
2748
2770
  optionalBoolean,