orionplace-react 0.1.0 → 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.
Files changed (633) hide show
  1. package/README.md +276 -35
  2. package/bin/orionplace-react.mjs +4 -1
  3. package/package.json +18 -5
  4. package/runtime/app.d.mts +6 -0
  5. package/runtime/app.mjs +48 -0
  6. package/runtime/marketplace.d.mts +13 -8
  7. package/runtime/marketplace.mjs +287 -211
  8. package/src/project-readme.mjs +95 -35
  9. package/src/setup.mjs +36 -13
  10. package/templates/app/StorefrontShell.tsx +435 -0
  11. package/templates/app/[[...path]]/page.app.tsx +11 -0
  12. package/templates/app/_storefront/ActivityPage.tsx +23 -0
  13. package/templates/app/_storefront/AddressesPage.tsx +29 -0
  14. package/templates/app/_storefront/AppRoutes.tsx +182 -0
  15. package/templates/app/_storefront/ArticlePage.tsx +81 -0
  16. package/templates/app/_storefront/CartPage.tsx +53 -0
  17. package/templates/app/_storefront/CatalogPage.tsx +37 -0
  18. package/templates/app/_storefront/CategoryPage.tsx +64 -0
  19. package/templates/app/_storefront/ContactPage.tsx +32 -0
  20. package/templates/app/_storefront/HomePage.tsx +140 -0
  21. package/templates/app/_storefront/JournalPage.tsx +36 -0
  22. package/templates/app/_storefront/LogoutPage.tsx +91 -0
  23. package/templates/app/_storefront/ManufacturersPage.tsx +64 -0
  24. package/templates/app/_storefront/OrdersPage.tsx +129 -0
  25. package/templates/app/_storefront/ProductPage.tsx +105 -0
  26. package/templates/app/_storefront/ProfilePage.tsx +75 -0
  27. package/templates/app/_storefront/PromoPage.tsx +66 -0
  28. package/templates/app/_storefront/PromocodesPage.tsx +17 -0
  29. package/templates/app/_storefront/SearchPage.tsx +220 -0
  30. package/templates/app/_storefront/SellersPage.tsx +160 -0
  31. package/templates/app/_storefront/StorefrontErrorRoute.tsx +42 -0
  32. package/templates/app/_storefront/StorefrontPageMetadata.tsx +74 -0
  33. package/templates/app/_storefront/StorefrontPageStatus.tsx +61 -0
  34. package/templates/app/_storefront/StoresPage.tsx +53 -0
  35. package/templates/app/_storefront/SubscriptionsPage.tsx +62 -0
  36. package/templates/app/_storefront/SupportPage.tsx +141 -0
  37. package/templates/app/_storefront/TariffsPage.tsx +180 -0
  38. package/templates/app/_storefront/UnsubscribePage.tsx +29 -0
  39. package/templates/app/_storefront/WalletPage.tsx +78 -0
  40. package/templates/app/_storefront/WishlistPage.tsx +79 -0
  41. package/templates/app/_storefront/storefrontRoutes.tsx +738 -0
  42. package/templates/app/_storefront/useAccountWallet.ts +27 -0
  43. package/templates/app/_storefront/useBlogComments.ts +389 -0
  44. package/templates/app/_storefront/useProductSearch.ts +171 -0
  45. package/templates/app/_storefront/useStorefrontNavigation.ts +36 -0
  46. package/templates/app/activity/page.tsx +9 -0
  47. package/templates/app/addresses/page.tsx +9 -0
  48. package/templates/app/api/[...orionplace]/route.ts +11 -4
  49. package/templates/app/browse/page.tsx +27 -0
  50. package/templates/app/cart/page.tsx +9 -0
  51. package/templates/app/catalog/page.tsx +9 -0
  52. package/templates/app/category/[id]/page.tsx +9 -0
  53. package/templates/app/contact/page.tsx +9 -0
  54. package/templates/app/error.tsx +7 -0
  55. package/templates/app/featured/page.tsx +9 -0
  56. package/templates/app/global-error.tsx +61 -0
  57. package/templates/app/globals.css +50 -2
  58. package/templates/app/journal/[id]/page.tsx +9 -0
  59. package/templates/app/journal/page.tsx +9 -0
  60. package/templates/app/layout.app.tsx +20 -0
  61. package/templates/app/layout.tsx +44 -10
  62. package/templates/app/logout/page.tsx +9 -0
  63. package/templates/app/manufacturers/page.tsx +9 -0
  64. package/templates/app/not-found.tsx +5 -0
  65. package/templates/app/orders/page.tsx +9 -0
  66. package/templates/app/page.module.css +12 -8
  67. package/templates/app/page.tsx +8 -79
  68. package/templates/app/product/[id]/page.tsx +9 -0
  69. package/templates/app/profile/page.tsx +9 -0
  70. package/templates/app/promo/[id]/page.tsx +9 -0
  71. package/templates/app/promocodes/page.tsx +9 -0
  72. package/templates/app/ps3/blog/[...orionplace]/route.ts +13 -0
  73. package/templates/app/sellers/page.tsx +9 -0
  74. package/templates/app/storefront.module.css +406 -0
  75. package/templates/app/stores/page.tsx +9 -0
  76. package/templates/app/subscriptions/page.tsx +9 -0
  77. package/templates/app/tariffs/[slug]/[id]/page.tsx +9 -0
  78. package/templates/app/tariffs/page.tsx +9 -0
  79. package/templates/app/tickets/page.tsx +9 -0
  80. package/templates/app/unsubscribe/page.tsx +9 -0
  81. package/templates/app/wallet/page.tsx +9 -0
  82. package/templates/app/wishlist/page.tsx +9 -0
  83. package/templates/components/orionplace/account/AccountActivity.README.md +70 -0
  84. package/templates/components/orionplace/account/AccountActivity.module.css +16 -0
  85. package/templates/components/orionplace/account/AccountActivity.tsx +94 -0
  86. package/templates/components/orionplace/account/AccountPromocodes.README.md +70 -0
  87. package/templates/components/orionplace/account/AccountPromocodes.module.css +92 -0
  88. package/templates/components/orionplace/account/AccountPromocodes.tsx +257 -0
  89. package/templates/components/orionplace/account/AccountSection.README.md +46 -0
  90. package/templates/components/orionplace/account/AccountSection.module.css +110 -0
  91. package/templates/components/orionplace/account/AccountSection.tsx +109 -0
  92. package/templates/components/orionplace/account/ProfileAvatar.README.md +48 -0
  93. package/templates/components/orionplace/account/ProfileAvatar.module.css +31 -0
  94. package/templates/components/orionplace/account/ProfileAvatar.tsx +138 -0
  95. package/templates/components/orionplace/account/ProfileDeleteAccount.README.md +59 -0
  96. package/templates/components/orionplace/account/ProfileDeleteAccount.module.css +51 -0
  97. package/templates/components/orionplace/account/ProfileDeleteAccount.tsx +171 -0
  98. package/templates/components/orionplace/account/ProfileDetailsForm.README.md +44 -0
  99. package/templates/components/orionplace/account/ProfileDetailsForm.module.css +35 -0
  100. package/templates/components/orionplace/account/ProfileDetailsForm.tsx +178 -0
  101. package/templates/components/orionplace/account/ProfileEmailSettings.README.md +52 -0
  102. package/templates/components/orionplace/account/ProfileEmailSettings.module.css +51 -0
  103. package/templates/components/orionplace/account/ProfileEmailSettings.tsx +276 -0
  104. package/templates/components/orionplace/account/ProfilePasswordForm.README.md +45 -0
  105. package/templates/components/orionplace/account/ProfilePasswordForm.module.css +43 -0
  106. package/templates/components/orionplace/account/ProfilePasswordForm.tsx +227 -0
  107. package/templates/components/orionplace/account/ProfileSignIn.README.md +64 -0
  108. package/templates/components/orionplace/account/ProfileSignIn.module.css +86 -0
  109. package/templates/components/orionplace/account/ProfileSignIn.tsx +463 -0
  110. package/templates/components/orionplace/account/SignInHistory.README.md +52 -0
  111. package/templates/components/orionplace/account/SignInHistory.module.css +81 -0
  112. package/templates/components/orionplace/account/SignInHistory.tsx +231 -0
  113. package/templates/components/orionplace/account/StorefrontLogout.README.md +48 -0
  114. package/templates/components/orionplace/account/StorefrontLogout.module.css +166 -0
  115. package/templates/components/orionplace/account/StorefrontLogout.tsx +73 -0
  116. package/templates/components/orionplace/account/StorefrontProfile.README.md +71 -0
  117. package/templates/components/orionplace/account/StorefrontProfile.module.css +65 -0
  118. package/templates/components/orionplace/account/StorefrontProfile.tsx +189 -0
  119. package/templates/components/orionplace/account/account.module.css +1180 -0
  120. package/templates/components/orionplace/account/profile.module.css +696 -0
  121. package/templates/components/orionplace/address/AddressForm.README.md +152 -0
  122. package/templates/components/orionplace/address/AddressForm.module.css +336 -0
  123. package/templates/components/orionplace/address/AddressForm.tsx +827 -0
  124. package/templates/components/orionplace/address/StorefrontAddresses.README.md +99 -0
  125. package/templates/components/orionplace/address/StorefrontAddresses.module.css +271 -0
  126. package/templates/components/orionplace/address/StorefrontAddresses.tsx +438 -0
  127. package/templates/components/orionplace/banners/MainBanner.README.md +65 -0
  128. package/templates/components/orionplace/banners/MainBanner.module.css +112 -0
  129. package/templates/components/orionplace/banners/MainBanner.tsx +159 -0
  130. package/templates/components/orionplace/blog/BlogArticle.README.md +143 -0
  131. package/templates/components/orionplace/blog/BlogArticle.module.css +304 -0
  132. package/templates/components/orionplace/blog/BlogArticle.tsx +371 -0
  133. package/templates/components/orionplace/blog/BlogCommentForm.README.md +51 -0
  134. package/templates/components/orionplace/blog/BlogCommentForm.module.css +93 -0
  135. package/templates/components/orionplace/blog/BlogCommentForm.tsx +125 -0
  136. package/templates/components/orionplace/blog/BlogComments.README.md +98 -0
  137. package/templates/components/orionplace/blog/BlogComments.module.css +151 -0
  138. package/templates/components/orionplace/blog/BlogComments.tsx +590 -0
  139. package/templates/components/orionplace/blog/BlogPostCard.README.md +51 -0
  140. package/templates/components/orionplace/blog/BlogPostCard.module.css +167 -0
  141. package/templates/components/orionplace/blog/BlogPostCard.tsx +177 -0
  142. package/templates/components/orionplace/blog/BlogPostGroup.README.md +49 -0
  143. package/templates/components/orionplace/blog/BlogPostGroup.module.css +37 -0
  144. package/templates/components/orionplace/blog/BlogPostGroup.tsx +129 -0
  145. package/templates/components/orionplace/blog/StorefrontJournal.README.md +105 -0
  146. package/templates/components/orionplace/blog/StorefrontJournal.module.css +387 -0
  147. package/templates/components/orionplace/blog/StorefrontJournal.tsx +548 -0
  148. package/templates/components/orionplace/branding/BrandLogos.README.md +29 -0
  149. package/templates/components/orionplace/branding/BrandLogos.module.css +7 -0
  150. package/templates/components/orionplace/branding/BrandLogos.tsx +30 -0
  151. package/templates/components/orionplace/cart/CartCheckoutDetails.README.md +92 -0
  152. package/templates/components/orionplace/cart/CartCheckoutDetails.module.css +159 -0
  153. package/templates/components/orionplace/cart/CartCheckoutDetails.tsx +370 -0
  154. package/templates/components/orionplace/cart/CartDeliverySelector.README.md +166 -0
  155. package/templates/components/orionplace/cart/CartDeliverySelector.module.css +105 -0
  156. package/templates/components/orionplace/cart/CartDeliverySelector.tsx +679 -0
  157. package/templates/components/orionplace/cart/CartItemOptions.README.md +172 -0
  158. package/templates/components/orionplace/cart/CartItemOptions.module.css +113 -0
  159. package/templates/components/orionplace/cart/CartItemOptions.tsx +503 -0
  160. package/templates/components/orionplace/cart/CartLineItem.README.md +138 -0
  161. package/templates/components/orionplace/cart/CartLineItem.module.css +57 -0
  162. package/templates/components/orionplace/cart/CartLineItem.tsx +263 -0
  163. package/templates/components/orionplace/cart/CartPreview.README.md +99 -0
  164. package/templates/components/orionplace/cart/CartPreview.module.css +225 -0
  165. package/templates/components/orionplace/cart/CartPreview.tsx +188 -0
  166. package/templates/components/orionplace/cart/DeliveryMap.README.md +65 -0
  167. package/templates/components/orionplace/cart/DeliveryMap.module.css +32 -0
  168. package/templates/components/orionplace/cart/DeliveryMap.tsx +131 -0
  169. package/templates/components/orionplace/cart/StorefrontCart.README.md +281 -0
  170. package/templates/components/orionplace/cart/StorefrontCart.module.css +460 -0
  171. package/templates/components/orionplace/cart/StorefrontCart.tsx +937 -0
  172. package/templates/components/orionplace/catalog/CatalogHeader.README.md +136 -0
  173. package/templates/components/orionplace/catalog/CatalogHeader.module.css +387 -0
  174. package/templates/components/orionplace/catalog/CatalogHeader.tsx +644 -0
  175. package/templates/components/orionplace/catalog/CatalogRepresentative.README.md +68 -0
  176. package/templates/components/orionplace/catalog/CatalogRepresentative.tsx +51 -0
  177. package/templates/components/orionplace/catalog/StorefrontCatalog.README.md +120 -0
  178. package/templates/components/orionplace/catalog/StorefrontCatalog.module.css +179 -0
  179. package/templates/components/orionplace/catalog/StorefrontCatalog.tsx +304 -0
  180. package/templates/components/orionplace/categories/CategoryGroup.README.md +68 -0
  181. package/templates/components/orionplace/categories/CategoryGroup.module.css +110 -0
  182. package/templates/components/orionplace/categories/CategoryGroup.tsx +123 -0
  183. package/templates/components/orionplace/categories/StorefrontCategory.README.md +155 -0
  184. package/templates/components/orionplace/categories/StorefrontCategory.module.css +148 -0
  185. package/templates/components/orionplace/categories/StorefrontCategory.tsx +225 -0
  186. package/templates/components/orionplace/contact/AboutPage.README.md +40 -0
  187. package/templates/components/orionplace/contact/AboutPage.module.css +216 -0
  188. package/templates/components/orionplace/contact/AboutPage.tsx +191 -0
  189. package/templates/components/orionplace/contact/ContactForm.README.md +93 -0
  190. package/templates/components/orionplace/contact/ContactForm.module.css +264 -0
  191. package/templates/components/orionplace/contact/ContactForm.tsx +382 -0
  192. package/templates/components/orionplace/contact/EmailUnsubscribe.README.md +67 -0
  193. package/templates/components/orionplace/contact/EmailUnsubscribe.module.css +94 -0
  194. package/templates/components/orionplace/contact/EmailUnsubscribe.tsx +130 -0
  195. package/templates/components/orionplace/contact/StorefrontContact.README.md +73 -0
  196. package/templates/components/orionplace/contact/StorefrontContact.module.css +101 -0
  197. package/templates/components/orionplace/contact/StorefrontContact.tsx +62 -0
  198. package/templates/components/orionplace/currencies/MainCurrencySwitcher.README.md +37 -0
  199. package/templates/components/orionplace/currencies/MainCurrencySwitcher.tsx +68 -0
  200. package/templates/components/orionplace/events/EventEnrollmentDialog.README.md +108 -0
  201. package/templates/components/orionplace/events/EventEnrollmentDialog.module.css +110 -0
  202. package/templates/components/orionplace/events/EventEnrollmentDialog.tsx +315 -0
  203. package/templates/components/orionplace/events/EventsBlock.README.md +58 -0
  204. package/templates/components/orionplace/events/EventsBlock.module.css +101 -0
  205. package/templates/components/orionplace/events/EventsBlock.tsx +109 -0
  206. package/templates/components/orionplace/locale/MainLanguageSwitcher.README.md +46 -0
  207. package/templates/components/orionplace/locale/MainLanguageSwitcher.module.css +196 -0
  208. package/templates/components/orionplace/locale/MainLanguageSwitcher.tsx +387 -0
  209. package/templates/components/orionplace/manufacturers/ManufacturerIndex.README.md +83 -0
  210. package/templates/components/orionplace/manufacturers/ManufacturerIndex.module.css +271 -0
  211. package/templates/components/orionplace/manufacturers/ManufacturerIndex.tsx +218 -0
  212. package/templates/components/orionplace/manufacturers/StorefrontManufacturers.README.md +122 -0
  213. package/templates/components/orionplace/manufacturers/StorefrontManufacturers.module.css +92 -0
  214. package/templates/components/orionplace/manufacturers/StorefrontManufacturers.tsx +455 -0
  215. package/templates/components/orionplace/orders/OrderDetails.README.md +116 -0
  216. package/templates/components/orionplace/orders/OrderDetails.module.css +730 -0
  217. package/templates/components/orionplace/orders/OrderDetails.tsx +693 -0
  218. package/templates/components/orionplace/orders/OrderPaymentForm.README.md +92 -0
  219. package/templates/components/orionplace/orders/OrderPaymentForm.module.css +105 -0
  220. package/templates/components/orionplace/orders/OrderPaymentForm.tsx +90 -0
  221. package/templates/components/orionplace/orders/StorefrontOrders.README.md +131 -0
  222. package/templates/components/orionplace/orders/StorefrontOrders.module.css +633 -0
  223. package/templates/components/orionplace/orders/StorefrontOrders.tsx +922 -0
  224. package/templates/components/orionplace/payments/PaymentPanel.README.md +114 -0
  225. package/templates/components/orionplace/payments/PaymentPanel.module.css +134 -0
  226. package/templates/components/orionplace/payments/PaymentPanel.tsx +76 -0
  227. package/templates/components/orionplace/products/HoverImageViewer.README.md +62 -0
  228. package/templates/components/orionplace/products/HoverImageViewer.module.css +88 -0
  229. package/templates/components/orionplace/products/HoverImageViewer.tsx +179 -0
  230. package/templates/components/orionplace/products/ProductCard.README.md +152 -0
  231. package/templates/components/orionplace/products/ProductCard.module.css +333 -0
  232. package/templates/components/orionplace/products/ProductCard.tsx +716 -0
  233. package/templates/components/orionplace/products/ProductCardActions.README.md +84 -0
  234. package/templates/components/orionplace/products/ProductCardActions.module.css +146 -0
  235. package/templates/components/orionplace/products/ProductCardActions.tsx +311 -0
  236. package/templates/components/orionplace/products/ProductCardBadges.README.md +44 -0
  237. package/templates/components/orionplace/products/ProductCardBadges.module.css +15 -0
  238. package/templates/components/orionplace/products/ProductCardBadges.tsx +78 -0
  239. package/templates/components/orionplace/products/ProductCardDescription.README.md +33 -0
  240. package/templates/components/orionplace/products/ProductCardDescription.module.css +6 -0
  241. package/templates/components/orionplace/products/ProductCardDescription.tsx +43 -0
  242. package/templates/components/orionplace/products/ProductCardFavorite.README.md +52 -0
  243. package/templates/components/orionplace/products/ProductCardFavorite.module.css +64 -0
  244. package/templates/components/orionplace/products/ProductCardFavorite.tsx +90 -0
  245. package/templates/components/orionplace/products/ProductCardPrice.README.md +33 -0
  246. package/templates/components/orionplace/products/ProductCardPrice.module.css +22 -0
  247. package/templates/components/orionplace/products/ProductCardPrice.tsx +50 -0
  248. package/templates/components/orionplace/products/ProductCardRating.README.md +33 -0
  249. package/templates/components/orionplace/products/ProductCardRating.module.css +7 -0
  250. package/templates/components/orionplace/products/ProductCardRating.tsx +56 -0
  251. package/templates/components/orionplace/products/ProductCardSeller.README.md +36 -0
  252. package/templates/components/orionplace/products/ProductCardSeller.module.css +31 -0
  253. package/templates/components/orionplace/products/ProductCardSeller.tsx +61 -0
  254. package/templates/components/orionplace/products/ProductCardStockNotice.README.md +36 -0
  255. package/templates/components/orionplace/products/ProductCardStockNotice.module.css +8 -0
  256. package/templates/components/orionplace/products/ProductCardStockNotice.tsx +35 -0
  257. package/templates/components/orionplace/products/ProductCardTitle.README.md +30 -0
  258. package/templates/components/orionplace/products/ProductCardTitle.module.css +13 -0
  259. package/templates/components/orionplace/products/ProductCardTitle.tsx +31 -0
  260. package/templates/components/orionplace/products/ProductCardVariants.README.md +77 -0
  261. package/templates/components/orionplace/products/ProductCardVariants.module.css +97 -0
  262. package/templates/components/orionplace/products/ProductCardVariants.tsx +350 -0
  263. package/templates/components/orionplace/products/ProductGroup.README.md +89 -0
  264. package/templates/components/orionplace/products/ProductGroup.module.css +64 -0
  265. package/templates/components/orionplace/products/ProductGroup.tsx +183 -0
  266. package/templates/components/orionplace/products/ProductPreorderDialog.README.md +89 -0
  267. package/templates/components/orionplace/products/ProductPreorderDialog.module.css +320 -0
  268. package/templates/components/orionplace/products/ProductPreorderDialog.tsx +346 -0
  269. package/templates/components/orionplace/products/ProductReviewForm.README.md +72 -0
  270. package/templates/components/orionplace/products/ProductReviewForm.module.css +217 -0
  271. package/templates/components/orionplace/products/ProductReviewForm.tsx +288 -0
  272. package/templates/components/orionplace/products/ProductReviewGallery.README.md +61 -0
  273. package/templates/components/orionplace/products/ProductReviewGallery.module.css +108 -0
  274. package/templates/components/orionplace/products/ProductReviewGallery.tsx +110 -0
  275. package/templates/components/orionplace/products/ProductReviewImageViewer.README.md +49 -0
  276. package/templates/components/orionplace/products/ProductReviewImageViewer.module.css +50 -0
  277. package/templates/components/orionplace/products/ProductReviewImageViewer.tsx +260 -0
  278. package/templates/components/orionplace/products/ProductReviews.README.md +137 -0
  279. package/templates/components/orionplace/products/ProductReviews.module.css +602 -0
  280. package/templates/components/orionplace/products/ProductReviews.tsx +944 -0
  281. package/templates/components/orionplace/products/ProductSecondaryVariantModal.README.md +67 -0
  282. package/templates/components/orionplace/products/ProductSecondaryVariantModal.module.css +79 -0
  283. package/templates/components/orionplace/products/ProductSecondaryVariantModal.tsx +324 -0
  284. package/templates/components/orionplace/products/ProductSpecificationsDialog.README.md +52 -0
  285. package/templates/components/orionplace/products/ProductSpecificationsDialog.module.css +138 -0
  286. package/templates/components/orionplace/products/ProductSpecificationsDialog.tsx +117 -0
  287. package/templates/components/orionplace/products/StorefrontProduct.README.md +200 -0
  288. package/templates/components/orionplace/products/StorefrontProduct.module.css +767 -0
  289. package/templates/components/orionplace/products/StorefrontProduct.tsx +1339 -0
  290. package/templates/components/orionplace/products/StorefrontWishlist.README.md +162 -0
  291. package/templates/components/orionplace/products/StorefrontWishlist.module.css +71 -0
  292. package/templates/components/orionplace/products/StorefrontWishlist.tsx +402 -0
  293. package/templates/components/orionplace/promo/PromoProducts.README.md +133 -0
  294. package/templates/components/orionplace/promo/PromoProducts.module.css +68 -0
  295. package/templates/components/orionplace/promo/PromoProducts.tsx +345 -0
  296. package/templates/components/orionplace/search/MainSearch.README.md +73 -0
  297. package/templates/components/orionplace/search/MainSearch.module.css +202 -0
  298. package/templates/components/orionplace/search/MainSearch.tsx +217 -0
  299. package/templates/components/orionplace/search/ProductSearchResults.README.md +166 -0
  300. package/templates/components/orionplace/search/ProductSearchResults.module.css +358 -0
  301. package/templates/components/orionplace/search/ProductSearchResults.tsx +308 -0
  302. package/templates/components/orionplace/search/SearchFilters.README.md +109 -0
  303. package/templates/components/orionplace/search/SearchFilters.module.css +274 -0
  304. package/templates/components/orionplace/search/SearchFilters.tsx +439 -0
  305. package/templates/components/orionplace/sellers/SellerFilters.README.md +66 -0
  306. package/templates/components/orionplace/sellers/SellerFilters.module.css +232 -0
  307. package/templates/components/orionplace/sellers/SellerFilters.tsx +201 -0
  308. package/templates/components/orionplace/sellers/SellerPhotoGallery.README.md +72 -0
  309. package/templates/components/orionplace/sellers/SellerPhotoGallery.module.css +314 -0
  310. package/templates/components/orionplace/sellers/SellerPhotoGallery.tsx +335 -0
  311. package/templates/components/orionplace/sellers/SellerSummary.README.md +62 -0
  312. package/templates/components/orionplace/sellers/SellerSummary.module.css +182 -0
  313. package/templates/components/orionplace/sellers/SellerSummary.tsx +107 -0
  314. package/templates/components/orionplace/sellers/StorefrontSellers.README.md +120 -0
  315. package/templates/components/orionplace/sellers/StorefrontSellers.module.css +147 -0
  316. package/templates/components/orionplace/sellers/StorefrontSellers.tsx +360 -0
  317. package/templates/components/orionplace/shared/OrionplaceAvatar.README.md +35 -0
  318. package/templates/components/orionplace/shared/OrionplaceAvatar.module.css +35 -0
  319. package/templates/components/orionplace/shared/OrionplaceAvatar.tsx +61 -0
  320. package/templates/components/orionplace/shared/OrionplaceBreadcrumbs.README.md +33 -0
  321. package/templates/components/orionplace/shared/OrionplaceBreadcrumbs.module.css +81 -0
  322. package/templates/components/orionplace/shared/OrionplaceBreadcrumbs.tsx +91 -0
  323. package/templates/components/orionplace/shared/OrionplaceEmptyState.README.md +37 -0
  324. package/templates/components/orionplace/shared/OrionplaceEmptyState.module.css +126 -0
  325. package/templates/components/orionplace/shared/OrionplaceEmptyState.tsx +78 -0
  326. package/templates/components/orionplace/shared/OrionplaceImage.README.md +45 -0
  327. package/templates/components/orionplace/shared/OrionplaceImage.module.css +22 -0
  328. package/templates/components/orionplace/shared/OrionplaceImage.tsx +96 -0
  329. package/templates/components/orionplace/shared/OrionplaceInfoHint.README.md +31 -0
  330. package/templates/components/orionplace/shared/OrionplaceInfoHint.module.css +59 -0
  331. package/templates/components/orionplace/shared/OrionplaceInfoHint.tsx +126 -0
  332. package/templates/components/orionplace/shared/OrionplacePasswordInput.README.md +39 -0
  333. package/templates/components/orionplace/shared/OrionplacePasswordInput.module.css +79 -0
  334. package/templates/components/orionplace/shared/OrionplacePasswordInput.tsx +103 -0
  335. package/templates/components/orionplace/shared/OrionplaceSearchSelect.README.md +97 -0
  336. package/templates/components/orionplace/shared/OrionplaceSearchSelect.module.css +246 -0
  337. package/templates/components/orionplace/shared/OrionplaceSearchSelect.tsx +367 -0
  338. package/templates/components/orionplace/shared/OrionplaceSelect.README.md +69 -0
  339. package/templates/components/orionplace/shared/OrionplaceSelect.module.css +233 -0
  340. package/templates/components/orionplace/shared/OrionplaceSelect.tsx +439 -0
  341. package/templates/components/orionplace/shared/StorefrontErrorPage.README.md +52 -0
  342. package/templates/components/orionplace/shared/StorefrontErrorPage.module.css +141 -0
  343. package/templates/components/orionplace/shared/StorefrontErrorPage.tsx +103 -0
  344. package/templates/components/orionplace/shared/StorefrontPagination.README.md +39 -0
  345. package/templates/components/orionplace/shared/StorefrontPagination.module.css +69 -0
  346. package/templates/components/orionplace/shared/StorefrontPagination.tsx +102 -0
  347. package/templates/components/orionplace/shared/dialogs.css +36 -0
  348. package/templates/components/orionplace/shared/emptyStateArt.ts +231 -0
  349. package/templates/components/orionplace/shared/features.module.css +162 -0
  350. package/templates/components/orionplace/social/SocialLinks.README.md +16 -0
  351. package/templates/components/orionplace/social/SocialLinks.module.css +36 -0
  352. package/templates/components/orionplace/social/SocialLinks.tsx +59 -0
  353. package/templates/components/orionplace/storefront/StorefrontAccount.README.md +59 -0
  354. package/templates/components/orionplace/storefront/StorefrontAccount.module.css +159 -0
  355. package/templates/components/orionplace/storefront/StorefrontAccount.tsx +303 -0
  356. package/templates/components/orionplace/storefront/StorefrontCartButton.README.md +47 -0
  357. package/templates/components/orionplace/storefront/StorefrontCartButton.module.css +88 -0
  358. package/templates/components/orionplace/storefront/StorefrontCartButton.tsx +54 -0
  359. package/templates/components/orionplace/storefront/StorefrontFooter.README.md +55 -0
  360. package/templates/components/orionplace/storefront/StorefrontFooter.module.css +118 -0
  361. package/templates/components/orionplace/storefront/StorefrontFooter.tsx +165 -0
  362. package/templates/components/orionplace/storefront/StorefrontHeader.README.md +150 -0
  363. package/templates/components/orionplace/storefront/StorefrontHeader.module.css +186 -0
  364. package/templates/components/orionplace/storefront/StorefrontHeader.tsx +389 -0
  365. package/templates/components/orionplace/storefront/StorefrontHome.README.md +138 -0
  366. package/templates/components/orionplace/storefront/StorefrontHome.module.css +116 -0
  367. package/templates/components/orionplace/storefront/StorefrontHome.tsx +282 -0
  368. package/templates/components/orionplace/storefront/StorefrontWishlistButton.README.md +51 -0
  369. package/templates/components/orionplace/storefront/StorefrontWishlistButton.module.css +95 -0
  370. package/templates/components/orionplace/storefront/StorefrontWishlistButton.tsx +80 -0
  371. package/templates/components/orionplace/stores/StorefrontStores.README.md +87 -0
  372. package/templates/components/orionplace/stores/StorefrontStores.module.css +218 -0
  373. package/templates/components/orionplace/stores/StorefrontStores.tsx +396 -0
  374. package/templates/components/orionplace/subscriptions/StorefrontSubscriptions.README.md +183 -0
  375. package/templates/components/orionplace/subscriptions/StorefrontSubscriptions.module.css +347 -0
  376. package/templates/components/orionplace/subscriptions/StorefrontSubscriptions.tsx +884 -0
  377. package/templates/components/orionplace/subscriptions/StorefrontTariff.README.md +111 -0
  378. package/templates/components/orionplace/subscriptions/StorefrontTariff.module.css +311 -0
  379. package/templates/components/orionplace/subscriptions/StorefrontTariff.tsx +474 -0
  380. package/templates/components/orionplace/subscriptions/StorefrontTariffs.README.md +102 -0
  381. package/templates/components/orionplace/subscriptions/StorefrontTariffs.module.css +219 -0
  382. package/templates/components/orionplace/subscriptions/StorefrontTariffs.tsx +350 -0
  383. package/templates/components/orionplace/subscriptions/SubscriptionCancelDialog.README.md +67 -0
  384. package/templates/components/orionplace/subscriptions/SubscriptionCancelDialog.tsx +175 -0
  385. package/templates/components/orionplace/subscriptions/SubscriptionChangeDialog.README.md +95 -0
  386. package/templates/components/orionplace/subscriptions/SubscriptionChangeDialog.tsx +634 -0
  387. package/templates/components/orionplace/subscriptions/SubscriptionDialog.module.css +453 -0
  388. package/templates/components/orionplace/subscriptions/SubscriptionExtendDialog.README.md +75 -0
  389. package/templates/components/orionplace/subscriptions/SubscriptionExtendDialog.tsx +384 -0
  390. package/templates/components/orionplace/subscriptions/SubscriptionPlanSummary.README.md +116 -0
  391. package/templates/components/orionplace/subscriptions/SubscriptionPlanSummary.module.css +168 -0
  392. package/templates/components/orionplace/subscriptions/SubscriptionPlanSummary.tsx +236 -0
  393. package/templates/components/orionplace/subscriptions/SubscriptionRemoteDetails.README.md +123 -0
  394. package/templates/components/orionplace/subscriptions/SubscriptionRemoteDetails.module.css +57 -0
  395. package/templates/components/orionplace/subscriptions/SubscriptionRemoteDetails.tsx +101 -0
  396. package/templates/components/orionplace/subscriptions/SubscriptionRenewalDialog.README.md +109 -0
  397. package/templates/components/orionplace/subscriptions/SubscriptionRenewalDialog.tsx +253 -0
  398. package/templates/components/orionplace/support/StorefrontSupport.README.md +157 -0
  399. package/templates/components/orionplace/support/StorefrontSupport.tsx +1040 -0
  400. package/templates/components/orionplace/support/Support.module.css +1133 -0
  401. package/templates/components/orionplace/support/TicketConversation.README.md +105 -0
  402. package/templates/components/orionplace/support/TicketConversation.tsx +502 -0
  403. package/templates/components/orionplace/support/TicketImage.README.md +52 -0
  404. package/templates/components/orionplace/support/TicketImage.tsx +254 -0
  405. package/templates/components/orionplace/support/TicketItemCard.README.md +66 -0
  406. package/templates/components/orionplace/support/TicketItemCard.tsx +140 -0
  407. package/templates/components/orionplace/support/TicketMessageForm.README.md +88 -0
  408. package/templates/components/orionplace/support/TicketMessageForm.tsx +261 -0
  409. package/templates/components/orionplace/support/TicketSubscriptionCard.README.md +53 -0
  410. package/templates/components/orionplace/support/TicketSubscriptionCard.tsx +118 -0
  411. package/templates/components/orionplace/tariffs/TariffCard.README.md +108 -0
  412. package/templates/components/orionplace/tariffs/TariffCard.module.css +86 -0
  413. package/templates/components/orionplace/tariffs/TariffCard.tsx +218 -0
  414. package/templates/components/orionplace/tariffs/TariffCardDescription.README.md +40 -0
  415. package/templates/components/orionplace/tariffs/TariffCardDescription.module.css +72 -0
  416. package/templates/components/orionplace/tariffs/TariffCardDescription.tsx +58 -0
  417. package/templates/components/orionplace/tariffs/TariffCardFeatures.README.md +58 -0
  418. package/templates/components/orionplace/tariffs/TariffCardFeatures.module.css +60 -0
  419. package/templates/components/orionplace/tariffs/TariffCardFeatures.tsx +140 -0
  420. package/templates/components/orionplace/tariffs/TariffCardImage.README.md +36 -0
  421. package/templates/components/orionplace/tariffs/TariffCardImage.module.css +8 -0
  422. package/templates/components/orionplace/tariffs/TariffCardImage.tsx +32 -0
  423. package/templates/components/orionplace/tariffs/TariffCardPrices.README.md +74 -0
  424. package/templates/components/orionplace/tariffs/TariffCardPrices.module.css +142 -0
  425. package/templates/components/orionplace/tariffs/TariffCardPrices.tsx +181 -0
  426. package/templates/components/orionplace/tariffs/TariffCardTitle.README.md +32 -0
  427. package/templates/components/orionplace/tariffs/TariffCardTitle.module.css +9 -0
  428. package/templates/components/orionplace/tariffs/TariffCardTitle.tsx +29 -0
  429. package/templates/components/orionplace/tariffs/TariffCards.README.md +88 -0
  430. package/templates/components/orionplace/tariffs/TariffCards.module.css +24 -0
  431. package/templates/components/orionplace/tariffs/TariffCards.tsx +44 -0
  432. package/templates/components/orionplace/tariffs/TariffSubscriptionModal.README.md +92 -0
  433. package/templates/components/orionplace/tariffs/TariffSubscriptionModal.module.css +214 -0
  434. package/templates/components/orionplace/tariffs/TariffSubscriptionModal.tsx +299 -0
  435. package/templates/components/orionplace/theme/OrionplaceThemeProvider.README.md +30 -0
  436. package/templates/components/orionplace/theme/OrionplaceThemeProvider.module.css +8 -0
  437. package/templates/components/orionplace/theme/OrionplaceThemeProvider.tsx +32 -0
  438. package/templates/components/orionplace/units/UnitSwitcher.README.md +35 -0
  439. package/templates/components/orionplace/units/UnitSwitcher.tsx +58 -0
  440. package/templates/components/orionplace/wallet/StorefrontWallet.README.md +154 -0
  441. package/templates/components/orionplace/wallet/StorefrontWallet.module.css +507 -0
  442. package/templates/components/orionplace/wallet/StorefrontWallet.tsx +915 -0
  443. package/templates/components/orionplace/wallet/WalletBalance.README.md +46 -0
  444. package/templates/components/orionplace/wallet/WalletBalance.module.css +46 -0
  445. package/templates/components/orionplace/wallet/WalletBalance.tsx +51 -0
  446. package/templates/components/orionplace/wallet/WalletTopUpForm.README.md +54 -0
  447. package/templates/components/orionplace/wallet/WalletTopUpForm.module.css +152 -0
  448. package/templates/components/orionplace/wallet/WalletTopUpForm.tsx +181 -0
  449. package/templates/hooks/useAccountActivity.ts +251 -0
  450. package/templates/hooks/useAccountAddresses.ts +81 -0
  451. package/templates/hooks/useAccountPromocodes.ts +91 -0
  452. package/templates/hooks/useOrionplaceActionMenu.ts +118 -0
  453. package/templates/hooks/useOrionplaceAddressForm.ts +262 -0
  454. package/templates/hooks/useOrionplaceApi.ts +98 -0
  455. package/templates/hooks/useOrionplaceAppLoad.ts +38 -6
  456. package/templates/hooks/useOrionplaceBlogReading.ts +351 -0
  457. package/templates/hooks/useOrionplaceDialog.ts +85 -0
  458. package/templates/hooks/useOrionplaceHomeProducts.ts +97 -0
  459. package/templates/hooks/useOrionplaceMap.tsx +230 -0
  460. package/templates/hooks/useOrionplaceMediaFormat.ts +33 -0
  461. package/templates/hooks/useOrionplaceProduct.ts +28 -0
  462. package/templates/hooks/useOrionplaceProfile.ts +347 -0
  463. package/templates/hooks/useOrionplaceReviews.ts +352 -0
  464. package/templates/hooks/useOrionplaceStableHeight.ts +65 -0
  465. package/templates/hooks/useOrionplaceStorefront.tsx +575 -0
  466. package/templates/hooks/useOrionplaceTariffPurchase.ts +152 -0
  467. package/templates/interfaces/orionplace/accounts/IContact.ts +21 -0
  468. package/templates/interfaces/orionplace/accounts/IEmailNotificationLocale.ts +17 -0
  469. package/templates/interfaces/orionplace/accounts/IEmailVerification.ts +59 -0
  470. package/templates/interfaces/orionplace/accounts/IProfileActions.ts +38 -0
  471. package/templates/interfaces/orionplace/accounts/IProfileData.ts +123 -0
  472. package/templates/interfaces/orionplace/accounts/IProfileMedia.ts +17 -0
  473. package/templates/interfaces/orionplace/accounts/IStorefrontProfile.ts +54 -0
  474. package/templates/interfaces/orionplace/address/IAddressCitySuggestion.ts +19 -0
  475. package/templates/interfaces/orionplace/address/IAddressEditor.ts +9 -0
  476. package/templates/interfaces/orionplace/address/IAddressLocationSuggestion.ts +22 -0
  477. package/templates/interfaces/orionplace/address/IAddressRegionSearch.ts +30 -0
  478. package/templates/interfaces/orionplace/address/IAddressSuggestion.ts +20 -0
  479. package/templates/interfaces/orionplace/address/IUserAddress.ts +87 -0
  480. package/templates/interfaces/orionplace/appBase/IAppLoad.ts +4 -3
  481. package/templates/interfaces/orionplace/appBase/IAppLoadCommon.ts +22 -31
  482. package/templates/interfaces/orionplace/appBase/IApploadUserProfile.ts +1 -1
  483. package/templates/interfaces/orionplace/appBase/IForm.ts +1 -1
  484. package/templates/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonBanner.ts +7 -7
  485. package/templates/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonPath.ts +2 -2
  486. package/templates/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonSEO.ts +20 -7
  487. package/templates/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonTariff.ts +2 -2
  488. package/templates/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonVariables.ts +1 -0
  489. package/templates/interfaces/orionplace/blog/IBlogApi.ts +247 -0
  490. package/templates/interfaces/orionplace/blog/IBlogPostPreview.ts +30 -0
  491. package/templates/interfaces/orionplace/cart/ICartActions.ts +67 -0
  492. package/templates/interfaces/orionplace/cart/ICartData.ts +382 -0
  493. package/templates/interfaces/orionplace/cart/ICartDeliveryActions.ts +68 -0
  494. package/templates/interfaces/orionplace/cart/ICartModal.ts +22 -0
  495. package/templates/interfaces/orionplace/cart/IDeliveryMap.ts +29 -0
  496. package/templates/interfaces/orionplace/catalog/ICatalogDirectory.ts +75 -0
  497. package/templates/interfaces/orionplace/category/ICategory.ts +2 -1
  498. package/templates/interfaces/orionplace/domains/IDomain.ts +2 -2
  499. package/templates/interfaces/orionplace/forms/ICustomerSubfilter.ts +19 -0
  500. package/templates/interfaces/orionplace/index.ts +64 -30
  501. package/templates/interfaces/orionplace/marketing/IPromoActions.ts +51 -0
  502. package/templates/interfaces/orionplace/orders/IOrderActions.ts +99 -0
  503. package/templates/interfaces/orionplace/orders/IOrderHistory.ts +259 -0
  504. package/templates/interfaces/orionplace/orders/IProductPreorder.ts +31 -0
  505. package/templates/interfaces/orionplace/payments/IInvoice.ts +147 -0
  506. package/templates/interfaces/orionplace/products/IProduct.ts +178 -151
  507. package/templates/interfaces/orionplace/products/IProductFeedbackApi.ts +218 -0
  508. package/templates/interfaces/orionplace/products/IProductSearchApi.ts +146 -0
  509. package/templates/interfaces/orionplace/products/IProductSearchFacets.ts +35 -0
  510. package/templates/interfaces/orionplace/products/IProductSecondaryVariantRequest.ts +15 -0
  511. package/templates/interfaces/orionplace/products/IProductSpecification.ts +14 -0
  512. package/templates/interfaces/orionplace/products/IWishlist.ts +22 -0
  513. package/templates/interfaces/orionplace/sellers/ISellerDirectory.ts +70 -0
  514. package/templates/interfaces/orionplace/shared/IBreadcrumb.ts +10 -0
  515. package/templates/interfaces/orionplace/shared/IStorefrontControls.ts +6 -0
  516. package/templates/interfaces/orionplace/state.ts +1 -1
  517. package/templates/interfaces/orionplace/stores/IStoreDirectory.ts +63 -0
  518. package/templates/interfaces/orionplace/subscribeTariff/ISubscribeTariff.ts +41 -31
  519. package/templates/interfaces/orionplace/subscribeTariff/ITariffSelection.ts +11 -0
  520. package/templates/interfaces/orionplace/subscribes/ISubscribe.ts +1 -1
  521. package/templates/interfaces/orionplace/subscribes/ISubscriptionApi.ts +125 -0
  522. package/templates/interfaces/orionplace/tickets/ICustomerTicketApi.ts +223 -0
  523. package/templates/interfaces/orionplace/transport.ts +7 -3
  524. package/templates/lib/orionplace/accountLabels.ts +22 -0
  525. package/templates/lib/orionplace/activityLabels.ts +65 -0
  526. package/templates/lib/orionplace/activityPresentation.ts +9 -0
  527. package/templates/lib/orionplace/addressChanges.ts +26 -0
  528. package/templates/lib/orionplace/addressPresentation.ts +58 -0
  529. package/templates/lib/orionplace/api.ts +326 -0
  530. package/templates/lib/orionplace/appLoadClient.ts +63 -21
  531. package/templates/lib/orionplace/appLoadResult.ts +75 -21
  532. package/templates/lib/orionplace/appLoadServer.ts +66 -21
  533. package/templates/lib/orionplace/blogArticle.ts +97 -0
  534. package/templates/lib/orionplace/blogArticleHtml.ts +370 -0
  535. package/templates/lib/orionplace/blogComments.ts +102 -0
  536. package/templates/lib/orionplace/blogJournal.ts +115 -0
  537. package/templates/lib/orionplace/blogLabels.ts +108 -0
  538. package/templates/lib/orionplace/blogPresentation.ts +67 -0
  539. package/templates/lib/orionplace/browseLabels.ts +98 -0
  540. package/templates/lib/orionplace/cartAddress.ts +86 -0
  541. package/templates/lib/orionplace/cartController.ts +104 -0
  542. package/templates/lib/orionplace/cartLabels.ts +261 -0
  543. package/templates/lib/orionplace/cartOptions.ts +115 -0
  544. package/templates/lib/orionplace/cartPreview.ts +120 -0
  545. package/templates/lib/orionplace/cartPromo.ts +150 -0
  546. package/templates/lib/orionplace/cartShipment.ts +61 -0
  547. package/templates/lib/orionplace/cartSnapshot.ts +21 -0
  548. package/templates/lib/orionplace/catalog.ts +150 -0
  549. package/templates/lib/orionplace/categoryLabels.ts +21 -0
  550. package/templates/lib/orionplace/contact.ts +47 -0
  551. package/templates/lib/orionplace/contactLabels.ts +102 -0
  552. package/templates/lib/orionplace/currency.ts +128 -0
  553. package/templates/lib/orionplace/deliveryMap.ts +86 -0
  554. package/templates/lib/orionplace/directoryLabels.ts +133 -0
  555. package/templates/lib/orionplace/domain.ts +11 -6
  556. package/templates/lib/orionplace/errorPageLabels.ts +74 -0
  557. package/templates/lib/orionplace/eventEnrollment.ts +22 -0
  558. package/templates/lib/orionplace/featureData.ts +28 -0
  559. package/templates/lib/orionplace/featureLabels.ts +271 -0
  560. package/templates/lib/orionplace/featuredLabels.ts +28 -0
  561. package/templates/lib/orionplace/footerLabels.ts +35 -0
  562. package/templates/lib/orionplace/guestSession.ts +55 -0
  563. package/templates/lib/orionplace/headerLabels.ts +68 -0
  564. package/templates/lib/orionplace/homeContent.ts +193 -0
  565. package/templates/lib/orionplace/homeLabels.ts +52 -0
  566. package/templates/lib/orionplace/htmlText.ts +51 -0
  567. package/templates/lib/orionplace/manufacturerDirectory.ts +71 -0
  568. package/templates/lib/orionplace/manufacturerIndex.ts +92 -0
  569. package/templates/lib/orionplace/modalWindow.ts +109 -0
  570. package/templates/lib/orionplace/nativeApp.ts +365 -0
  571. package/templates/lib/orionplace/navigation.tsx +121 -0
  572. package/templates/lib/orionplace/notice.ts +22 -0
  573. package/templates/lib/orionplace/orderLabels.ts +311 -0
  574. package/templates/lib/orionplace/orderPresentation.ts +574 -0
  575. package/templates/lib/orionplace/pageMetadata.ts +247 -0
  576. package/templates/lib/orionplace/pageSeo.ts +274 -0
  577. package/templates/lib/orionplace/paginationScroll.ts +131 -0
  578. package/templates/lib/orionplace/phone.ts +22 -0
  579. package/templates/lib/orionplace/presentation.ts +84 -0
  580. package/templates/lib/orionplace/productActionLabels.ts +60 -0
  581. package/templates/lib/orionplace/productAvailability.ts +30 -0
  582. package/templates/lib/orionplace/productGallery.ts +64 -0
  583. package/templates/lib/orionplace/productLabels.ts +82 -0
  584. package/templates/lib/orionplace/productPage.ts +308 -0
  585. package/templates/lib/orionplace/productPhotos.ts +67 -0
  586. package/templates/lib/orionplace/productSearch.ts +232 -0
  587. package/templates/lib/orionplace/productSeller.ts +56 -0
  588. package/templates/lib/orionplace/profile.ts +157 -0
  589. package/templates/lib/orionplace/profileLabels.ts +151 -0
  590. package/templates/lib/orionplace/promoLabels.ts +75 -0
  591. package/templates/lib/orionplace/promoPresentation.ts +381 -0
  592. package/templates/lib/orionplace/reviewLabels.ts +172 -0
  593. package/templates/lib/orionplace/reviewPresentation.ts +64 -0
  594. package/templates/lib/orionplace/reviewScope.ts +38 -0
  595. package/templates/lib/orionplace/searchFilters.ts +99 -0
  596. package/templates/lib/orionplace/searchHistory.ts +144 -0
  597. package/templates/lib/orionplace/sellerDirectory.ts +120 -0
  598. package/templates/lib/orionplace/seo.ts +52 -0
  599. package/templates/lib/orionplace/serverApi.ts +158 -0
  600. package/templates/lib/orionplace/serverLocale.ts +29 -0
  601. package/templates/lib/orionplace/serverMetadata.ts +86 -0
  602. package/templates/lib/orionplace/serverRecords.ts +25 -0
  603. package/templates/lib/orionplace/serverRoute.tsx +51 -0
  604. package/templates/lib/orionplace/sessionLabels.ts +27 -0
  605. package/templates/lib/orionplace/signInReturn.ts +89 -0
  606. package/templates/lib/orionplace/storeDirectory.ts +47 -0
  607. package/templates/lib/orionplace/storefrontRecords.ts +29 -0
  608. package/templates/lib/orionplace/subscriptionChanges.ts +120 -0
  609. package/templates/lib/orionplace/subscriptionLabels.ts +67 -0
  610. package/templates/lib/orionplace/subscriptionManageLabels.ts +306 -0
  611. package/templates/lib/orionplace/subscriptionPlans.ts +122 -0
  612. package/templates/lib/orionplace/subscriptionRemote.ts +85 -0
  613. package/templates/lib/orionplace/subscriptionUsage.ts +58 -0
  614. package/templates/lib/orionplace/tariffLabels.ts +123 -0
  615. package/templates/lib/orionplace/tariffPage.ts +54 -0
  616. package/templates/lib/orionplace/tariffVariables.ts +136 -0
  617. package/templates/lib/orionplace/tariffs.ts +192 -0
  618. package/templates/lib/orionplace/ticketItems.ts +75 -0
  619. package/templates/lib/orionplace/ticketLabels.ts +335 -0
  620. package/templates/lib/orionplace/ticketLocation.ts +79 -0
  621. package/templates/lib/orionplace/ticketRequests.ts +70 -0
  622. package/templates/lib/orionplace/walletTransaction.ts +105 -0
  623. package/templates/orionplace/COMPONENT-DOWNLOAD.md +66 -0
  624. package/templates/orionplace/COMPONENTS.md +35 -0
  625. package/templates/providers/OrionplaceProvider.tsx +164 -12
  626. package/templates/proxy.ts +8 -3
  627. package/templates/stores/orionplace.ts +212 -49
  628. package/templates/stores/orionplaceBrowse.ts +89 -0
  629. package/templates/stores/orionplaceCatalog.ts +144 -0
  630. package/templates/stores/orionplaceProducts.ts +389 -0
  631. package/templates/stores/orionplaceScope.ts +104 -0
  632. package/templates/stores/orionplaceSelections.ts +130 -0
  633. package/templates/themes/orionplace.ts +106 -0
