@shoppexio/storefront-react 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ Breaking:
6
+
7
+ - `settings.availableGateways` is gone. Use `settings.paymentMethods`: the
8
+ public payment methods the backend derives from the shop's live gateways,
9
+ each `{ id, label, kind }` with `kind` `card`, `wallet` or `crypto`,
10
+ already in display order. A theme no longer maps gateway codes itself.
11
+ - `resolveDeliveryPromise()` returns `{ kind: 'unspecified' }` when the
12
+ merchant set no delivery time. A missing delivery time used to read as
13
+ instant delivery; hosted checkout never promised that, and themes no longer
14
+ do. Handle the new kind in an exhaustive `switch`.
15
+
16
+ Added:
17
+
18
+ - Product meta: `shortDescription` (one plain-text line, at most 200
19
+ characters), the unit labels `unitLabelSingular` / `unitLabelPlural`, an
20
+ option's `description`, and `resolveYouReceive()` for a "You receive" line
21
+ that only states the real delivery type: keys for serial products, a
22
+ download for file products, nothing otherwise.
23
+ - Favorites: `<FavoritesProvider>` and `useFavorites()` for a heart on
24
+ cards and the product page. It shares one store with the account's
25
+ favorites tab (`FAVORITES_CHANGED_EVENT`) and reports `signed_out`
26
+ instead of failing when there is no customer session.
27
+ - Back-in-stock ("Notify me"): `canRequestStockAlert()` and
28
+ `requestStockAlert()`. `settings.stockAlertsEnabled` reflects the shop
29
+ toggle.
30
+ - Status page: `parseServiceStatus()` and `buildStatusPageModel()` for a
31
+ `/status` page. It lists only the products the merchant gave a status
32
+ (`operational`, `updating`, `detected`, `offline`).
33
+
34
+ Needs: a Shoppex backend that sends `payment_methods`,
35
+ `stock_alerts_enabled`, `short_description` and `service_status` in the
36
+ storefront payload. Parsing fails fast when a field is missing.
37
+
38
+ ## 0.1.5
39
+
40
+ - `loadShopPage()` returns `error` for a failed request or render and `missing`
41
+ only for a 404 or a nameless page. Needs Storefront SDK 1.0.87 for the
42
+ status; with an older SDK every failure is `error`.
43
+ - `usePagedShopReviews({ product })` and `loadShopReviewsPage()`: reviews one
44
+ page at a time with the API's summary, optionally for one product (catalog
45
+ `id`). A filtered answer must echo the filter, or it is refused.
46
+ `loadLatestWrittenReviews()` takes `{ product }` too. `ShopReview.automated`.
47
+ - Account: a warranty claim over 2,000 characters is refused with a message
48
+ naming the limit; the account root sets `dir="rtl"` for right-to-left locales.
49
+
3
50
  ## 0.1.4
4
51
 
5
52
  - Catalog cards are sold out when no option can be ordered, including a
package/README.md CHANGED
@@ -80,7 +80,10 @@ createRoot(document.getElementById('root')!).render(
80
80
 
81
81
  - Catalog: product and bundle cards, categories, sorting, filtering, paging.
82
82
  - Product pages: options, pricing per quantity, stock, badges, videos, the
83
- rating breakdown.
83
+ rating breakdown, a short description and a "You receive" line.
84
+ - Favorites on cards and product pages, "Notify me" for sold-out products,
85
+ and a `/status` page from the statuses the merchant sets.
86
+ - Payment method marks from the shop's live payment methods.
84
87
  - Cart: add, update, remove, coupons, quotes from the server, checkout
85
88
  hand-off, per-product checkout fields.
86
89
  - Reviews, shop pages, navigation menus and the contact form, from the SDK.
@@ -164,7 +167,10 @@ Most themes never need this.
164
167
  | Product | The product page model: variants, rating, pricing, quantity bounds, badges, videos | `buildProductDetail()`, `calculateLinePricing()`, `badgesForProductPage()` |
165
168
  | Cart | Cart state, quotes, coupons, checkout hand-off | `<CartProvider>`, `useCart()`, `buildCartView()`, `buildCartTotals()` |
166
169
  | Checkout fields | Per-product fields a buyer fills before checkout | `resolveCheckoutFields()`, `readCheckoutNeedsDetails()` |
167
- | Reviews, pages, menus, contact | SDK-backed hooks | `useShopReviews()`, `loadShopPage()`, `useNavigationLinks()`, `submitContactTicket()` |
170
+ | Favorites | The signed-in customer's saved products, shared with the account tab | `<FavoritesProvider>`, `useFavorites()` |
171
+ | Back in stock | "Notify me" on a sold-out product or option | `canRequestStockAlert()`, `requestStockAlert()` |
172
+ | Status page | The merchant-set status of each product | `buildStatusPageModel()`, `parseServiceStatus()` |
173
+ | Reviews, pages, menus, contact | SDK-backed hooks | `usePagedShopReviews()`, `loadLatestWrittenReviews()`, `loadShopPage()`, `useNavigationLinks()`, `submitContactTicket()` |
168
174
  | Merchant HTML | Sanitizing and flattening merchant rich text | `sanitizeMerchantHtml()`, `flattenMerchantHtmlToText()` |
169
175
  | Account routing | Mounting the account and its lazy chunk | `ACCOUNT_PATH`, `ACCOUNT_PATH_ALIAS`, `accountRouteMessage()`, `ACCOUNT_STOREFRONT_ROUTES`, `ACCOUNT_ARTIFACT_CAPABILITIES` |
170
176
 
package/dist/account.js CHANGED
@@ -4,9 +4,13 @@ import {
4
4
  accountTabHref,
5
5
  accountTabLabelKey,
6
6
  canonicalAccountPath,
7
+ favoriteProductHref,
7
8
  getShoppex,
8
- resolveAccountView
9
- } from "./chunk-D6XKDVAV.js";
9
+ narrowFavorites,
10
+ notifyFavoritesChanged,
11
+ resolveAccountView,
12
+ withoutFavorite
13
+ } from "./chunk-ZEFZPUAW.js";
10
14
 