@@ -0,0 +1,1339 @@
1
+ 'use client';
2
+
3
+ import { StorefrontLink as Link } from '@/lib/orionplace/navigation';
4
+ import {
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ type KeyboardEvent,
11
+ type MouseEvent,
12
+ type TouchEvent,
13
+ } from 'react';
14
+ import {
15
+ useOrionplace,
16
+ useOrionplaceCatalog,
17
+ useOrionplaceProductsStore,
18
+ useOrionplaceSelections,
19
+ } from '@/providers/OrionplaceProvider';
20
+ import { useOrionplaceProduct } from '@/hooks/useOrionplaceProduct';
21
+ import { selectInit } from '@/stores/orionplace';
22
+ import type {
23
+ IProduct,
24
+ IProductSeller,
25
+ } from '@/interfaces/orionplace/products/IProduct';
26
+ import { EProductStatus } from '@/interfaces/orionplace/products/IProduct';
27
+ import type { IProductSearchRequest } from '@/interfaces/orionplace/products/IProductSearchApi';
28
+ import type { IBreadcrumb } from '@/interfaces/orionplace/shared/IBreadcrumb';
29
+ import { apiErrorMessage, OrionplaceApiError } from '@/lib/orionplace/api';
30
+ import { catalogTrailToCategory } from '@/lib/orionplace/catalog';
31
+ import { categoryLabels } from '@/lib/orionplace/categoryLabels';
32
+ import { featureLabels } from '@/lib/orionplace/featureLabels';
33
+ import { htmlTextParagraphs } from '@/lib/orionplace/htmlText';
34
+ import { productActionLabels } from '@/lib/orionplace/productActionLabels';
35
+ import {
36
+ productThumbnailKeyTarget,
37
+ productThumbnailRailEdges,
38
+ productThumbnailScrollTarget,
39
+ type ProductThumbnailRailMetrics,
40
+ } from '@/lib/orionplace/productGallery';
41
+ import { productPhotoAfterSwipe } from '@/lib/orionplace/productPhotos';
42
+ import { productLabels } from '@/lib/orionplace/productLabels';
43
+ import {
44
+ productPageManufacturer,
45
+ productPagePhotos,
46
+ productPageReturns,
47
+ productPageReviewStat,
48
+ productPageSpecifications,
49
+ } from '@/lib/orionplace/productPage';
50
+ import {
51
+ getAvailableNestedValueFromObject,
52
+ safePublicUrl,
53
+ } from '@/lib/orionplace/presentation';
54
+ import { reviewLabels } from '@/lib/orionplace/reviewLabels';
55
+ import { safeBlogArticleHtml } from '@/lib/orionplace/blogArticleHtml';
56
+ import { OrionplaceAvatar } from '@/components/orionplace/shared/OrionplaceAvatar';
57
+ import { OrionplaceBreadcrumbs } from '@/components/orionplace/shared/OrionplaceBreadcrumbs';
58
+ import { OrionplaceEmptyState } from '@/components/orionplace/shared/OrionplaceEmptyState';
59
+ import { OrionplaceImage } from '@/components/orionplace/shared/OrionplaceImage';
60
+ import { ProductCard, type ProductCardProps } from './ProductCard';
61
+ import {
62
+ ProductPreorderDialog,
63
+ type ProductPreorderSelection,
64
+ } from './ProductPreorderDialog';
65
+ import { ProductReviews, type ProductReviewsProps } from './ProductReviews';
66
+ import { ProductSpecificationsDialog } from './ProductSpecificationsDialog';
67
+ import { ProductReviewImageViewer } from './ProductReviewImageViewer';
68
+ import styles from './StorefrontProduct.module.css';
69
+
70
+ export interface ProductPageLoadAction {
71
+ endpoint: 'products_search' | 'products_preview';
72
+ request: IProductSearchRequest & {
73
+ filters: NonNullable<IProductSearchRequest['filters']> & {
74
+ id: number;
75
+ view_key?: string;
76
+ };
77
+ };
78
+ signal: AbortSignal;
79
+ }
80
+
81
+ export interface ProductPreorderAction {
82
+ endpoint: 'preorders_create';
83
+ request: ProductPreorderSelection;
84
+ signal: AbortSignal;
85
+ }
86
+
87
+ export interface ProductActionNotice {
88
+ message: string;
89
+ tone?: 'success' | 'error';
90
+ emailRequired?: boolean;
91
+ }
92
+
93
+ type CardCallbacks = Pick<
94
+ ProductCardProps,
95
+ | 'variantProductIds'
96
+ | 'onToggleFavorite'
97
+ | 'onSelectVariant'
98
+ | 'onSelectSecondaryVariant'
99
+ | 'onRequestSecondaryVariants'
100
+ | 'onAdd'
101
+ | 'onRemove'
102
+ | 'onSubscribe'
103
+ | 'onUnsubscribe'
104
+ >;
105
+
106
+ export interface StorefrontProductProps extends CardCallbacks {
107
+ productId: number;
108
+ initialProduct?: IProduct;
109
+ preloaded?: boolean;
110
+ previewKey?: string;
111
+ loadOnMount?: boolean;
112
+ initialError?: string;
113
+ actionPending?: boolean;
114
+ actionNotice?: ProductActionNotice;
115
+ needsRefresh?: boolean;
116
+ profileHref?: string;
117
+ reviewProps?: Omit<ProductReviewsProps, 'productId'>;
118
+ onLoad?: (
119
+ action: ProductPageLoadAction,
120
+ ) => Promise<IProduct | undefined> | IProduct | undefined;
121
+ onLoaded?: (product: IProduct) => void;
122
+ onMissing?: () => void;
123
+ onSelectSeller?: (seller: IProductSeller) => void;
124
+ onOpenPreorder?: (product: IProduct) => void;
125
+ onSubmitPreorder?: (
126
+ action: ProductPreorderAction,
127
+ ) => Promise<boolean | void> | boolean | void;
128
+ /**
129
+ * Opens the sign-in page from the preorder dialog when OrionMarketplace asks a guest
130
+ * to sign in first (error code `sign_in`).
131
+ */
132
+ onSignIn?: () => void;
133
+ onRetryRefresh?: () => Promise<void> | void;
134
+ }
135
+
136
+ const railInset = 40;
137
+ const emptyUnits = {};
138
+ const publishedPreviewStatuses: number[] = [
139
+ EProductStatus.PUBLISHED,
140
+ EProductStatus.LOW_STOCK,
141
+ EProductStatus.LOW_SALE_STOCK,
142
+ ];
143
+
144
+ function isModifiedClick(event: MouseEvent<HTMLAnchorElement>) {
145
+ return (
146
+ event.button > 0 ||
147
+ event.metaKey ||
148
+ event.ctrlKey ||
149
+ event.shiftKey ||
150
+ event.altKey
151
+ );
152
+ }
153
+
154
+ export function StorefrontProduct({
155
+ productId,
156
+ initialProduct,
157
+ preloaded = false,
158
+ previewKey = '',
159
+ loadOnMount = true,
160
+ initialError = '',
161
+ actionPending = false,
162
+ actionNotice,
163
+ needsRefresh = false,
164
+ profileHref,
165
+ reviewProps,
166
+ variantProductIds,
167
+ onLoad,
168
+ onLoaded,
169
+ onMissing,
170
+ onSelectSeller,
171
+ onOpenPreorder,
172
+ onSubmitPreorder,
173
+ onSignIn,
174
+ onRetryRefresh,
175
+ onToggleFavorite,
176
+ onSelectVariant,
177
+ onSelectSecondaryVariant,
178
+ onRequestSecondaryVariants,
179
+ onAdd,
180
+ onRemove,
181
+ onSubscribe,
182
+ onUnsubscribe,
183
+ }: StorefrontProductProps) {
184
+ const init = useOrionplace(selectInit);
185
+ const locale = useOrionplaceSelections((state) => state.localeCode);
186
+ const weightId = useOrionplaceSelections((state) => state.weightId);
187
+ const lengthId = useOrionplaceSelections((state) => state.lengthId);
188
+ const weights = useOrionplaceSelections((state) => state.init?.weights);
189
+ const lengths = useOrionplaceSelections((state) => state.init?.lengths);
190
+ const catalog = useOrionplaceCatalog((state) => state.catalog);
191
+ const productStore = useOrionplaceProductsStore();
192
+ const rootProduct = useOrionplaceProduct(productId);
193
+ const [currentId, setCurrentId] = useState(productId);
194
+ const storedProduct = useOrionplaceProduct(currentId);
195
+ const [localProduct, setLocalProduct] = useState<IProduct | undefined>(() =>
196
+ initialProduct?.id === productId
197
+ ? initialProduct
198
+ : preloaded
199
+ ? rootProduct
200
+ : undefined,
201
+ );
202
+ const [confirmed, setConfirmed] = useState(Boolean(localProduct));
203
+ const [settled, setSettled] = useState(Boolean(localProduct));
204
+ const [pending, setPending] = useState(false);
205
+ const [error, setError] = useState(initialError);
206
+ const [requestedVariantId, setRequestedVariantId] = useState<number>();
207
+ const requestedVariant = useOrionplaceProduct(
208
+ requestedVariantId ?? currentId,
209
+ );
210
+ const [image, setImage] = useState(0);
211
+ const swipeStart = useRef<{ x: number; y: number } | undefined>(undefined);
212
+ const [mounted, setMounted] = useState(false);
213
+ const [descriptionOpen, setDescriptionOpen] = useState(false);
214
+ const [descriptionLong, setDescriptionLong] = useState(false);
215
+ const [specificationsOpen, setSpecificationsOpen] = useState(false);
216
+ // A tap on the main photo opens it full screen; the page keeps the photo
217
+ // chosen there.
218
+ const [viewerOpen, setViewerOpen] = useState(false);
219
+ const [specificationsLong, setSpecificationsLong] = useState(false);
220
+ const [preorderId, setPreorderId] = useState<number>();
221
+ const [preorderPending, setPreorderPending] = useState(false);
222
+ const [preorderError, setPreorderError] = useState('');
223
+ const [preorderFields, setPreorderFields] = useState<Record<string, string>>({});
224
+ const [preorderSignIn, setPreorderSignIn] = useState(false);
225
+ const [railEdges, setRailEdges] = useState({ back: false, forward: false });
226
+ const request = useRef<AbortController | undefined>(undefined);
227
+ const preorderRequest = useRef<AbortController | undefined>(undefined);
228
+ const rail = useRef<HTMLDivElement>(null);
229
+ const descriptionBody = useRef<HTMLDivElement>(null);
230
+ const specificationsBody = useRef<HTMLDivElement>(null);
231
+ const callbacks = useRef({
232
+ onLoad,
233
+ onLoaded,
234
+ onMissing,
235
+ onSubmitPreorder,
236
+ });
237
+ callbacks.current = {
238
+ onLoad,
239
+ onLoaded,
240
+ onMissing,
241
+ onSubmitPreorder,
242
+ };
243
+
244
+ const labels = reviewLabels(locale);
245
+ const common = featureLabels(locale);
246
+ const cardLabels = productLabels(locale);
247
+ const actionLabels = productActionLabels(locale);
248
+ const categoryNames = categoryLabels(locale);
249
+ const product = confirmed
250
+ ? (storedProduct ??
251
+ (localProduct?.id === currentId ? localProduct : undefined))
252
+ : undefined;
253
+ const title =
254
+ getAvailableNestedValueFromObject(
255
+ product?.locale,
256
+ locale,
257
+ 'product_name',
258
+ ) || labels.product;
259
+ const descriptionSource =
260
+ getAvailableNestedValueFromObject(
261
+ product?.locale,
262
+ locale,
263
+ 'product_description',
264
+ ) || '';
265
+ const descriptionText = useMemo(
266
+ () => htmlTextParagraphs(descriptionSource),
267
+ [descriptionSource],
268
+ );
269
+ const descriptionHtml = useMemo(
270
+ () => (mounted ? safeBlogArticleHtml(descriptionSource) : ''),
271
+ [descriptionSource, mounted],
272
+ );
273
+ const hasDescription = mounted
274
+ ? Boolean(descriptionHtml)
275
+ : descriptionText.length > 0;
276
+ const photos = useMemo(() => productPagePhotos(product), [product]);
277
+ const manufacturer = useMemo(
278
+ () => productPageManufacturer(product, locale),
279
+ [product, locale],
280
+ );
281
+ const reviewStat = useMemo(() => productPageReviewStat(product), [product]);
282
+ const returns = useMemo(
283
+ () => productPageReturns(product, locale, labels),
284
+ [product, locale, labels],
285
+ );
286
+ const specifications = useMemo(
287
+ () =>
288
+ productPageSpecifications({
289
+ product,
290
+ locale,
291
+ weights: weights ?? emptyUnits,
292
+ lengths: lengths ?? emptyUnits,
293
+ weightId,
294
+ lengthId,
295
+ labels,
296
+ }),
297
+ [product, locale, weights, lengths, weightId, lengthId, labels],
298
+ );
299
+ const categoryPath =
300
+ typeof product?.main_category === 'number'
301
+ ? catalogTrailToCategory(catalog, product.main_category)
302
+ : [];
303
+ const breadcrumbs: IBreadcrumb[] = [
304
+ {
305
+ id: 'catalog',
306
+ label: categoryNames.catalog,
307
+ to: {
308
+ path: '/catalog',
309
+ query: { locale },
310
+ },
311
+ },
312
+ ...categoryPath.map((category) => ({
313
+ id: category.id,
314
+ label:
315
+ getAvailableNestedValueFromObject(category.locale, locale, 'cg_name') ||
316
+ `${categoryNames.category} ${category.id}`,
317
+ to: {
318
+ path: `/category/${category.id}`,
319
+ query: { locale },
320
+ },
321
+ })),
322
+ { id: `product-${currentId}`, label: title },
323
+ ];
324
+ const seller = init?.visual_settings?.single_seller_mode
325
+ ? undefined
326
+ : product?.seller;
327
+ const sellerHref = seller
328
+ ? `/sellers?locale=${encodeURIComponent(locale)}&seller=${seller.id}`
329
+ : '';
330
+ const safeProfileHref = safePublicUrl(
331
+ profileHref ?? `/profile?locale=${encodeURIComponent(locale)}`,
332
+ );
333
+ const previewStatus =
334
+ !previewKey || !product
335
+ ? ''
336
+ : product.status.status === EProductStatus.PUBLISHED_AS_OPTION
337
+ ? cardLabels.previewOption
338
+ : !publishedPreviewStatuses.includes(product.status.status)
339
+ ? cardLabels.previewUnpublished
340
+ : '';
341
+ const showSkeleton = !product && !error && (!settled || pending);
342
+
343
+ const load = useCallback(async () => {
344
+ if (!callbacks.current.onLoad) {
345
+ setSettled(true);
346
+ return;
347
+ }
348
+ request.current?.abort();
349
+ const controller = new AbortController();
350
+ request.current = controller;
351
+ setPending(true);
352
+ setError('');
353
+ try {
354
+ const found = await callbacks.current.onLoad({
355
+ endpoint: previewKey ? 'products_preview' : 'products_search',
356
+ request: {
357
+ filters: previewKey
358
+ ? { id: productId, view_key: previewKey }
359
+ : { id: productId },
360
+ paginate: { page: 1, pp_items: 1 },
361
+ },
362
+ signal: controller.signal,
363
+ });
364
+ if (controller.signal.aborted) {
365
+ return;
366
+ }
367
+ setSettled(true);
368
+ setConfirmed(Boolean(found));
369
+ if (found) {
370
+ productStore.getState().setProduct(found);
371
+ setLocalProduct(found);
372
+ setCurrentId(found.id);
373
+ } else {
374
+ callbacks.current.onMissing?.();
375
+ }
376
+ } catch (reason) {
377
+ if (!controller.signal.aborted) {
378
+ setSettled(true);
379
+ setError(
380
+ apiErrorMessage(reason) || common.failed,
381
+ );
382
+ }
383
+ } finally {
384
+ if (request.current === controller) {
385
+ request.current = undefined;
386
+ setPending(false);
387
+ }
388
+ }
389
+ }, [common.failed, previewKey, productId, productStore]);
390
+
391
+ useEffect(() => {
392
+ setMounted(true);
393
+ return () => {
394
+ request.current?.abort();
395
+ preorderRequest.current?.abort();
396
+ };
397
+ }, []);
398
+
399
+ useEffect(() => {
400
+ request.current?.abort();
401
+ const seeded =
402
+ initialProduct?.id === productId
403
+ ? initialProduct
404
+ : preloaded
405
+ ? productStore.getState().getProduct(productId)
406
+ : undefined;
407
+ setCurrentId(productId);
408
+ setRequestedVariantId(undefined);
409
+ setImage(0);
410
+ setLocalProduct(seeded);
411
+ setConfirmed(Boolean(seeded));
412
+ setSettled(Boolean(seeded));
413
+ setError(initialError);
414
+ setSpecificationsOpen(false);
415
+ setPreorderId(undefined);
416
+ if (seeded) {
417
+ productStore.getState().setProduct(seeded);
418
+ } else if (loadOnMount && callbacks.current.onLoad) {
419
+ void load();
420
+ }
421
+ }, [
422
+ productId,
423
+ previewKey,
424
+ preloaded,
425
+ initialProduct,
426
+ initialError,
427
+ loadOnMount,
428
+ load,
429
+ productStore,
430
+ ]);
431
+
432
+ const notifiedProduct = useRef<IProduct | undefined>(undefined);
433
+ useEffect(() => {
434
+ if (product && notifiedProduct.current !== product) {
435
+ notifiedProduct.current = product;
436
+ callbacks.current.onLoaded?.(product);
437
+ }
438
+ }, [product]);
439
+
440
+ useEffect(() => {
441
+ if (requestedVariantId && requestedVariant?.id === requestedVariantId) {
442
+ setCurrentId(requestedVariantId);
443
+ setRequestedVariantId(undefined);
444
+ setImage(0);
445
+ }
446
+ }, [requestedVariantId, requestedVariant]);
447
+
448
+ useEffect(() => {
449
+ setDescriptionOpen(false);
450
+ setDescriptionLong(false);
451
+ }, [descriptionSource]);
452
+
453
+ useEffect(() => {
454
+ setSpecificationsLong(false);
455
+ if (!specifications.length) {
456
+ setSpecificationsOpen(false);
457
+ }
458
+ }, [specifications]);
459
+
460
+ function railIsVertical(element: HTMLElement) {
461
+ return getComputedStyle(element).flexDirection === 'column';
462
+ }
463
+
464
+ function railMetrics(element: HTMLElement): ProductThumbnailRailMetrics {
465
+ return railIsVertical(element)
466
+ ? {
467
+ position: element.scrollTop,
468
+ size: element.scrollHeight,
469
+ viewport: element.clientHeight,
470
+ }
471
+ : {
472
+ position: element.scrollLeft,
473
+ size: element.scrollWidth,
474
+ viewport: element.clientWidth,
475
+ };
476
+ }
477
+
478
+ const updateRail = useCallback(() => {
479
+ const element = rail.current;
480
+ setRailEdges(
481
+ element
482
+ ? productThumbnailRailEdges(railMetrics(element))
483
+ : { back: false, forward: false },
484
+ );
485
+ }, []);
486
+
487
+ useEffect(() => {
488
+ const element = rail.current;
489
+ if (!element) {
490
+ setRailEdges({ back: false, forward: false });
491
+ return;
492
+ }
493
+ const observer =
494
+ typeof ResizeObserver === 'undefined'
495
+ ? undefined
496
+ : new ResizeObserver(updateRail);
497
+ observer?.observe(element);
498
+ updateRail();
499
+
500
+ return () => observer?.disconnect();
501
+ }, [photos, updateRail]);
502
+
503
+ useEffect(() => {
504
+ const element = descriptionBody.current;
505
+ if (!element || descriptionOpen) {
506
+ return;
507
+ }
508
+ const measure = () => {
509
+ setDescriptionLong(element.scrollHeight > element.clientHeight + 1);
510
+ };
511
+ const observer =
512
+ typeof ResizeObserver === 'undefined'
513
+ ? undefined
514
+ : new ResizeObserver(measure);
515
+ observer?.observe(element);
516
+ measure();
517
+
518
+ return () => observer?.disconnect();
519
+ }, [descriptionHtml, descriptionText, descriptionOpen]);
520
+
521
+ useEffect(() => {
522
+ const element = specificationsBody.current;
523
+ if (!element) {
524
+ return;
525
+ }
526
+ const measure = () => {
527
+ setSpecificationsLong(element.scrollHeight > element.clientHeight + 1);
528
+ };
529
+ const observer =
530
+ typeof ResizeObserver === 'undefined'
531
+ ? undefined
532
+ : new ResizeObserver(measure);
533
+ observer?.observe(element);
534
+ measure();
535
+
536
+ return () => observer?.disconnect();
537
+ }, [specifications]);
538
+
539
+ useEffect(() => {
540
+ const element = rail.current;
541
+ const thumbnail = element?.children[image];
542
+ if (!element || !(thumbnail instanceof HTMLElement)) {
543
+ return;
544
+ }
545
+ const bounds = railIsVertical(element)
546
+ ? { start: thumbnail.offsetTop, length: thumbnail.offsetHeight }
547
+ : { start: thumbnail.offsetLeft, length: thumbnail.offsetWidth };
548
+ const target = productThumbnailScrollTarget(
549
+ bounds,
550
+ railMetrics(element),
551
+ railInset,
552
+ );
553
+ if (target !== undefined) {
554
+ scrollRail(element, target);
555
+ }
556
+ }, [image]);
557
+
558
+ useEffect(() => {
559
+ setImage(0);
560
+ if (rail.current) {
561
+ rail.current.scrollTop = 0;
562
+ rail.current.scrollLeft = 0;
563
+ }
564
+ updateRail();
565
+ }, [photos, updateRail]);
566
+
567
+ const viewerPhotos = useMemo(
568
+ () => photos.map((photo, id) => ({ id, image: photo })),
569
+ [photos],
570
+ );
571
+
572
+ function openViewerKey(event: { key: string; preventDefault: () => void }) {
573
+ if (event.key === 'Enter' || event.key === ' ') {
574
+ event.preventDefault();
575
+ setViewerOpen(true);
576
+ }
577
+ }
578
+
579
+ function showImage(index: number) {
580
+ if (photos.length) {
581
+ setImage((index + photos.length) % photos.length);
582
+ }
583
+ }
584
+
585
+ // A sideways swipe on a touch screen steps to the next or previous photo,
586
+ // as on the product cards.
587
+ function startSwipe(event: TouchEvent<HTMLElement>) {
588
+ const touch = event.touches[0];
589
+ swipeStart.current =
590
+ touch && event.touches.length === 1
591
+ ? { x: touch.clientX, y: touch.clientY }
592
+ : undefined;
593
+ }
594
+
595
+ function endSwipe(event: TouchEvent<HTMLElement>) {
596
+ const touch = event.changedTouches[0];
597
+ const start = swipeStart.current;
598
+
599
+ swipeStart.current = undefined;
600
+ if (!start || !touch) {
601
+ return;
602
+ }
603
+ showImage(
604
+ productPhotoAfterSwipe(
605
+ image,
606
+ photos.length,
607
+ touch.clientX - start.x,
608
+ touch.clientY - start.y,
609
+ ),
610
+ );
611
+ }
612
+
613
+ function scrollRail(element: HTMLElement, position: number) {
614
+ const reduced = window.matchMedia(
615
+ '(prefers-reduced-motion: reduce)',
616
+ ).matches;
617
+ const behavior = reduced ? 'auto' : 'smooth';
618
+ if (railIsVertical(element)) {
619
+ element.scrollTo({ top: position, behavior });
620
+ } else {
621
+ element.scrollTo({ left: position, behavior });
622
+ }
623
+ }
624
+
625
+ function stepRail(direction: 1 | -1) {
626
+ const element = rail.current;
627
+ const thumbnail = element?.firstElementChild;
628
+ if (!element || !(thumbnail instanceof HTMLElement)) {
629
+ return;
630
+ }
631
+ const style = getComputedStyle(element);
632
+ const step = railIsVertical(element)
633
+ ? thumbnail.offsetHeight + Number.parseFloat(style.rowGap || '0')
634
+ : thumbnail.offsetWidth + Number.parseFloat(style.columnGap || '0');
635
+ scrollRail(element, railMetrics(element).position + direction * step);
636
+ }
637
+
638
+ function thumbnailKeydown(
639
+ event: KeyboardEvent<HTMLButtonElement>,
640
+ index: number,
641
+ ) {
642
+ const target = productThumbnailKeyTarget(event.key, index, photos.length);
643
+ if (target === undefined) {
644
+ return;
645
+ }
646
+ event.preventDefault();
647
+ setImage(target);
648
+ const button = rail.current?.children[target];
649
+ if (button instanceof HTMLElement) {
650
+ button.focus({ preventScroll: true });
651
+ }
652
+ }
653
+
654
+ function selectMainVariant(
655
+ selection: Parameters<NonNullable<ProductCardProps['onSelectVariant']>>[0],
656
+ ) {
657
+ const target = selection.variant.reload_product;
658
+ if (target) {
659
+ setRequestedVariantId(target);
660
+ const available = productStore.getState().getProduct(target);
661
+ if (available) {
662
+ setCurrentId(target);
663
+ setRequestedVariantId(undefined);
664
+ setImage(0);
665
+ }
666
+ }
667
+ onSelectVariant?.(selection);
668
+ }
669
+
670
+ function selectSecondaryVariant(
671
+ selection: Parameters<
672
+ NonNullable<ProductCardProps['onSelectSecondaryVariant']>
673
+ >[0],
674
+ ) {
675
+ setCurrentId(selection.variant.product.id);
676
+ setImage(0);
677
+ onSelectSecondaryVariant?.(selection);
678
+ }
679
+
680
+ function openPreorder(selection: { product: number }) {
681
+ const selected = productStore.getState().getProduct(selection.product);
682
+ setPreorderError('');
683
+ setPreorderFields({});
684
+ setPreorderSignIn(false);
685
+ setPreorderId(selection.product);
686
+ if (selected) {
687
+ onOpenPreorder?.(selected);
688
+ }
689
+ }
690
+
691
+ async function submitPreorder(selection: ProductPreorderSelection) {
692
+ if (!callbacks.current.onSubmitPreorder || preorderPending) {
693
+ return;
694
+ }
695
+ const controller = new AbortController();
696
+ preorderRequest.current = controller;
697
+ setPreorderPending(true);
698
+ setPreorderError('');
699
+ setPreorderFields({});
700
+ setPreorderSignIn(false);
701
+ try {
702
+ const saved = await callbacks.current.onSubmitPreorder({
703
+ endpoint: 'preorders_create',
704
+ request: selection,
705
+ signal: controller.signal,
706
+ });
707
+ if (!controller.signal.aborted && saved !== false) {
708
+ setPreorderId(undefined);
709
+ }
710
+ } catch (reason) {
711
+ if (!controller.signal.aborted) {
712
+ setPreorderError(
713
+ apiErrorMessage(reason) || actionLabels.failed,
714
+ );
715
+ if (reason instanceof OrionplaceApiError) {
716
+ setPreorderFields(reason.fields);
717
+ setPreorderSignIn(reason.codes.includes('sign_in'));
718
+ }
719
+ }
720
+ } finally {
721
+ if (preorderRequest.current === controller) {
722
+ preorderRequest.current = undefined;
723
+ setPreorderPending(false);
724
+ }
725
+ }
726
+ }
727
+
728
+ function information() {
729
+ if (!hasDescription && !specifications.length) {
730
+ return null;
731
+ }
732
+
733
+ return (
734
+ <div
735
+ className={styles.information}
736
+ data-product-information
737
+ >
738
+ {hasDescription ? (
739
+ <section
740
+ className={styles.informationSection}
741
+ data-product-description
742
+ >
743
+ <h2>{labels.description}</h2>
744
+ {mounted ? (
745
+ <div
746
+ ref={descriptionBody}
747
+ id="product-description-text"
748
+ className={`${styles.description} ${
749
+ descriptionOpen ? '' : styles.descriptionShort
750
+ } ${
751
+ !descriptionOpen && descriptionLong
752
+ ? styles.descriptionFade
753
+ : ''
754
+ }`}
755
+ dangerouslySetInnerHTML={{ __html: descriptionHtml }}
756
+ />
757
+ ) : (
758
+ <div
759
+ ref={descriptionBody}
760
+ id="product-description-text"
761
+ className={`${styles.description} ${styles.descriptionShort}`}
762
+ >
763
+ {descriptionText.map((paragraph, index) => (
764
+ <p key={`${index}:${paragraph}`}>{paragraph}</p>
765
+ ))}
766
+ </div>
767
+ )}
768
+ {descriptionLong ? (
769
+ <button
770
+ type="button"
771
+ className={styles.textAction}
772
+ aria-controls="product-description-text"
773
+ aria-expanded={descriptionOpen}
774
+ data-product-description-toggle
775
+ onClick={() => setDescriptionOpen((value) => !value)}
776
+ >
777
+ {descriptionOpen ? labels.less : labels.more}
778
+ </button>
779
+ ) : null}
780
+ </section>
781
+ ) : null}
782
+
783
+ {specifications.length ? (
784
+ <section
785
+ className={styles.informationSection}
786
+ data-product-specifications
787
+ >
788
+ <h2>{labels.characteristics}</h2>
789
+ <div
790
+ ref={specificationsBody}
791
+ id="product-specifications-list"
792
+ className={`${styles.specifications} ${
793
+ specificationsLong ? styles.specificationsFade : ''
794
+ }`}
795
+ >
796
+ {specifications.map((group) => (
797
+ <div
798
+ key={group.key}
799
+ className={styles.specificationGroup}
800
+ >
801
+ {group.title ? <h3>{group.title}</h3> : null}
802
+ <dl>
803
+ {group.rows.map((row) => (
804
+ <div key={row.key}>
805
+ <dt>{row.label}</dt>
806
+ <dd>
807
+ {row.to ? (
808
+ <Link
809
+ href={{
810
+ pathname: row.to.path,
811
+ query: row.to.query,
812
+ }}
813
+ >
814
+ {row.value}
815
+ </Link>
816
+ ) : (
817
+ row.value
818
+ )}
819
+ </dd>
820
+ </div>
821
+ ))}
822
+ </dl>
823
+ </div>
824
+ ))}
825
+ </div>
826
+ {specificationsLong ? (
827
+ <button
828
+ type="button"
829
+ className={styles.textAction}
830
+ aria-haspopup="dialog"
831
+ data-product-specifications-toggle
832
+ onClick={() => setSpecificationsOpen(true)}
833
+ >
834
+ {labels.allCharacteristics}
835
+ </button>
836
+ ) : null}
837
+ </section>
838
+ ) : null}
839
+ </div>
840
+ );
841
+ }
842
+
843
+ const reviewCaption = reviewStat
844
+ ? pluralReviewWord(reviewStat.count, cardLabels.reviews, locale)
845
+ : '';
846
+
847
+ return (
848
+ <section
849
+ className={styles.root}
850
+ data-feature-part="StorefrontProduct"
851
+ aria-busy={showSkeleton || undefined}
852
+ >
853
+ {error ? (
854
+ <div
855
+ className={styles.error}
856
+ role="alert"
857
+ >
858
+ {error}
859
+ <button
860
+ className={styles.noticeAction}
861
+ type="button"
862
+ disabled={pending}
863
+ onClick={() => void load()}
864
+ >
865
+ {common.retry}
866
+ </button>
867
+ </div>
868
+ ) : null}
869
+ {pending || showSkeleton ? (
870
+ <p
871
+ className="op-feature-loading"
872
+ role="status"
873
+ >
874
+ <span className={styles.screenReaderOnly}>{common.loading}</span>
875
+ </p>
876
+ ) : null}
877
+ {product || showSkeleton ? (
878
+ <div className={styles.breadcrumbs}>
879
+ <OrionplaceBreadcrumbs
880
+ items={breadcrumbs}
881
+ loading={!product}
882
+ />
883
+ </div>
884
+ ) : null}
885
+
886
+ {product ? (
887
+ <>
888
+ <div className={styles.layout}>
889
+ {photos.length ? (
890
+ <section
891
+ className={styles.gallery}
892
+ aria-label={labels.gallery}
893
+ data-product-gallery
894
+ >
895
+ <div
896
+ className={
897
+ photos.length > 1
898
+ ? `${styles.mainImage} ${styles.mainImageSwipe}`
899
+ : styles.mainImage
900
+ }
901
+ data-product-main-image
902
+ onTouchStart={startSwipe}
903
+ onTouchEnd={endSwipe}
904
+ >
905
+ <OrionplaceImage
906
+ src={photos[image]}
907
+ alt={title}
908
+ width={800}
909
+ height={1000}
910
+ loading="eager"
911
+ role="button"
912
+ tabIndex={0}
913
+ aria-label={labels.openProductPhoto}
914
+ data-product-photo-open
915
+ onClick={() => setViewerOpen(true)}
916
+ onKeyDown={openViewerKey}
917
+ className={styles.mainImageAsset}
918
+ />
919
+ {photos.length > 1 ? (
920
+ <>
921
+ <button
922
+ type="button"
923
+ className={`${styles.imageArrow} ${styles.imagePrevious}`}
924
+ aria-label={labels.previousImage}
925
+ data-product-image-previous
926
+ onClick={() => showImage(image - 1)}
927
+ >
928
+ <svg
929
+ viewBox="0 0 24 24"
930
+ fill="none"
931
+ stroke="currentColor"
932
+ strokeWidth="1.5"
933
+ aria-hidden="true"
934
+ >
935
+ <path d="m15 5-7 7 7 7" />
936
+ </svg>
937
+ </button>
938
+ <button
939
+ type="button"
940
+ className={`${styles.imageArrow} ${styles.imageNext}`}
941
+ aria-label={labels.nextImage}
942
+ data-product-image-next
943
+ onClick={() => showImage(image + 1)}
944
+ >
945
+ <svg
946
+ viewBox="0 0 24 24"
947
+ fill="none"
948
+ stroke="currentColor"
949
+ strokeWidth="1.5"
950
+ aria-hidden="true"
951
+ >
952
+ <path d="m9 5 7 7-7 7" />
953
+ </svg>
954
+ </button>
955
+ </>
956
+ ) : null}
957
+ </div>
958
+ {photos.length > 1 ? (
959
+ <div
960
+ className={styles.thumbnailRail}
961
+ data-product-thumbnail-rail
962
+ >
963
+ <div
964
+ ref={rail}
965
+ className={styles.thumbnails}
966
+ data-product-thumbnails
967
+ onScroll={updateRail}
968
+ >
969
+ {photos.map((photo, index) => (
970
+ <button
971
+ key={`${photo}:${index}`}
972
+ type="button"
973
+ className={`${styles.thumbnail} ${
974
+ index === image ? styles.thumbnailSelected : ''
975
+ }`}
976
+ aria-label={`${labels.gallery} ${index + 1}`}
977
+ aria-pressed={index === image}
978
+ tabIndex={index === image ? 0 : -1}
979
+ data-product-thumbnail={index}
980
+ onClick={() => setImage(index)}
981
+ onKeyDown={(event) => thumbnailKeydown(event, index)}
982
+ >
983
+ <OrionplaceImage
984
+ src={photo}
985
+ alt=""
986
+ width={72}
987
+ height={90}
988
+ className={styles.thumbnailImage}
989
+ />
990
+ </button>
991
+ ))}
992
+ </div>
993
+ <button
994
+ type="button"
995
+ className={`${styles.railArrow} ${styles.railPrevious}`}
996
+ aria-label={labels.thumbnailsBack}
997
+ disabled={!railEdges.back}
998
+ data-product-thumbnails-previous
999
+ onClick={() => stepRail(-1)}
1000
+ >
1001
+ <svg
1002
+ viewBox="0 0 24 24"
1003
+ fill="none"
1004
+ stroke="currentColor"
1005
+ strokeWidth="1.5"
1006
+ aria-hidden="true"
1007
+ >
1008
+ <path d="m15 5-7 7 7 7" />
1009
+ </svg>
1010
+ </button>
1011
+ <button
1012
+ type="button"
1013
+ className={`${styles.railArrow} ${styles.railNext}`}
1014
+ aria-label={labels.thumbnailsForward}
1015
+ disabled={!railEdges.forward}
1016
+ data-product-thumbnails-next
1017
+ onClick={() => stepRail(1)}
1018
+ >
1019
+ <svg
1020
+ viewBox="0 0 24 24"
1021
+ fill="none"
1022
+ stroke="currentColor"
1023
+ strokeWidth="1.5"
1024
+ aria-hidden="true"
1025
+ >
1026
+ <path d="m9 5 7 7-7 7" />
1027
+ </svg>
1028
+ </button>
1029
+ </div>
1030
+ ) : null}
1031
+ </section>
1032
+ ) : null}
1033
+
1034
+ <div
1035
+ className={styles.summary}
1036
+ data-product-summary
1037
+ >
1038
+ <header className={styles.heading}>
1039
+ <h1>{title}</h1>
1040
+ {reviewStat ? (
1041
+ <a
1042
+ href="#product-reviews"
1043
+ className={styles.reviewSummary}
1044
+ aria-label={`${labels.showReviews}: ${reviewStat.average} ${
1045
+ labels.ofFive
1046
+ }, ${reviewStat.count} ${reviewCaption}`}
1047
+ data-product-review-summary
1048
+ onClick={(event) => {
1049
+ event.preventDefault();
1050
+ document
1051
+ .getElementById('product-reviews')
1052
+ ?.scrollIntoView({
1053
+ behavior: 'smooth',
1054
+ block: 'start',
1055
+ });
1056
+ }}
1057
+ >
1058
+ <span
1059
+ className={styles.stars}
1060
+ aria-hidden="true"
1061
+ >
1062
+ {[1, 2, 3, 4, 5].map((star) => (
1063
+ <span
1064
+ key={star}
1065
+ className={
1066
+ star <= Math.round(reviewStat.average)
1067
+ ? styles.starSelected
1068
+ : ''
1069
+ }
1070
+ >
1071
+ ★
1072
+ </span>
1073
+ ))}
1074
+ </span>
1075
+ <span className={styles.reviewAverage}>
1076
+ {reviewStat.average}
1077
+ </span>
1078
+ <span>
1079
+ · {reviewStat.count} {reviewCaption}
1080
+ </span>
1081
+ </a>
1082
+ ) : null}
1083
+ </header>
1084
+
1085
+ {previewStatus ? (
1086
+ <p
1087
+ className={styles.attention}
1088
+ role="status"
1089
+ data-product-preview-status
1090
+ >
1091
+ {previewStatus}
1092
+ </p>
1093
+ ) : null}
1094
+
1095
+ <ProductCard
1096
+ key={currentId}
1097
+ productId={currentId}
1098
+ initialProduct={product}
1099
+ details
1100
+ actionsDisabled={actionPending || needsRefresh}
1101
+ variantProductIds={variantProductIds}
1102
+ onSelectSeller={onSelectSeller}
1103
+ onAdd={onAdd}
1104
+ onRemove={onRemove}
1105
+ onToggleFavorite={onToggleFavorite}
1106
+ onSubscribe={onSubscribe}
1107
+ onUnsubscribe={onUnsubscribe}
1108
+ onPreorder={openPreorder}
1109
+ onRequestSecondaryVariants={onRequestSecondaryVariants}
1110
+ onSelectVariant={selectMainVariant}
1111
+ onSelectSecondaryVariant={selectSecondaryVariant}
1112
+ renderAfterPrice={information}
1113
+ />
1114
+
1115
+ {actionNotice?.message ? (
1116
+ <div
1117
+ role={actionNotice.tone === 'error' ? 'alert' : 'status'}
1118
+ className={`${styles.notice} ${
1119
+ actionNotice.tone === 'error'
1120
+ ? styles.noticeError
1121
+ : styles.noticeSuccess
1122
+ }`}
1123
+ data-product-action-notice
1124
+ >
1125
+ {actionNotice.message}
1126
+ {needsRefresh && onRetryRefresh ? (
1127
+ <button
1128
+ className={styles.noticeAction}
1129
+ type="button"
1130
+ disabled={actionPending}
1131
+ onClick={() => void onRetryRefresh()}
1132
+ >
1133
+ {actionLabels.retry}
1134
+ </button>
1135
+ ) : null}
1136
+ {actionNotice.emailRequired && safeProfileHref ? (
1137
+ <a
1138
+ className={styles.noticeAction}
1139
+ href={safeProfileHref}
1140
+ >
1141
+ {common.navigation.profile}
1142
+ </a>
1143
+ ) : null}
1144
+ </div>
1145
+ ) : null}
1146
+
1147
+ {returns.length ? (
1148
+ <section
1149
+ className={styles.returns}
1150
+ data-product-returns
1151
+ >
1152
+ <h2>{labels.returns}</h2>
1153
+ <dl>
1154
+ {returns.map((entry) => (
1155
+ <div key={entry.key}>
1156
+ <dt>{entry.label}</dt>
1157
+ <dd>{entry.value}</dd>
1158
+ </div>
1159
+ ))}
1160
+ </dl>
1161
+ </section>
1162
+ ) : null}
1163
+
1164
+ {manufacturer || seller ? (
1165
+ <div className={styles.partners}>
1166
+ {manufacturer ? (
1167
+ <Link
1168
+ href={{
1169
+ pathname: manufacturer.to.path,
1170
+ query: manufacturer.to.query,
1171
+ }}
1172
+ className={styles.partner}
1173
+ aria-label={`${labels.manufacturerHint}: ${manufacturer.name}`}
1174
+ data-product-manufacturer
1175
+ >
1176
+ {manufacturer.logo ? (
1177
+ <span data-product-manufacturer-logo>
1178
+ <OrionplaceImage
1179
+ src={manufacturer.logo}
1180
+ alt=""
1181
+ width={64}
1182
+ height={40}
1183
+ className={styles.partnerLogo}
1184
+ dataLogoType="manufacturer"
1185
+ />
1186
+ </span>
1187
+ ) : (
1188
+ <OrionplaceAvatar name={manufacturer.name} />
1189
+ )}
1190
+ <span className={styles.partnerText}>
1191
+ <small>{labels.manufacturer}</small>
1192
+ <span>{manufacturer.name}</span>
1193
+ </span>
1194
+ <svg
1195
+ viewBox="0 0 24 24"
1196
+ fill="none"
1197
+ stroke="currentColor"
1198
+ strokeWidth="1.5"
1199
+ aria-hidden="true"
1200
+ >
1201
+ <path d="m9 5 7 7-7 7" />
1202
+ </svg>
1203
+ </Link>
1204
+ ) : null}
1205
+ {seller ? (
1206
+ <a
1207
+ href={sellerHref}
1208
+ className={styles.partner}
1209
+ aria-label={`${labels.sellerHint}: ${seller.name}`}
1210
+ data-product-seller-link
1211
+ onClick={(event) => {
1212
+ if (!isModifiedClick(event) && onSelectSeller) {
1213
+ event.preventDefault();
1214
+ onSelectSeller(seller);
1215
+ }
1216
+ }}
1217
+ >
1218
+ <OrionplaceAvatar
1219
+ src={seller.avatar}
1220
+ name={seller.name}
1221
+ />
1222
+ <span className={styles.partnerText}>
1223
+ <small>{labels.seller}</small>
1224
+ <span>
1225
+ {seller.name}
1226
+ {Number(seller.raiting) > 0 ? (
1227
+ <em>★ {seller.raiting}</em>
1228
+ ) : null}
1229
+ </span>
1230
+ </span>
1231
+ <svg
1232
+ viewBox="0 0 24 24"
1233
+ fill="none"
1234
+ stroke="currentColor"
1235
+ strokeWidth="1.5"
1236
+ aria-hidden="true"
1237
+ >
1238
+ <path d="m9 5 7 7-7 7" />
1239
+ </svg>
1240
+ </a>
1241
+ ) : null}
1242
+ </div>
1243
+ ) : null}
1244
+ </div>
1245
+ </div>
1246
+
1247
+ <div
1248
+ id="product-reviews"
1249
+ className={styles.reviews}
1250
+ >
1251
+ <ProductReviews
1252
+ key={currentId}
1253
+ {...reviewProps}
1254
+ productId={currentId}
1255
+ />
1256
+ </div>
1257
+ {preorderId !== undefined ? (
1258
+ <ProductPreorderDialog
1259
+ productId={preorderId}
1260
+ pending={preorderPending}
1261
+ error={preorderError}
1262
+ fieldErrors={preorderFields}
1263
+ suggestSignIn={preorderSignIn}
1264
+ onSubmit={(selection) => void submitPreorder(selection)}
1265
+ onClose={() => setPreorderId(undefined)}
1266
+ onSignIn={onSignIn}
1267
+ />
1268
+ ) : null}
1269
+ {specificationsOpen ? (
1270
+ <ProductSpecificationsDialog
1271
+ groups={specifications}
1272
+ onClose={() => setSpecificationsOpen(false)}
1273
+ />
1274
+ ) : null}
1275
+ {viewerOpen && photos.length ? (
1276
+ <ProductReviewImageViewer
1277
+ images={viewerPhotos}
1278
+ index={image}
1279
+ label={labels.productPhotos}
1280
+ alt={title}
1281
+ onChange={(photo) => setImage(photo.index)}
1282
+ onClose={() => setViewerOpen(false)}
1283
+ />
1284
+ ) : null}
1285
+ </>
1286
+ ) : showSkeleton ? (
1287
+ <ProductSkeleton />
1288
+ ) : !pending && !error ? (
1289
+ <OrionplaceEmptyState
1290
+ kind="search"
1291
+ message={labels.notFound}
1292
+ />
1293
+ ) : null}
1294
+ </section>
1295
+ );
1296
+ }
1297
+
1298
+ function pluralReviewWord(count: number, forms: string[], locale: string) {
1299
+ let rule: Intl.LDMLPluralRule;
1300
+ try {
1301
+ rule = new Intl.PluralRules(locale).select(count);
1302
+ } catch {
1303
+ rule = new Intl.PluralRules('en').select(count);
1304
+ }
1305
+ if (rule === 'one') {
1306
+ return forms[0] ?? '';
1307
+ }
1308
+ if (rule === 'few' && forms.length > 2) {
1309
+ return forms[1] ?? '';
1310
+ }
1311
+
1312
+ return forms.at(-1) ?? '';
1313
+ }
1314
+
1315
+ function ProductSkeleton() {
1316
+ return (
1317
+ <div
1318
+ className={styles.skeleton}
1319
+ aria-hidden="true"
1320
+ data-product-skeleton
1321
+ >
1322
+ <div className={styles.skeletonGallery}>
1323
+ <span className={styles.skeletonImage} />
1324
+ <div className={styles.skeletonThumbs}>
1325
+ {[1, 2, 3, 4].map((item) => (
1326
+ <span key={item} />
1327
+ ))}
1328
+ </div>
1329
+ </div>
1330
+ <div className={styles.skeletonSummary}>
1331
+ <span className={styles.skeletonTitle} />
1332
+ <span className={styles.skeletonPrice} />
1333
+ {[1, 2, 3, 4, 5, 6].map((item) => (
1334
+ <span key={item} />
1335
+ ))}
1336
+ </div>
1337
+ </div>
1338
+ );
1339
+ }