11
15
  // src/account/account-page.tsx
12
16
  import { useEffect as useEffect5, useState as useState11 } from "react";
@@ -299,7 +303,8 @@ var ar_default = {
299
303
  warrantyClaimSend: "\u0625\u0631\u0633\u0627\u0644 \u0627\u0644\u0637\u0644\u0628",
300
304
  warrantyClaimSending: "\u062C\u0627\u0631\u064D \u0627\u0644\u0625\u0631\u0633\u0627\u0644\u2026",
301
305
  warrantyClaimDone: "\u0648\u0635\u0644 \u0637\u0644\u0628\u0643 \u0625\u0644\u0649 \u0627\u0644\u0645\u062A\u062C\u0631. \u0633\u064A\u0631\u062F\u0651\u0648\u0646 \u0639\u0628\u0631 \u0627\u0644\u0628\u0631\u064A\u062F \u0627\u0644\u0625\u0644\u0643\u062A\u0631\u0648\u0646\u064A.",
302
- warrantyClaimFailed: "\u062A\u0639\u0630\u0651\u0631 \u0625\u0631\u0633\u0627\u0644 \u0637\u0644\u0628\u0643."
306
+ warrantyClaimFailed: "\u062A\u0639\u0630\u0651\u0631 \u0625\u0631\u0633\u0627\u0644 \u0637\u0644\u0628\u0643.",
307
+ warrantyClaimTooLong: "\u0644\u0627 \u064A\u0645\u0643\u0646 \u0623\u0646 \u064A\u062A\u062C\u0627\u0648\u0632 \u0637\u0644\u0628\u0643 {max} \u062D\u0631\u0641."
303
308
  },
304
309
  downloads: {
305
310
  loading: "\u062C\u0627\u0631\u064D \u062A\u062D\u0645\u064A\u0644 \u062A\u0646\u0632\u064A\u0644\u0627\u062A\u0643",
@@ -764,7 +769,8 @@ var de_default = {
764
769
  warrantyClaimSend: "Fall senden",
765
770
  warrantyClaimSending: "Wird gesendet\u2026",
766
771
  warrantyClaimDone: "Der Shop hat deinen Fall erhalten. Die Antwort kommt per E-Mail.",
767
- warrantyClaimFailed: "Dein Fall konnte nicht gesendet werden."
772
+ warrantyClaimFailed: "Dein Fall konnte nicht gesendet werden.",
773
+ warrantyClaimTooLong: "Dein Fall darf h\xF6chstens {max} Zeichen lang sein."
768
774
  },
769
775
  downloads: {
770
776
  loading: "Deine Downloads werden geladen",
@@ -1229,7 +1235,8 @@ var en_default = {
1229
1235
  warrantyClaimSend: "Send claim",
1230
1236
  warrantyClaimSending: "Sending\u2026",
1231
1237
  warrantyClaimDone: "The store has your claim. They reply by email.",
1232
- warrantyClaimFailed: "Your claim could not be sent."
1238
+ warrantyClaimFailed: "Your claim could not be sent.",
1239
+ warrantyClaimTooLong: "Your claim can be at most {max} characters."
1233
1240
  },
1234
1241
  downloads: {
1235
1242
  loading: "Loading your downloads",
@@ -1694,7 +1701,8 @@ var es_default = {
1694
1701
  warrantyClaimSend: "Enviar reclamaci\xF3n",
1695
1702
  warrantyClaimSending: "Enviando\u2026",
1696
1703
  warrantyClaimDone: "La tienda ha recibido tu reclamaci\xF3n. Te responder\xE1n por correo.",
1697
- warrantyClaimFailed: "No se ha podido enviar tu reclamaci\xF3n."
1704
+ warrantyClaimFailed: "No se ha podido enviar tu reclamaci\xF3n.",
1705
+ warrantyClaimTooLong: "Tu reclamaci\xF3n no puede superar los {max} caracteres."
1698
1706
  },
1699
1707
  downloads: {
1700
1708
  loading: "Cargando tus descargas",
@@ -2159,7 +2167,8 @@ var fi_default = {
2159
2167
  warrantyClaimSend: "L\xE4het\xE4 vaatimus",
2160
2168
  warrantyClaimSending: "L\xE4hetet\xE4\xE4n\u2026",
2161
2169
  warrantyClaimDone: "Kauppa sai vaatimuksesi. He vastaavat s\xE4hk\xF6postitse.",
2162
- warrantyClaimFailed: "Vaatimustasi ei voitu l\xE4hett\xE4\xE4."
2170
+ warrantyClaimFailed: "Vaatimustasi ei voitu l\xE4hett\xE4\xE4.",
2171
+ warrantyClaimTooLong: "Vaatimuksessa saa olla enint\xE4\xE4n {max} merkki\xE4."
2163
2172
  },
2164
2173
  downloads: {
2165
2174
  loading: "Ladataan latauksiasi",
@@ -2624,7 +2633,8 @@ var fr_default = {
2624
2633
  warrantyClaimSend: "Envoyer la demande",
2625
2634
  warrantyClaimSending: "Envoi en cours\u2026",
2626
2635
  warrantyClaimDone: "La boutique a bien re\xE7u votre demande. Elle r\xE9pond par e-mail.",
2627
- warrantyClaimFailed: "Votre demande n'a pas pu \xEAtre envoy\xE9e."
2636
+ warrantyClaimFailed: "Votre demande n'a pas pu \xEAtre envoy\xE9e.",
2637
+ warrantyClaimTooLong: "Votre demande ne peut pas d\xE9passer {max} caract\xE8res."
2628
2638
  },
2629
2639
  downloads: {
2630
2640
  loading: "Chargement de vos t\xE9l\xE9chargements",
@@ -3089,7 +3099,8 @@ var hi_default = {
3089
3099
  warrantyClaimSend: "\u0926\u093E\u0935\u093E \u092D\u0947\u091C\u0947\u0902",
3090
3100
  warrantyClaimSending: "\u092D\u0947\u091C\u093E \u091C\u093E \u0930\u0939\u093E \u0939\u0948\u2026",
3091
3101
  warrantyClaimDone: "\u0938\u094D\u091F\u094B\u0930 \u0915\u094B \u0906\u092A\u0915\u093E \u0926\u093E\u0935\u093E \u092E\u093F\u0932 \u0917\u092F\u093E\u0964 \u091C\u0935\u093E\u092C \u0908\u092E\u0947\u0932 \u0938\u0947 \u0906\u090F\u0917\u093E\u0964",
3092
- warrantyClaimFailed: "\u0906\u092A\u0915\u093E \u0926\u093E\u0935\u093E \u0928\u0939\u0940\u0902 \u092D\u0947\u091C\u093E \u091C\u093E \u0938\u0915\u093E\u0964"
3102
+ warrantyClaimFailed: "\u0906\u092A\u0915\u093E \u0926\u093E\u0935\u093E \u0928\u0939\u0940\u0902 \u092D\u0947\u091C\u093E \u091C\u093E \u0938\u0915\u093E\u0964",
3103
+ warrantyClaimTooLong: "\u0906\u092A\u0915\u093E \u0926\u093E\u0935\u093E {max} \u0935\u0930\u094D\u0923\u094B\u0902 \u0938\u0947 \u0905\u0927\u093F\u0915 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u0924\u093E\u0964"
3093
3104
  },
3094
3105
  downloads: {
3095
3106
  loading: "\u0906\u092A\u0915\u0947 \u0921\u093E\u0909\u0928\u0932\u094B\u0921 \u0932\u094B\u0921 \u0939\u094B \u0930\u0939\u0947 \u0939\u0948\u0902",
@@ -3554,7 +3565,8 @@ var it_default = {
3554
3565
  warrantyClaimSend: "Invia richiesta",
3555
3566
  warrantyClaimSending: "Invio in corso\u2026",
3556
3567
  warrantyClaimDone: "Il negozio ha ricevuto la tua richiesta. Ti rispondono via e-mail.",
3557
- warrantyClaimFailed: "Non \xE8 stato possibile inviare la richiesta."
3568
+ warrantyClaimFailed: "Non \xE8 stato possibile inviare la richiesta.",
3569
+ warrantyClaimTooLong: "La richiesta non pu\xF2 superare i {max} caratteri."
3558
3570
  },
3559
3571
  downloads: {
3560
3572
  loading: "Caricamento dei tuoi download",
@@ -4019,7 +4031,8 @@ var lt_default = {
4019
4031
  warrantyClaimSend: "Si\u0173sti pra\u0161ym\u0105",
4020
4032
  warrantyClaimSending: "Siun\u010Diama\u2026",
4021
4033
  warrantyClaimDone: "Parduotuv\u0117 gavo j\u016Bs\u0173 pra\u0161ym\u0105. Atsakymas ateis el. pa\u0161tu.",
4022
- warrantyClaimFailed: "J\u016Bs\u0173 pra\u0161ymo i\u0161si\u0173sti nepavyko."
4034
+ warrantyClaimFailed: "J\u016Bs\u0173 pra\u0161ymo i\u0161si\u0173sti nepavyko.",
4035
+ warrantyClaimTooLong: "Pra\u0161ymas negali vir\u0161yti {max} simboli\u0173."
4023
4036
  },
4024
4037
  downloads: {
4025
4038
  loading: "\u012Ekeliami j\u016Bs\u0173 atsisiuntimai",
@@ -4484,7 +4497,8 @@ var nl_default = {
4484
4497
  warrantyClaimSend: "Melding versturen",
4485
4498
  warrantyClaimSending: "Versturen\u2026",
4486
4499
  warrantyClaimDone: "De winkel heeft je melding ontvangen. Ze antwoorden per e-mail.",
4487
- warrantyClaimFailed: "Je melding kon niet worden verstuurd."
4500
+ warrantyClaimFailed: "Je melding kon niet worden verstuurd.",
4501
+ warrantyClaimTooLong: "Je melding mag maximaal {max} tekens bevatten."
4488
4502
  },
4489
4503
  downloads: {
4490
4504
  loading: "Je downloads worden geladen",
@@ -4949,7 +4963,8 @@ var pl_default = {
4949
4963
  warrantyClaimSend: "Wy\u015Blij zg\u0142oszenie",
4950
4964
  warrantyClaimSending: "Wysy\u0142anie\u2026",
4951
4965
  warrantyClaimDone: "Sklep otrzyma\u0142 Twoje zg\u0142oszenie. Odpowied\u017A przyjdzie e-mailem.",
4952
- warrantyClaimFailed: "Nie uda\u0142o si\u0119 wys\u0142a\u0107 zg\u0142oszenia."
4966
+ warrantyClaimFailed: "Nie uda\u0142o si\u0119 wys\u0142a\u0107 zg\u0142oszenia.",
4967
+ warrantyClaimTooLong: "Zg\u0142oszenie mo\u017Ce mie\u0107 najwy\u017Cej {max} znak\xF3w."
4953
4968
  },
4954
4969
  downloads: {
4955
4970
  loading: "Wczytujemy Twoje pliki",
@@ -5414,7 +5429,8 @@ var pt_default = {
5414
5429
  warrantyClaimSend: "Enviar pedido",
5415
5430
  warrantyClaimSending: "A enviar\u2026",
5416
5431
  warrantyClaimDone: "A loja recebeu o teu pedido. Respondem por e-mail.",
5417
- warrantyClaimFailed: "N\xE3o foi poss\xEDvel enviar o teu pedido."
5432
+ warrantyClaimFailed: "N\xE3o foi poss\xEDvel enviar o teu pedido.",
5433
+ warrantyClaimTooLong: "O teu pedido n\xE3o pode ter mais de {max} caracteres."
5418
5434
  },
5419
5435
  downloads: {
5420
5436
  loading: "A carregar as tuas transfer\xEAncias",
@@ -5879,7 +5895,8 @@ var ru_default = {
5879
5895
  warrantyClaimSend: "\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
5880
5896
  warrantyClaimSending: "\u041E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u043C\u2026",
5881
5897
  warrantyClaimDone: "\u041C\u0430\u0433\u0430\u0437\u0438\u043D \u043F\u043E\u043B\u0443\u0447\u0438\u043B \u0432\u0430\u0448\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435. \u041E\u0442\u0432\u0435\u0442 \u043F\u0440\u0438\u0434\u0451\u0442 \u043D\u0430 \u043F\u043E\u0447\u0442\u0443.",
5882
- warrantyClaimFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435."
5898
+ warrantyClaimFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435.",
5899
+ warrantyClaimTooLong: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u0440\u0435\u0432\u044B\u0448\u0430\u0442\u044C {max} \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432."
5883
5900
  },
5884
5901
  downloads: {
5885
5902
  loading: "\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043C \u0432\u0430\u0448\u0438 \u0444\u0430\u0439\u043B\u044B",
@@ -6344,7 +6361,8 @@ var tr_default = {
6344
6361
  warrantyClaimSend: "Talebi g\xF6nder",
6345
6362
  warrantyClaimSending: "G\xF6nderiliyor\u2026",
6346
6363
  warrantyClaimDone: "Ma\u011Faza talebinizi ald\u0131. E-posta ile yan\u0131t verirler.",
6347
- warrantyClaimFailed: "Talebiniz g\xF6nderilemedi."
6364
+ warrantyClaimFailed: "Talebiniz g\xF6nderilemedi.",
6365
+ warrantyClaimTooLong: "Talebiniz en fazla {max} karakter olabilir."
6348
6366
  },
6349
6367
  downloads: {
6350
6368
  loading: "\u0130ndirmeleriniz y\xFCkleniyor",
@@ -6809,7 +6827,8 @@ var uk_default = {
6809
6827
  warrantyClaimSend: "\u041D\u0430\u0434\u0456\u0441\u043B\u0430\u0442\u0438 \u0437\u0432\u0435\u0440\u043D\u0435\u043D\u043D\u044F",
6810
6828
  warrantyClaimSending: "\u041D\u0430\u0434\u0441\u0438\u043B\u0430\u0454\u043C\u043E\u2026",
6811
6829
  warrantyClaimDone: "\u041C\u0430\u0433\u0430\u0437\u0438\u043D \u043E\u0442\u0440\u0438\u043C\u0430\u0432 \u0432\u0430\u0448\u0435 \u0437\u0432\u0435\u0440\u043D\u0435\u043D\u043D\u044F. \u0412\u0456\u0434\u043F\u043E\u0432\u0456\u0434\u044C \u043D\u0430\u0434\u0456\u0439\u0434\u0435 \u043D\u0430 \u043F\u043E\u0448\u0442\u0443.",
6812
- warrantyClaimFailed: "\u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u043D\u0430\u0434\u0456\u0441\u043B\u0430\u0442\u0438 \u0437\u0432\u0435\u0440\u043D\u0435\u043D\u043D\u044F."
6830
+ warrantyClaimFailed: "\u041D\u0435 \u0432\u0434\u0430\u043B\u043E\u0441\u044F \u043D\u0430\u0434\u0456\u0441\u043B\u0430\u0442\u0438 \u0437\u0432\u0435\u0440\u043D\u0435\u043D\u043D\u044F.",
6831
+ warrantyClaimTooLong: "\u0417\u0432\u0435\u0440\u043D\u0435\u043D\u043D\u044F \u043D\u0435 \u043C\u0430\u0454 \u043F\u0435\u0440\u0435\u0432\u0438\u0449\u0443\u0432\u0430\u0442\u0438 {max} \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432."
6813
6832
  },
6814
6833
  downloads: {
6815
6834
  loading: "\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0443\u0454\u043C\u043E \u0432\u0430\u0448\u0456 \u0444\u0430\u0439\u043B\u0438",
@@ -7274,7 +7293,8 @@ var zh_default = {
7274
7293
  warrantyClaimSend: "\u63D0\u4EA4\u7533\u8BF7",
7275
7294
  warrantyClaimSending: "\u63D0\u4EA4\u4E2D\u2026",
7276
7295
  warrantyClaimDone: "\u5546\u5E97\u5DF2\u6536\u5230\u4F60\u7684\u7533\u8BF7\uFF0C\u4ED6\u4EEC\u4F1A\u901A\u8FC7\u90AE\u4EF6\u56DE\u590D\u3002",
7277
- warrantyClaimFailed: "\u7533\u8BF7\u63D0\u4EA4\u5931\u8D25\u3002"
7296
+ warrantyClaimFailed: "\u7533\u8BF7\u63D0\u4EA4\u5931\u8D25\u3002",
7297
+ warrantyClaimTooLong: "\u7533\u8BF7\u5185\u5BB9\u4E0D\u80FD\u8D85\u8FC7 {max} \u4E2A\u5B57\u7B26\u3002"
7278
7298
  },
7279
7299
  downloads: {
7280
7300
  loading: "\u6B63\u5728\u52A0\u8F7D\u4F60\u7684\u4E0B\u8F7D\u5185\u5BB9",
@@ -7617,6 +7637,7 @@ var MESSAGE_CATALOGS = {
7617
7637
  zh: zh_default
7618
7638
  };
7619
7639
  var EN_MESSAGES = MESSAGE_CATALOGS.en;
7640
+ var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
7620
7641
  function isSupportedLocale(value) {
7621
7642
  return SUPPORTED_LOCALES.includes(value);
7622
7643
  }
@@ -7624,6 +7645,9 @@ function resolveLocale(lang) {
7624
7645
  const base = (lang ?? "").trim().toLowerCase().split(/[-_]/)[0] ?? "";
7625
7646
  return isSupportedLocale(base) ? base : DEFAULT_LOCALE;
7626
7647
  }
7648
+ function isRightToLeft(locale) {
7649
+ return RTL_LOCALES.has(locale);
7650
+ }
7627
7651
  function messageAt(catalog, key) {
7628
7652
  let value = catalog;
7629
7653
  for (const segment of key.split(".")) {
@@ -8598,52 +8622,6 @@ function CodeRow({ code }) {
8598
8622
  // src/account/favorites.tsx
8599
8623
  import { useEffect as useEffect2, useState as useState3 } from "react";
8600
8624
  import { Link as Link3 } from "react-router-dom";
8601
-
8602
- // src/account/account-favorites.ts
8603
- function isRecord2(value) {
8604
- return typeof value === "object" && value !== null && !Array.isArray(value);
8605
- }
8606
- function readNullableString(record, key) {
8607
- const value = record[key];
8608
- if (value === null || typeof value === "string") return value;
8609
- return void 0;
8610
- }
8611
- function narrowFavorites(payload) {
8612
- if (!isRecord2(payload) || !Array.isArray(payload.favorites)) return { ok: false };
8613
- const favorites = [];
8614
- for (const entry of payload.favorites) {
8615
- if (!isRecord2(entry)) return { ok: false };
8616
- const productUniqid = entry.product_uniqid;
8617
- const title = entry.title;
8618
- const slug = readNullableString(entry, "slug");
8619
- const imageUrl = readNullableString(entry, "image_url");
8620
- const priceDisplay = entry.price_display;
8621
- const unavailable = entry.unavailable;
8622
- const createdAt = entry.created_at;
8623
- if (typeof productUniqid !== "string" || typeof title !== "string" || slug === void 0 || imageUrl === void 0 || typeof priceDisplay !== "number" || !Number.isFinite(priceDisplay) || typeof unavailable !== "boolean" || typeof createdAt !== "string") {
8624
- return { ok: false };
8625
- }
8626
- favorites.push({
8627
- product_uniqid: productUniqid,
8628
- title,
8629
- slug,
8630
- image_url: imageUrl,
8631
- price_display: priceDisplay,
8632
- unavailable,
8633
- created_at: createdAt
8634
- });
8635
- }
8636
- return { ok: true, favorites };
8637
- }
8638
- function favoriteProductHref(favorite) {
8639
- if (favorite.unavailable || !favorite.slug) return null;
8640
- return `/product/${encodeURIComponent(favorite.slug)}`;
8641
- }
8642
- function withoutFavorite(favorites, productUniqid) {
8643
- return favorites.filter((favorite) => favorite.product_uniqid !== productUniqid);
8644
- }
8645
-
8646
- // src/account/favorites.tsx
8647
8625
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
8648
8626
  function AccountFavorites() {
8649
8627
  const { state, reload } = useAccountResource("favorites", () => callCustomerApi((sdk) => sdk.favorites()));
@@ -8689,6 +8667,7 @@ function AccountFavorites() {
8689
8667
  setRemoveError(result.message?.trim() || t("favorites.removeFailed"));
8690
8668
  return;
8691
8669
  }
8670
+ notifyFavoritesChanged();
8692
8671
  setFavorites((current) => current ? withoutFavorite(current, favorite.product_uniqid) : current);
8693
8672
  };
8694
8673
  return /* @__PURE__ */ jsxs4("div", { children: [
@@ -9060,6 +9039,9 @@ function canClaimWarranty(warranty, now = /* @__PURE__ */ new Date()) {
9060
9039
  return expires !== null && expires > now.getTime();
9061
9040
  }
9062
9041
  var WARRANTY_CLAIM_MESSAGE_MAX = 2e3;
9042
+ function isWarrantyClaimTooLong(input) {
9043
+ return input.trim().length > WARRANTY_CLAIM_MESSAGE_MAX;
9044
+ }
9063
9045
  function warrantyClaimMessage(input) {
9064
9046
  const message = input.trim();
9065
9047
  return message.length === 0 ? void 0 : message;
@@ -9351,6 +9333,10 @@ function WarrantyRow({
9351
9333
  const claimKey = warrantyClaimKey(warranty.claim_status);
9352
9334
  const fieldId = `warranty-claim-${warranty.uniqid}`;
9353
9335
  const send = async () => {
9336
+ if (isWarrantyClaimTooLong(message)) {
9337
+ setError(t("orders.warrantyClaimTooLong", { max: WARRANTY_CLAIM_MESSAGE_MAX }));
9338
+ return;
9339
+ }
9354
9340
  setSending(true);
9355
9341
  setError(null);
9356
9342
  const response = await callCustomerApi((sdk) => sdk.claimWarranty(
@@ -9402,7 +9388,7 @@ function WarrantyRow({
9402
9388
  rows: 3,
9403
9389
  value: message,
9404
9390
  disabled: sending,
9405
- maxLength: WARRANTY_CLAIM_MESSAGE_MAX,
9391
+ "aria-invalid": error !== null || void 0,
9406
9392
  onChange: (event) => {
9407
9393
  setMessage(event.target.value);
9408
9394
  setError(null);
@@ -11237,21 +11223,21 @@ function grantStatusLabel(status) {
11237
11223
  import { useEffect as useEffect4, useId as useId2, useRef as useRef4, useState as useState8 } from "react";
11238
11224
 
11239
11225
  // src/account/account-settings.ts
11240
- function isRecord3(value) {
11226
+ function isRecord2(value) {
11241
11227
  return typeof value === "object" && value !== null && !Array.isArray(value);
11242
11228
  }
11243
- function readNullableString2(record, key) {
11229
+ function readNullableString(record, key) {
11244
11230
  const value = record[key];
11245
11231
  if (value === null || typeof value === "string") return value;
11246
11232
  return void 0;
11247
11233
  }
11248
11234
  function narrowEmailPreferences(payload) {
11249
- if (!isRecord3(payload) || typeof payload.global_unsubscribed !== "boolean") return { ok: false };
11235
+ if (!isRecord2(payload) || typeof payload.global_unsubscribed !== "boolean") return { ok: false };
11250
11236
  if (!Array.isArray(payload.lists)) return { ok: false };
11251
11237
  const lists = [];
11252
11238
  for (const entry of payload.lists) {
11253
- if (!isRecord3(entry)) return { ok: false };
11254
- const description = readNullableString2(entry, "description");
11239
+ if (!isRecord2(entry)) return { ok: false };
11240
+ const description = readNullableString(entry, "description");
11255
11241
  if (typeof entry.id !== "string" || typeof entry.name !== "string" || description === void 0 || typeof entry.subscribed !== "boolean") {
11256
11242
  return { ok: false };
11257
11243
  }
@@ -11260,12 +11246,12 @@ function narrowEmailPreferences(payload) {
11260
11246
  return { ok: true, preferences: { global_unsubscribed: payload.global_unsubscribed, lists } };
11261
11247
  }
11262
11248
  function narrowCustomerSessions(payload) {
11263
- if (!isRecord3(payload) || !Array.isArray(payload.sessions)) return { ok: false };
11249
+ if (!isRecord2(payload) || !Array.isArray(payload.sessions)) return { ok: false };
11264
11250
  const sessions = [];
11265
11251
  for (const entry of payload.sessions) {
11266
- if (!isRecord3(entry)) return { ok: false };
11267
- const ipAddress = readNullableString2(entry, "ip_address");
11268
- const userAgent = readNullableString2(entry, "user_agent");
11252
+ if (!isRecord2(entry)) return { ok: false };
11253
+ const ipAddress = readNullableString(entry, "ip_address");
11254
+ const userAgent = readNullableString(entry, "user_agent");
11269
11255
  if (typeof entry.id !== "string" || typeof entry.created_at !== "string" || typeof entry.expires_at !== "string" || ipAddress === void 0 || userAgent === void 0 || typeof entry.current !== "boolean") {
11270
11256
  return { ok: false };
11271
11257
  }
@@ -11830,7 +11816,7 @@ function billingMessage(plan) {
11830
11816
  function cancelsAtPeriodEnd(gateway) {
11831
11817
  return gateway.trim().toUpperCase() !== "PAYPAL";
11832
11818
  }
11833
- function isRecord4(value) {
11819
+ function isRecord3(value) {
11834
11820
  return typeof value === "object" && value !== null && !Array.isArray(value);
11835
11821
  }
11836
11822
  function readString(record, key) {
@@ -11838,10 +11824,10 @@ function readString(record, key) {
11838
11824
  return typeof value === "string" ? value : null;
11839
11825
  }
11840
11826
  function narrowBillingHistory(payload) {
11841
- if (!isRecord4(payload) || !Array.isArray(payload.invoices)) return { ok: false };
11827
+ if (!isRecord3(payload) || !Array.isArray(payload.invoices)) return { ok: false };
11842
11828
  const invoices = [];
11843
11829
  for (const entry of payload.invoices) {
11844
- if (!isRecord4(entry)) return { ok: false };
11830
+ if (!isRecord3(entry)) return { ok: false };
11845
11831
  const uniqid = readString(entry, "uniqid");
11846
11832
  const total = readString(entry, "total");
11847
11833
  const currency = readString(entry, "currency");
@@ -12108,10 +12094,10 @@ function BillingHistoryList({ payload, reload }) {
12108
12094
  import { useId as useId3, useState as useState10 } from "react";
12109
12095
 
12110
12096
  // src/account/account-support.ts
12111
- function isRecord5(value) {
12097
+ function isRecord4(value) {
12112
12098
  return typeof value === "object" && value !== null && !Array.isArray(value);
12113
12099
  }
12114
- function readNullableString3(record, key) {
12100
+ function readNullableString2(record, key) {
12115
12101
  const value = record[key];
12116
12102
  if (value === null || typeof value === "string") return value;
12117
12103
  return void 0;
@@ -12120,12 +12106,12 @@ function isSupportMessageRole(value) {
12120
12106
  return value === "CUSTOMER" || value === "MERCHANT" || value === "SYSTEM";
12121
12107
  }
12122
12108
  function narrowSupportTicket(payload) {
12123
- if (!isRecord5(payload) || !isRecord5(payload.ticket)) return { ok: false };
12109
+ if (!isRecord4(payload) || !isRecord4(payload.ticket)) return { ok: false };
12124
12110
  const raw = payload.ticket;
12125
12111
  const uniqid = raw.uniqid;
12126
- const title = readNullableString3(raw, "title");
12112
+ const title = readNullableString2(raw, "title");
12127
12113
  const status = raw.status;
12128
- const invoiceId = readNullableString3(raw, "invoice_id");
12114
+ const invoiceId = readNullableString2(raw, "invoice_id");
12129
12115
  const createdAt = raw.created_at;
12130
12116
  const updatedAt = raw.updated_at;
12131
12117
  if (typeof uniqid !== "string" || title === void 0 || typeof status !== "string" || invoiceId === void 0 || typeof createdAt !== "string" || typeof updatedAt !== "string" || !Array.isArray(raw.messages)) {
@@ -12133,7 +12119,7 @@ function narrowSupportTicket(payload) {
12133
12119
  }
12134
12120
  const messages = [];
12135
12121
  for (const entry of raw.messages) {
12136
- if (!isRecord5(entry)) return { ok: false };
12122
+ if (!isRecord4(entry)) return { ok: false };
12137
12123
  const { id, role, message, created_at: messageCreatedAt } = entry;
12138
12124
  if (typeof id !== "string" || !isSupportMessageRole(role) || typeof message !== "string" || typeof messageCreatedAt !== "string") {
12139
12125
  return { ok: false };
@@ -12169,7 +12155,7 @@ function messageAuthorKey(role) {
12169
12155
  return "support.fromMerchant";
12170
12156
  }
12171
12157
  function readCreatedTicketUniqid(data) {
12172
- if (!isRecord5(data)) return null;
12158
+ if (!isRecord4(data)) return null;
12173
12159
  const uniqid = data.uniqid;
12174
12160
  return typeof uniqid === "string" && uniqid.length > 0 ? uniqid : null;
12175
12161
  }
@@ -12567,47 +12553,55 @@ function AccountPage({ frame: Frame, notFound }) {
12567
12553
  };
12568
12554
  if (canonical !== null) return /* @__PURE__ */ jsx15(Navigate, { to: `${canonical}${remainingSearch(search)}`, replace: true });
12569
12555
  if (view.kind === "unknown-tab") return /* @__PURE__ */ jsx15(Fragment3, { children: notFound });
12570
- return /* @__PURE__ */ jsx15(Frame, { children: /* @__PURE__ */ jsxs14("section", { className: "sx-account sxa:space-y-8 sxa:py-6 sxa:md:py-8", "data-sx-account-session": session.status, children: [
12571
- /* @__PURE__ */ jsxs14("div", { className: "sx-account-header sxa:flex sxa:flex-wrap sxa:items-end sxa:justify-between sxa:gap-4", children: [
12572
- /* @__PURE__ */ jsx15("h1", { className: "sx-account-title sxa:font-heading sxa:text-h1", children: t("account.title") }),
12573
- session.status === "signed-in" ? /* @__PURE__ */ jsxs14(
12574
- "button",
12575
- {
12576
- type: "button",
12577
- onClick: () => void signOut(),
12578
- disabled: signingOut,
12579
- "aria-busy": signingOut || void 0,
12580
- className: "sx-account-signout sx-account-button sxa:card-button sxa:card-button--add sxa:disabled:cursor-not-allowed sxa:disabled:opacity-50",
12581
- children: [
12582
- signingOut ? /* @__PURE__ */ jsx15(BusySpinner, { className: "sxa:mr-1.5 sxa:h-3.5 sxa:w-3.5" }) : null,
12583
- signingOut ? t("account.signingOut") : t("account.signOut")
12584
- ]
12585
- }
12586
- ) : null
12587
- ] }),
12588
- session.status === "checking" ? /* @__PURE__ */ jsx15(AccountRowsSkeleton, { rows: 3, label: t("account.checkingSession") }) : null,
12589
- session.status === "signed-out" ? /* @__PURE__ */ jsx15(
12590
- AccountLogin,
12591
- {
12592
- prefill,
12593
- onSignedIn: () => {
12594
- setPrefill(spendSignInPrefill());
12595
- setSession({ status: "signed-in" });
12596
- }
12597
- }
12598
- ) : null,
12599
- session.status === "unknown" ? /* @__PURE__ */ jsx15("div", { className: "sxa:mx-auto sxa:w-full sxa:max-w-md", children: /* @__PURE__ */ jsx15(
12600
- AccountError,
12601
- {
12602
- message: session.message,
12603
- onRetry: () => {
12604
- setSession({ status: "checking" });
12605
- setProbeToken((token) => token + 1);
12606
- }
12607
- }
12608
- ) }) : null,
12609
- session.status === "signed-in" ? /* @__PURE__ */ jsx15(SignedInAccount, { view, signOutError }) : null
12610
- ] }) });
12556
+ return /* @__PURE__ */ jsx15(Frame, { children: /* @__PURE__ */ jsxs14(
12557
+ "section",
12558
+ {
12559
+ className: "sx-account sxa:space-y-8 sxa:py-6 sxa:md:py-8",
12560
+ dir: isRightToLeft(getActiveLocale()) ? "rtl" : "ltr",
12561
+ "data-sx-account-session": session.status,
12562
+ children: [
12563
+ /* @__PURE__ */ jsxs14("div", { className: "sx-account-header sxa:flex sxa:flex-wrap sxa:items-end sxa:justify-between sxa:gap-4", children: [
12564
+ /* @__PURE__ */ jsx15("h1", { className: "sx-account-title sxa:font-heading sxa:text-h1", children: t("account.title") }),
12565
+ session.status === "signed-in" ? /* @__PURE__ */ jsxs14(
12566
+ "button",
12567
+ {
12568
+ type: "button",
12569
+ onClick: () => void signOut(),
12570
+ disabled: signingOut,
12571
+ "aria-busy": signingOut || void 0,
12572
+ className: "sx-account-signout sx-account-button sxa:card-button sxa:card-button--add sxa:disabled:cursor-not-allowed sxa:disabled:opacity-50",
12573
+ children: [
12574
+ signingOut ? /* @__PURE__ */ jsx15(BusySpinner, { className: "sxa:mr-1.5 sxa:h-3.5 sxa:w-3.5" }) : null,
12575
+ signingOut ? t("account.signingOut") : t("account.signOut")
12576
+ ]
12577
+ }
12578
+ ) : null
12579
+ ] }),
12580
+ session.status === "checking" ? /* @__PURE__ */ jsx15(AccountRowsSkeleton, { rows: 3, label: t("account.checkingSession") }) : null,
12581
+ session.status === "signed-out" ? /* @__PURE__ */ jsx15(
12582
+ AccountLogin,
12583
+ {
12584
+ prefill,
12585
+ onSignedIn: () => {
12586
+ setPrefill(spendSignInPrefill());
12587
+ setSession({ status: "signed-in" });
12588
+ }
12589
+ }
12590
+ ) : null,
12591
+ session.status === "unknown" ? /* @__PURE__ */ jsx15("div", { className: "sxa:mx-auto sxa:w-full sxa:max-w-md", children: /* @__PURE__ */ jsx15(
12592
+ AccountError,
12593
+ {
12594
+ message: session.message,
12595
+ onRetry: () => {
12596
+ setSession({ status: "checking" });
12597
+ setProbeToken((token) => token + 1);
12598
+ }
12599
+ }
12600
+ ) }) : null,
12601
+ session.status === "signed-in" ? /* @__PURE__ */ jsx15(SignedInAccount, { view, signOutError }) : null
12602
+ ]
12603
+ }
12604
+ ) });
12611
12605
  }
12612
12606
  function SignedInAccount({
12613
12607
  view,