@aglyn/plugins-commerce 1.0.0-beta.143

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 (417) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +10 -0
  3. package/package.json +57 -0
  4. package/src/index.d.ts +38 -0
  5. package/src/index.js +38 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/account.d.ts +31 -0
  8. package/src/lib/components/account.js +645 -0
  9. package/src/lib/components/account.js.map +1 -0
  10. package/src/lib/components/cart.d.ts +38 -0
  11. package/src/lib/components/cart.js +851 -0
  12. package/src/lib/components/cart.js.map +1 -0
  13. package/src/lib/components/commerce-console-page.d.ts +32 -0
  14. package/src/lib/components/commerce-console-page.js +296 -0
  15. package/src/lib/components/commerce-console-page.js.map +1 -0
  16. package/src/lib/components/commerce-console-sections.d.ts +34 -0
  17. package/src/lib/components/commerce-console-sections.js +62 -0
  18. package/src/lib/components/commerce-console-sections.js.map +1 -0
  19. package/src/lib/components/console/catalog-organization-card.component.d.ts +14 -0
  20. package/src/lib/components/console/catalog-organization-card.component.js +1087 -0
  21. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -0
  22. package/src/lib/components/console/commerce-analytics-card.component.d.ts +27 -0
  23. package/src/lib/components/console/commerce-analytics-card.component.js +310 -0
  24. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -0
  25. package/src/lib/components/console/commerce-glance-card.component.d.ts +20 -0
  26. package/src/lib/components/console/commerce-glance-card.component.js +340 -0
  27. package/src/lib/components/console/commerce-glance-card.component.js.map +1 -0
  28. package/src/lib/components/console/commerce-stat-tile.component.d.ts +37 -0
  29. package/src/lib/components/console/commerce-stat-tile.component.js +72 -0
  30. package/src/lib/components/console/commerce-stat-tile.component.js.map +1 -0
  31. package/src/lib/components/console/discounts-card.component.d.ts +14 -0
  32. package/src/lib/components/console/discounts-card.component.js +443 -0
  33. package/src/lib/components/console/discounts-card.component.js.map +1 -0
  34. package/src/lib/components/console/entitlement-gate.component.d.ts +76 -0
  35. package/src/lib/components/console/entitlement-gate.component.js +115 -0
  36. package/src/lib/components/console/entitlement-gate.component.js.map +1 -0
  37. package/src/lib/components/console/gift-cards-card.component.d.ts +29 -0
  38. package/src/lib/components/console/gift-cards-card.component.js +379 -0
  39. package/src/lib/components/console/gift-cards-card.component.js.map +1 -0
  40. package/src/lib/components/console/host-coupons-card.component.d.ts +14 -0
  41. package/src/lib/components/console/host-coupons-card.component.js +252 -0
  42. package/src/lib/components/console/host-coupons-card.component.js.map +1 -0
  43. package/src/lib/components/console/host-orders-card.component.d.ts +13 -0
  44. package/src/lib/components/console/host-orders-card.component.js +861 -0
  45. package/src/lib/components/console/host-orders-card.component.js.map +1 -0
  46. package/src/lib/components/console/locations-card.component.d.ts +14 -0
  47. package/src/lib/components/console/locations-card.component.js +256 -0
  48. package/src/lib/components/console/locations-card.component.js.map +1 -0
  49. package/src/lib/components/console/member-posts-card.component.d.ts +8 -0
  50. package/src/lib/components/console/member-posts-card.component.js +283 -0
  51. package/src/lib/components/console/member-posts-card.component.js.map +1 -0
  52. package/src/lib/components/console/newest-site-users-card.component.d.ts +36 -0
  53. package/src/lib/components/console/newest-site-users-card.component.js +126 -0
  54. package/src/lib/components/console/newest-site-users-card.component.js.map +1 -0
  55. package/src/lib/components/console/order-detail-dialog.component.d.ts +28 -0
  56. package/src/lib/components/console/order-detail-dialog.component.js +976 -0
  57. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -0
  58. package/src/lib/components/console/paid-media.d.ts +127 -0
  59. package/src/lib/components/console/paid-media.js +397 -0
  60. package/src/lib/components/console/paid-media.js.map +1 -0
  61. package/src/lib/components/console/payments-settings-card.component.d.ts +13 -0
  62. package/src/lib/components/console/payments-settings-card.component.js +295 -0
  63. package/src/lib/components/console/payments-settings-card.component.js.map +1 -0
  64. package/src/lib/components/console/pos-page.component.d.ts +13 -0
  65. package/src/lib/components/console/pos-page.component.js +916 -0
  66. package/src/lib/components/console/pos-page.component.js.map +1 -0
  67. package/src/lib/components/console/product-editor-dialog.component.d.ts +32 -0
  68. package/src/lib/components/console/product-editor-dialog.component.js +1374 -0
  69. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -0
  70. package/src/lib/components/console/products-hub-card.component.d.ts +16 -0
  71. package/src/lib/components/console/products-hub-card.component.js +1280 -0
  72. package/src/lib/components/console/products-hub-card.component.js.map +1 -0
  73. package/src/lib/components/console/products-hub-zone.component.d.ts +50 -0
  74. package/src/lib/components/console/products-hub-zone.component.js +200 -0
  75. package/src/lib/components/console/products-hub-zone.component.js.map +1 -0
  76. package/src/lib/components/console/recovery-queue-card.component.d.ts +39 -0
  77. package/src/lib/components/console/recovery-queue-card.component.js +262 -0
  78. package/src/lib/components/console/recovery-queue-card.component.js.map +1 -0
  79. package/src/lib/components/console/registers-card.component.d.ts +21 -0
  80. package/src/lib/components/console/registers-card.component.js +296 -0
  81. package/src/lib/components/console/registers-card.component.js.map +1 -0
  82. package/src/lib/components/console/reservations-card.component.d.ts +14 -0
  83. package/src/lib/components/console/reservations-card.component.js +650 -0
  84. package/src/lib/components/console/reservations-card.component.js.map +1 -0
  85. package/src/lib/components/console/reviews-moderation-card.component.d.ts +8 -0
  86. package/src/lib/components/console/reviews-moderation-card.component.js +235 -0
  87. package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -0
  88. package/src/lib/components/console/shipping-settings-card.component.d.ts +38 -0
  89. package/src/lib/components/console/shipping-settings-card.component.js +560 -0
  90. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -0
  91. package/src/lib/components/console/stock-movements-card.component.d.ts +31 -0
  92. package/src/lib/components/console/stock-movements-card.component.js +333 -0
  93. package/src/lib/components/console/stock-movements-card.component.js.map +1 -0
  94. package/src/lib/components/console/store-settings-card.component.d.ts +14 -0
  95. package/src/lib/components/console/store-settings-card.component.js +300 -0
  96. package/src/lib/components/console/store-settings-card.component.js.map +1 -0
  97. package/src/lib/components/console/storefront-tax-summary-card.component.d.ts +73 -0
  98. package/src/lib/components/console/storefront-tax-summary-card.component.js +408 -0
  99. package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -0
  100. package/src/lib/components/console/suppliers-card.component.d.ts +8 -0
  101. package/src/lib/components/console/suppliers-card.component.js +328 -0
  102. package/src/lib/components/console/suppliers-card.component.js.map +1 -0
  103. package/src/lib/components/console/tax-settings-card.component.d.ts +14 -0
  104. package/src/lib/components/console/tax-settings-card.component.js +473 -0
  105. package/src/lib/components/console/tax-settings-card.component.js.map +1 -0
  106. package/src/lib/components/gate.d.ts +39 -0
  107. package/src/lib/components/gate.js +202 -0
  108. package/src/lib/components/gate.js.map +1 -0
  109. package/src/lib/components/gated-video.d.ts +40 -0
  110. package/src/lib/components/gated-video.js +366 -0
  111. package/src/lib/components/gated-video.js.map +1 -0
  112. package/src/lib/components/member-feed.d.ts +32 -0
  113. package/src/lib/components/member-feed.js +198 -0
  114. package/src/lib/components/member-feed.js.map +1 -0
  115. package/src/lib/components/member-recovery.d.ts +35 -0
  116. package/src/lib/components/member-recovery.js +313 -0
  117. package/src/lib/components/member-recovery.js.map +1 -0
  118. package/src/lib/components/member-signin.d.ts +35 -0
  119. package/src/lib/components/member-signin.js +257 -0
  120. package/src/lib/components/member-signin.js.map +1 -0
  121. package/src/lib/components/member-signup.d.ts +34 -0
  122. package/src/lib/components/member-signup.js +287 -0
  123. package/src/lib/components/member-signup.js.map +1 -0
  124. package/src/lib/components/newsletter-signup.d.ts +39 -0
  125. package/src/lib/components/newsletter-signup.js +229 -0
  126. package/src/lib/components/newsletter-signup.js.map +1 -0
  127. package/src/lib/components/product-detail.d.ts +38 -0
  128. package/src/lib/components/product-detail.js +1022 -0
  129. package/src/lib/components/product-detail.js.map +1 -0
  130. package/src/lib/components/product-grid.d.ts +67 -0
  131. package/src/lib/components/product-grid.js +909 -0
  132. package/src/lib/components/product-grid.js.map +1 -0
  133. package/src/lib/components/product-reviews.d.ts +32 -0
  134. package/src/lib/components/product-reviews.js +349 -0
  135. package/src/lib/components/product-reviews.js.map +1 -0
  136. package/src/lib/components/related-products.d.ts +33 -0
  137. package/src/lib/components/related-products.js +232 -0
  138. package/src/lib/components/related-products.js.map +1 -0
  139. package/src/lib/components/reservation-widget.d.ts +33 -0
  140. package/src/lib/components/reservation-widget.js +418 -0
  141. package/src/lib/components/reservation-widget.js.map +1 -0
  142. package/src/lib/components/storefront-payment-element-fallback.d.ts +18 -0
  143. package/src/lib/components/storefront-payment-element-fallback.js +51 -0
  144. package/src/lib/components/storefront-payment-element-fallback.js.map +1 -0
  145. package/src/lib/components/storefront-payment-element.d.ts +78 -0
  146. package/src/lib/components/storefront-payment-element.js +156 -0
  147. package/src/lib/components/storefront-payment-element.js.map +1 -0
  148. package/src/lib/components/wishlist.d.ts +46 -0
  149. package/src/lib/components/wishlist.js +304 -0
  150. package/src/lib/components/wishlist.js.map +1 -0
  151. package/src/lib/constants/bundle-common.d.ts +18 -0
  152. package/src/lib/constants/bundle-common.js +18 -0
  153. package/src/lib/constants/bundle-common.js.map +1 -0
  154. package/src/lib/constants/product-filters.d.ts +35 -0
  155. package/src/lib/constants/product-filters.js +109 -0
  156. package/src/lib/constants/product-filters.js.map +1 -0
  157. package/src/lib/model/commerce-cart.d.ts +52 -0
  158. package/src/lib/model/commerce-cart.js +102 -0
  159. package/src/lib/model/commerce-cart.js.map +1 -0
  160. package/src/lib/model/commerce-connect-transfer.d.ts +243 -0
  161. package/src/lib/model/commerce-connect-transfer.js +231 -0
  162. package/src/lib/model/commerce-connect-transfer.js.map +1 -0
  163. package/src/lib/model/commerce-discounts.d.ts +164 -0
  164. package/src/lib/model/commerce-discounts.js +231 -0
  165. package/src/lib/model/commerce-discounts.js.map +1 -0
  166. package/src/lib/model/commerce-dispute.d.ts +124 -0
  167. package/src/lib/model/commerce-dispute.js +225 -0
  168. package/src/lib/model/commerce-dispute.js.map +1 -0
  169. package/src/lib/model/commerce-gift-cards.d.ts +107 -0
  170. package/src/lib/model/commerce-gift-cards.js +121 -0
  171. package/src/lib/model/commerce-gift-cards.js.map +1 -0
  172. package/src/lib/model/commerce-io.d.ts +36 -0
  173. package/src/lib/model/commerce-io.js +245 -0
  174. package/src/lib/model/commerce-io.js.map +1 -0
  175. package/src/lib/model/commerce-orders.d.ts +1040 -0
  176. package/src/lib/model/commerce-orders.js +1022 -0
  177. package/src/lib/model/commerce-orders.js.map +1 -0
  178. package/src/lib/model/commerce-promotions.d.ts +188 -0
  179. package/src/lib/model/commerce-promotions.js +199 -0
  180. package/src/lib/model/commerce-promotions.js.map +1 -0
  181. package/src/lib/model/commerce-reservations.d.ts +175 -0
  182. package/src/lib/model/commerce-reservations.js +157 -0
  183. package/src/lib/model/commerce-reservations.js.map +1 -0
  184. package/src/lib/model/commerce-shipping.d.ts +316 -0
  185. package/src/lib/model/commerce-shipping.js +484 -0
  186. package/src/lib/model/commerce-shipping.js.map +1 -0
  187. package/src/lib/model/commerce-stock-holds.d.ts +199 -0
  188. package/src/lib/model/commerce-stock-holds.js +190 -0
  189. package/src/lib/model/commerce-stock-holds.js.map +1 -0
  190. package/src/lib/model/commerce-subscription-status.d.ts +67 -0
  191. package/src/lib/model/commerce-subscription-status.js +76 -0
  192. package/src/lib/model/commerce-subscription-status.js.map +1 -0
  193. package/src/lib/model/commerce-tax-decision.d.ts +222 -0
  194. package/src/lib/model/commerce-tax-decision.js +165 -0
  195. package/src/lib/model/commerce-tax-decision.js.map +1 -0
  196. package/src/lib/model/commerce-tax.d.ts +176 -0
  197. package/src/lib/model/commerce-tax.js +92 -0
  198. package/src/lib/model/commerce-tax.js.map +1 -0
  199. package/src/lib/model/commerce.d.ts +551 -0
  200. package/src/lib/model/commerce.js +739 -0
  201. package/src/lib/model/commerce.js.map +1 -0
  202. package/src/lib/model/index.d.ts +40 -0
  203. package/src/lib/model/index.js +40 -0
  204. package/src/lib/model/index.js.map +1 -0
  205. package/src/lib/model/order-figures.d.ts +48 -0
  206. package/src/lib/model/order-figures.js +74 -0
  207. package/src/lib/model/order-figures.js.map +1 -0
  208. package/src/lib/model/plugin-permissions.d.ts +23 -0
  209. package/src/lib/model/plugin-permissions.js +34 -0
  210. package/src/lib/model/plugin-permissions.js.map +1 -0
  211. package/src/lib/model/purchase-analytics.d.ts +186 -0
  212. package/src/lib/model/purchase-analytics.js +216 -0
  213. package/src/lib/model/purchase-analytics.js.map +1 -0
  214. package/src/lib/plugin-config.d.ts +62 -0
  215. package/src/lib/plugin-config.js +78 -0
  216. package/src/lib/plugin-config.js.map +1 -0
  217. package/src/lib/plugin.d.ts +28 -0
  218. package/src/lib/plugin.js +147 -0
  219. package/src/lib/plugin.js.map +1 -0
  220. package/src/lib/server/billing-webhook.d.ts +26 -0
  221. package/src/lib/server/billing-webhook.js +4011 -0
  222. package/src/lib/server/billing-webhook.js.map +1 -0
  223. package/src/lib/server/cancel-order.d.ts +87 -0
  224. package/src/lib/server/cancel-order.js +368 -0
  225. package/src/lib/server/cancel-order.js.map +1 -0
  226. package/src/lib/server/cart-checkout.d.ts +25 -0
  227. package/src/lib/server/cart-checkout.js +976 -0
  228. package/src/lib/server/cart-checkout.js.map +1 -0
  229. package/src/lib/server/cart-cookie.d.ts +32 -0
  230. package/src/lib/server/cart-cookie.js +77 -0
  231. package/src/lib/server/cart-cookie.js.map +1 -0
  232. package/src/lib/server/cart.d.ts +34 -0
  233. package/src/lib/server/cart.js +181 -0
  234. package/src/lib/server/cart.js.map +1 -0
  235. package/src/lib/server/catalog.d.ts +127 -0
  236. package/src/lib/server/catalog.js +277 -0
  237. package/src/lib/server/catalog.js.map +1 -0
  238. package/src/lib/server/checkout.d.ts +36 -0
  239. package/src/lib/server/checkout.js +915 -0
  240. package/src/lib/server/checkout.js.map +1 -0
  241. package/src/lib/server/connect.d.ts +26 -0
  242. package/src/lib/server/connect.js +238 -0
  243. package/src/lib/server/connect.js.map +1 -0
  244. package/src/lib/server/contact-refund.d.ts +117 -0
  245. package/src/lib/server/contact-refund.js +196 -0
  246. package/src/lib/server/contact-refund.js.map +1 -0
  247. package/src/lib/server/download.d.ts +39 -0
  248. package/src/lib/server/download.js +206 -0
  249. package/src/lib/server/download.js.map +1 -0
  250. package/src/lib/server/draft-order.d.ts +26 -0
  251. package/src/lib/server/draft-order.js +706 -0
  252. package/src/lib/server/draft-order.js.map +1 -0
  253. package/src/lib/server/feed.d.ts +24 -0
  254. package/src/lib/server/feed.js +59 -0
  255. package/src/lib/server/feed.js.map +1 -0
  256. package/src/lib/server/fulfill-order.d.ts +97 -0
  257. package/src/lib/server/fulfill-order.js +235 -0
  258. package/src/lib/server/fulfill-order.js.map +1 -0
  259. package/src/lib/server/gate.d.ts +36 -0
  260. package/src/lib/server/gate.js +112 -0
  261. package/src/lib/server/gate.js.map +1 -0
  262. package/src/lib/server/gift-cards.d.ts +42 -0
  263. package/src/lib/server/gift-cards.js +219 -0
  264. package/src/lib/server/gift-cards.js.map +1 -0
  265. package/src/lib/server/low-stock.d.ts +49 -0
  266. package/src/lib/server/low-stock.js +60 -0
  267. package/src/lib/server/low-stock.js.map +1 -0
  268. package/src/lib/server/manual-tax-rate.d.ts +82 -0
  269. package/src/lib/server/manual-tax-rate.js +113 -0
  270. package/src/lib/server/manual-tax-rate.js.map +1 -0
  271. package/src/lib/server/member-feed.d.ts +24 -0
  272. package/src/lib/server/member-feed.js +72 -0
  273. package/src/lib/server/member-feed.js.map +1 -0
  274. package/src/lib/server/member-name-search.d.ts +37 -0
  275. package/src/lib/server/member-name-search.js +42 -0
  276. package/src/lib/server/member-name-search.js.map +1 -0
  277. package/src/lib/server/member-post.d.ts +23 -0
  278. package/src/lib/server/member-post.js +166 -0
  279. package/src/lib/server/member-post.js.map +1 -0
  280. package/src/lib/server/membership-account.d.ts +37 -0
  281. package/src/lib/server/membership-account.js +148 -0
  282. package/src/lib/server/membership-account.js.map +1 -0
  283. package/src/lib/server/membership-admin-password.d.ts +33 -0
  284. package/src/lib/server/membership-admin-password.js +202 -0
  285. package/src/lib/server/membership-admin-password.js.map +1 -0
  286. package/src/lib/server/membership-content.d.ts +23 -0
  287. package/src/lib/server/membership-content.js +105 -0
  288. package/src/lib/server/membership-content.js.map +1 -0
  289. package/src/lib/server/membership-login.d.ts +19 -0
  290. package/src/lib/server/membership-login.js +137 -0
  291. package/src/lib/server/membership-login.js.map +1 -0
  292. package/src/lib/server/membership-logout.d.ts +19 -0
  293. package/src/lib/server/membership-logout.js +35 -0
  294. package/src/lib/server/membership-logout.js.map +1 -0
  295. package/src/lib/server/membership-recover.d.ts +60 -0
  296. package/src/lib/server/membership-recover.js +244 -0
  297. package/src/lib/server/membership-recover.js.map +1 -0
  298. package/src/lib/server/membership-register.d.ts +22 -0
  299. package/src/lib/server/membership-register.js +241 -0
  300. package/src/lib/server/membership-register.js.map +1 -0
  301. package/src/lib/server/membership-reset.d.ts +26 -0
  302. package/src/lib/server/membership-reset.js +88 -0
  303. package/src/lib/server/membership-reset.js.map +1 -0
  304. package/src/lib/server/membership-wishlist.d.ts +23 -0
  305. package/src/lib/server/membership-wishlist.js +89 -0
  306. package/src/lib/server/membership-wishlist.js.map +1 -0
  307. package/src/lib/server/membership.d.ts +96 -0
  308. package/src/lib/server/membership.js +222 -0
  309. package/src/lib/server/membership.js.map +1 -0
  310. package/src/lib/server/native-checkout.d.ts +170 -0
  311. package/src/lib/server/native-checkout.js +193 -0
  312. package/src/lib/server/native-checkout.js.map +1 -0
  313. package/src/lib/server/newsletter.d.ts +23 -0
  314. package/src/lib/server/newsletter.js +323 -0
  315. package/src/lib/server/newsletter.js.map +1 -0
  316. package/src/lib/server/notify-restock.d.ts +22 -0
  317. package/src/lib/server/notify-restock.js +84 -0
  318. package/src/lib/server/notify-restock.js.map +1 -0
  319. package/src/lib/server/order-analytics.d.ts +134 -0
  320. package/src/lib/server/order-analytics.js +263 -0
  321. package/src/lib/server/order-analytics.js.map +1 -0
  322. package/src/lib/server/order-figures.d.ts +33 -0
  323. package/src/lib/server/order-figures.js +220 -0
  324. package/src/lib/server/order-figures.js.map +1 -0
  325. package/src/lib/server/paid-media-delivery.d.ts +173 -0
  326. package/src/lib/server/paid-media-delivery.js +212 -0
  327. package/src/lib/server/paid-media-delivery.js.map +1 -0
  328. package/src/lib/server/pos-order.d.ts +54 -0
  329. package/src/lib/server/pos-order.js +1137 -0
  330. package/src/lib/server/pos-order.js.map +1 -0
  331. package/src/lib/server/process-abandoned.d.ts +54 -0
  332. package/src/lib/server/process-abandoned.js +222 -0
  333. package/src/lib/server/process-abandoned.js.map +1 -0
  334. package/src/lib/server/process-restock.d.ts +42 -0
  335. package/src/lib/server/process-restock.js +219 -0
  336. package/src/lib/server/process-restock.js.map +1 -0
  337. package/src/lib/server/product-card.d.ts +29 -0
  338. package/src/lib/server/product-card.js +54 -0
  339. package/src/lib/server/product-card.js.map +1 -0
  340. package/src/lib/server/product.d.ts +70 -0
  341. package/src/lib/server/product.js +105 -0
  342. package/src/lib/server/product.js.map +1 -0
  343. package/src/lib/server/promotion-hold.d.ts +144 -0
  344. package/src/lib/server/promotion-hold.js +174 -0
  345. package/src/lib/server/promotion-hold.js.map +1 -0
  346. package/src/lib/server/reconcile-stock.d.ts +106 -0
  347. package/src/lib/server/reconcile-stock.js +349 -0
  348. package/src/lib/server/reconcile-stock.js.map +1 -0
  349. package/src/lib/server/refund.d.ts +45 -0
  350. package/src/lib/server/refund.js +607 -0
  351. package/src/lib/server/refund.js.map +1 -0
  352. package/src/lib/server/related.d.ts +24 -0
  353. package/src/lib/server/related.js +104 -0
  354. package/src/lib/server/related.js.map +1 -0
  355. package/src/lib/server/reservation-availability.d.ts +23 -0
  356. package/src/lib/server/reservation-availability.js +111 -0
  357. package/src/lib/server/reservation-availability.js.map +1 -0
  358. package/src/lib/server/reserve-stock.d.ts +156 -0
  359. package/src/lib/server/reserve-stock.js +146 -0
  360. package/src/lib/server/reserve-stock.js.map +1 -0
  361. package/src/lib/server/reserve.d.ts +25 -0
  362. package/src/lib/server/reserve.js +426 -0
  363. package/src/lib/server/reserve.js.map +1 -0
  364. package/src/lib/server/restock-flag.d.ts +202 -0
  365. package/src/lib/server/restock-flag.js +449 -0
  366. package/src/lib/server/restock-flag.js.map +1 -0
  367. package/src/lib/server/reviews.d.ts +51 -0
  368. package/src/lib/server/reviews.js +132 -0
  369. package/src/lib/server/reviews.js.map +1 -0
  370. package/src/lib/server/site-page-enricher.d.ts +42 -0
  371. package/src/lib/server/site-page-enricher.js +149 -0
  372. package/src/lib/server/site-page-enricher.js.map +1 -0
  373. package/src/lib/server/site-page-resolver.d.ts +26 -0
  374. package/src/lib/server/site-page-resolver.js +223 -0
  375. package/src/lib/server/site-page-resolver.js.map +1 -0
  376. package/src/lib/server/stock-hold.d.ts +127 -0
  377. package/src/lib/server/stock-hold.js +258 -0
  378. package/src/lib/server/stock-hold.js.map +1 -0
  379. package/src/lib/server/storefront-tax-record.d.ts +22 -0
  380. package/src/lib/server/storefront-tax-record.js +218 -0
  381. package/src/lib/server/storefront-tax-record.js.map +1 -0
  382. package/src/lib/server/storefront-tax.d.ts +197 -0
  383. package/src/lib/server/storefront-tax.js +245 -0
  384. package/src/lib/server/storefront-tax.js.map +1 -0
  385. package/src/lib/server/stream.d.ts +25 -0
  386. package/src/lib/server/stream.js +191 -0
  387. package/src/lib/server/stream.js.map +1 -0
  388. package/src/lib/server/subscription-portal.d.ts +24 -0
  389. package/src/lib/server/subscription-portal.js +91 -0
  390. package/src/lib/server/subscription-portal.js.map +1 -0
  391. package/src/lib/server/supplier-outbox.d.ts +190 -0
  392. package/src/lib/server/supplier-outbox.js +342 -0
  393. package/src/lib/server/supplier-outbox.js.map +1 -0
  394. package/src/lib/server/supplier-update.d.ts +23 -0
  395. package/src/lib/server/supplier-update.js +289 -0
  396. package/src/lib/server/supplier-update.js.map +1 -0
  397. package/src/lib/server/tax-profile.d.ts +29 -0
  398. package/src/lib/server/tax-profile.js +40 -0
  399. package/src/lib/server/tax-profile.js.map +1 -0
  400. package/src/lib/server.d.ts +32 -0
  401. package/src/lib/server.js +311 -0
  402. package/src/lib/server.js.map +1 -0
  403. package/src/lib/site.d.ts +27 -0
  404. package/src/lib/site.js +134 -0
  405. package/src/lib/site.js.map +1 -0
  406. package/src/lib/utils/escape-html.d.ts +41 -0
  407. package/src/lib/utils/escape-html.js +41 -0
  408. package/src/lib/utils/escape-html.js.map +1 -0
  409. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  410. package/src/lib/utils/generate-preset-id.js +25 -0
  411. package/src/lib/utils/generate-preset-id.js.map +1 -0
  412. package/src/lib/utils/member-continue.d.ts +45 -0
  413. package/src/lib/utils/member-continue.js +49 -0
  414. package/src/lib/utils/member-continue.js.map +1 -0
  415. package/src/lib/utils/use-storefront-purchase-event.d.ts +60 -0
  416. package/src/lib/utils/use-storefront-purchase-event.js +142 -0
  417. package/src/lib/utils/use-storefront-purchase-event.js.map +1 -0
@@ -0,0 +1,1280 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
19
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
20
+ import * as Aglyn from "@aglyn/aglyn";
21
+ import * as CommerceModel from "../../model/index.js";
22
+ import { PRODUCT_LIST_FILTER_FIELDS } from "../../constants/product-filters.js";
23
+ import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
24
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
25
+ import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
26
+ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
27
+ import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
28
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
29
+ import { Timestamp } from "@aglyn/shared-util-timestamp";
30
+ import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@mui/material";
31
+ import { addDoc, collection, doc, documentId, getCountFromServer, limit, orderBy, query, updateDoc, where } from "firebase/firestore";
32
+ import { useCallback, useEffect, useMemo, useState } from "react";
33
+ import { useFirestore } from "@aglyn/tenant-feature-instance";
34
+ import { listFilterConstraints, useFirestoreCollection, writeGuardedBySeed } from "@aglyn/tenant-feature-instance";
35
+ import { useHostResourceApi } from "@aglyn/tenant-feature-instance";
36
+ import { useOrgPlan } from "@aglyn/tenant-feature-instance";
37
+ import ProductEditorDialog from "./product-editor-dialog.component.js";
38
+ import ProductsHubZone from "./products-hub-zone.component.js";
39
+ import { pluginDocsHelp } from "@aglyn/aglyn";
40
+ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
41
+ /**
42
+ * How many catalog documents the table's listener holds.
43
+ *
44
+ * A CEILING, not a page size: a 25,000-product catalog does not belong in a
45
+ * table, and three other readers on this card — the CSV export, the importer's
46
+ * duplicate-slug check and the reserved-stock clock — need the window whole.
47
+ * The footer pages what this holds; the query is what bounds it.
48
+ */ const CATALOG_CEILING = 500;
49
+ /**
50
+ * How many of ONE product's license keys the dialog reads.
51
+ *
52
+ * Per product, not per store: the query carries the `productId` equality, so
53
+ * this bounds a single pool rather than the site's whole key collection.
54
+ */ const KEY_POOL_CEILING = 500;
55
+ const STATUS_COLOR = {
56
+ active: 'success',
57
+ draft: 'warning',
58
+ archived: 'default'
59
+ };
60
+ /**
61
+ * Products hub v1 (AGL-279): the catalog manager replacing the Commerce
62
+ * Starter card — search + status filter over `hosts/{hostId}/products`,
63
+ * full editor dialog, duplicate, archive/activate, soft delete (past
64
+ * order rows keep resolving). Product cap (`productsPerHost`, AGL-278)
65
+ * gated here on create/duplicate/import (AGL-471); server-side
66
+ * enforcement of the client-write path rides AGL-473.
67
+ */ export function ProductsHubCard(props) {
68
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12;
69
+ var _importing_parsed, _importing_parsed1, _importing_parsed2;
70
+ const { hostId } = props;
71
+ const firestore = useFirestore();
72
+ const { enqueueSnackbar } = useSnackbar();
73
+ // Product cap (AGL-471): per-plan `productsPerHost`, same pattern as
74
+ // locations. Console-side gate; server enforcement rides AGL-473.
75
+ const createHostResource = useHostResourceApi();
76
+ const { org, ready: planReady } = useOrgPlan(hostId);
77
+ const { confirm } = useConfirmationContext();
78
+ const [search, setSearch] = useState('');
79
+ /*
80
+ * WHICH field the search box searches (AGL-2501).
81
+ *
82
+ * The box used to compare four fields at once — name, slug, tag, SKU — over
83
+ * the rows the listener had already fetched. Server-side that is not one
84
+ * query: Firestore allows a single `array-contains` per query and cannot OR
85
+ * across fields, so four fields at once means four queries and a merge, and
86
+ * a merged result cannot be paged or capped coherently.
87
+ *
88
+ * Naming the field is the honest trade. It buys a search that reaches the
89
+ * WHOLE catalog instead of an arbitrary five hundred rows of it, which is
90
+ * the case the old box got wrong — and a merchant looking for a SKU knows
91
+ * they are looking for a SKU.
92
+ */ const [searchField, setSearchField] = useState('name');
93
+ const [statusFilter, setStatusFilter] = useState('all');
94
+ const [editing, setEditing] = useState(null);
95
+ const [creating, setCreating] = useState(false);
96
+ const [adjusting, setAdjusting] = useState(null);
97
+ const [importing, setImporting] = useState(null);
98
+ const [keysFor, setKeysFor] = useState(null);
99
+ const [keysText, setKeysText] = useState('');
100
+ /**
101
+ * What the import zone set for the import in the dialog, and what the last
102
+ * import created with those options (AGL-2916), which the hub's zone hands
103
+ * to its widgets.
104
+ */ const [importOptions, setImportOptions] = useState({});
105
+ const [lastImport, setLastImport] = useState(null);
106
+ const WidgetSlot = useConsoleWidgetSlot();
107
+ const { data: productDocs, status: productsStatus, /**
108
+ * The product rows the stock dialog is seeded from are unconfirmed by
109
+ * the server (AGL-1358). A stock adjustment does not write the delta —
110
+ * it recomputes the WHOLE `variants` array from the seeded product and
111
+ * replaces it, so a cached seed silently reverts every sale, return and
112
+ * adjustment the server has recorded since that snapshot, on every
113
+ * variant and every location.
114
+ */ fromCache: productsFromCache } = useFirestoreCollection(()=>{
115
+ /*
116
+ * FILTERED BY THE QUERY, and ordered rather than merely capped
117
+ * (AGL-2501, AGL-2292).
118
+ *
119
+ * `limit(500)` with no `orderBy` returns documents in ID order, and
120
+ * products are created at `createResourceUid()` — so that was a
121
+ * pseudo-random SAMPLE of five hundred, which the client `.sort()` by
122
+ * name below dressed up as a reliable alphabetical page. The search then
123
+ * ran over that sample, so a product on the wrong side of the cap
124
+ * answered "no products match": the one answer a search must never get
125
+ * wrong, on the list a merchant uses to find one item in their catalog.
126
+ *
127
+ * The cap STAYS — a 25,000-product catalog does not belong in a table,
128
+ * and the head-count has been a server aggregate since AGL-1716. What
129
+ * changes is that a filter now reaches the whole collection BEFORE the
130
+ * cap applies.
131
+ *
132
+ * ONE request, because Firestore allows one `array-contains` and the
133
+ * shared translator builds one predicate. Whichever control is set alone
134
+ * is answered entirely by the server; with BOTH set the name reaches the
135
+ * whole catalog and the status narrows those matches client-side below,
136
+ * which is complete unless a single name matches more than five hundred
137
+ * products.
138
+ *
139
+ * ⚠️ The default ordering is by DOCUMENT ID, not by `nameLower`.
140
+ * Ordering by a denormalized key drops every document that lacks it, and
141
+ * an unfiltered list must not be able to hide a product — a catalog
142
+ * imported before the search keys existed, or written by a path that
143
+ * forgot them, would simply stop appearing. Under a name filter the
144
+ * ordering does move to `nameLower`, and there it is safe: a document
145
+ * without the key has no `nameTokens` either, so the `array-contains`
146
+ * has already excluded it.
147
+ */ const constraints = listFilterConstraints(PRODUCT_LIST_FILTER_FIELDS, search.trim() ? {
148
+ field: searchField,
149
+ op: 'contains',
150
+ value: search.trim()
151
+ } : statusFilter !== 'all' ? {
152
+ field: 'status',
153
+ op: 'equals',
154
+ value: statusFilter
155
+ } : null);
156
+ return query(collection(firestore, 'hosts', hostId, 'products'), ...constraints != null ? constraints : [
157
+ orderBy(documentId())
158
+ ], /*
159
+ * `CATALOG_CEILING + 1` is a PROBE (AGL-2501). One document past the
160
+ * ceiling turns "this catalog is larger than the table" into a fact.
161
+ * The probe row is dropped below and never rendered, exported or
162
+ * counted.
163
+ */ limit(CATALOG_CEILING + 1));
164
+ }, [
165
+ firestore,
166
+ hostId,
167
+ search,
168
+ searchField,
169
+ statusFilter
170
+ ], {
171
+ idField: '$id'
172
+ });
173
+ /*
174
+ * License key pool (AGL-308) for the open dialog's product — asked FOR that
175
+ * product, and ordered (AGL-2501).
176
+ *
177
+ * This read `limit(500)` over the site's whole `licenseKeys` collection with
178
+ * no `orderBy`, then filtered by `productId` in the browser. Firestore
179
+ * answers an unordered limit in DOCUMENT-ID order, so on a store past five
180
+ * hundred keys the window was an arbitrary five hundred taken across every
181
+ * product — and the "N available" line below counted what happened to be in
182
+ * it. A product whose keys all hashed high showed a pool of zero while the
183
+ * storefront went on delivering them.
184
+ *
185
+ * The equality moves into the query, so the window is this product's keys
186
+ * rather than the store's. `orderBy(documentId())` orders on the document
187
+ * NAME, which cannot be absent — every candidate FIELD here (`createdAtMs`,
188
+ * `assignedAtMs`, `revokedAtMs`) is either optional by design or absent on
189
+ * exactly the keys the counts are about. An equality plus an ordering on
190
+ * `__name__` is served by the automatic single-field index, so this adds no
191
+ * composite for anyone to deploy.
192
+ */ const { data: keyDocs } = useFirestoreCollection(()=>keysFor ? query(collection(firestore, 'hosts', hostId, 'licenseKeys'), where('productId', '==', keysFor.$id), orderBy(documentId()), // One past the ceiling, so a pool larger than the window is a
193
+ // fact rather than a guess from `length === 500`.
194
+ limit(KEY_POOL_CEILING + 1)) : null, [
195
+ firestore,
196
+ hostId,
197
+ keysFor == null ? void 0 : keysFor.$id
198
+ ], {
199
+ idField: '$id'
200
+ });
201
+ /** The key pool is larger than the window, so the counts below are partial. */ const keyPoolTruncated = ((_ref = keyDocs == null ? void 0 : keyDocs.length) != null ? _ref : 0) > KEY_POOL_CEILING;
202
+ // Locations (AGL-286): the stock dialog buckets deltas when they exist.
203
+ const { data: locationDocs } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)), [
204
+ firestore,
205
+ hostId
206
+ ], {
207
+ idField: '$id'
208
+ });
209
+ /** The read went past the ceiling, so the catalog is larger than the window. */ const catalogTruncated = ((_ref1 = productDocs == null ? void 0 : productDocs.length) != null ? _ref1 : 0) > CATALOG_CEILING;
210
+ const products = useMemo(()=>{
211
+ /*
212
+ * The SEARCH is gone from here — it is the query's job now, and running it
213
+ * twice would be worse than redundant. A server `contains` matches a word
214
+ * PREFIX ("cof" finds "Coffee"), while the old `includes` matched a raw
215
+ * substring, so a two-word search would have been sent to the server as
216
+ * its first word and then dropped again here by a whole-string compare
217
+ * that the matching row never satisfies: rows found, then hidden.
218
+ *
219
+ * The status narrowing stays, and only earns its keep when a name search
220
+ * is also active — that is the one combination the single predicate above
221
+ * cannot express, and here it runs over rows the server already matched by
222
+ * name across the whole catalog rather than over an arbitrary window.
223
+ *
224
+ * Soft-deleted products are still dropped here rather than in the query:
225
+ * `deletedAt` is absent on a live product, and Firestore cannot ask for
226
+ * documents that LACK a field.
227
+ */ return (productDocs != null ? productDocs : []).slice(0, CATALOG_CEILING).filter((product)=>!product.deletedAt).map((product)=>_extends({}, CommerceModel.liftLegacyProduct(product), {
228
+ $id: product.$id
229
+ })).filter((product)=>statusFilter === 'all' || product.status === statusFilter).sort((a, b)=>a.name.localeCompare(b.name));
230
+ }, [
231
+ productDocs,
232
+ statusFilter
233
+ ]);
234
+ /*==========================================
235
+ * THE TABLE PAGES, and the READ deliberately does not (AGL-2501).
236
+ *
237
+ * The card rendered every row of a five-hundred-document window in one wall
238
+ * with no footer under it — the shape this sweep is about. What it must not
239
+ * become is a server-paged query, because three other things in this file
240
+ * read the same window and every one of them needs it whole:
241
+ *
242
+ * * `handleExport` writes the CSV from these rows. A ten-row page would
243
+ * silently export ten products under a filename that claims the catalog.
244
+ * * the CSV importer builds `existingSlugs` from them to refuse a
245
+ * duplicate slug. Narrowed to a page, it would stop seeing the clash and
246
+ * create the duplicate it exists to prevent.
247
+ * * the reserved-stock clock arms off them.
248
+ *
249
+ * The window is also already correct: `orderBy(documentId())` unfiltered,
250
+ * `nameLower` under a name filter, and the filters themselves reach the
251
+ * whole collection through the query. So the fix here is the CONTROL, not
252
+ * the read — and the head-count beside it has been a server aggregate since
253
+ * AGL-1716.
254
+ *=========================================*/ const [page, setPage] = useState(0);
255
+ const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
256
+ // A filter narrows the list under the reader's feet, and page four of the
257
+ // unfiltered catalog is not a position in the filtered one.
258
+ useEffect(()=>setPage(0), [
259
+ search,
260
+ searchField,
261
+ statusFilter
262
+ ]);
263
+ const visibleProducts = useMemo(()=>products.slice(page * pageSize, page * pageSize + pageSize), [
264
+ products,
265
+ page,
266
+ pageSize
267
+ ]);
268
+ /**
269
+ * A CLOCK, because a hold lapses without anybody writing anything
270
+ * (AGL-2356).
271
+ *
272
+ * The Firestore listener re-renders on document changes, and a reservation
273
+ * expiring is not one — `expiresAtMs` simply passes. Without a tick the
274
+ * "reserved" caption below would keep naming a hold that lapsed twenty
275
+ * minutes ago, which is worse than not showing it at all: the merchant would
276
+ * be told stock is spoken for while the storefront happily sells it.
277
+ *
278
+ * Only runs while something is actually held, so an ordinary catalog costs
279
+ * no timer. A NEW hold arrives as a document change, which re-renders, which
280
+ * re-arms this.
281
+ */ const [nowMs, setNowMs] = useState(()=>Date.now());
282
+ const anyHeld = useMemo(()=>products.some((product)=>CommerceModel.heldProductUnits(product, nowMs) > 0), [
283
+ products,
284
+ nowMs
285
+ ]);
286
+ useEffect(()=>{
287
+ if (!anyHeld) return undefined;
288
+ const timer = setInterval(()=>setNowMs(Date.now()), 60000);
289
+ return ()=>clearInterval(timer);
290
+ }, [
291
+ anyHeld
292
+ ]);
293
+ /**
294
+ * The catalog HEAD-COUNT is a server aggregate, not the length of the
295
+ * capped listener (AGL-1716, the AGL-1706 shape).
296
+ *
297
+ * The listener is `limit(500)` — correctly; a 25,000-product catalog does
298
+ * not belong in a table. What it must not do is answer "how many products
299
+ * does this site have", and it did: the length saturated at 500 and was
300
+ * handed to `checkQuota(org, 'productsPerHost', …)` and to the batch check
301
+ * the CSV importer runs. The bands are 2,500 / 10,000 / 25,000 above the
302
+ * window, so on Pro and up the check compared 500 against thousands and
303
+ * could never refuse — the card offered headroom and `api/hosts/resources`
304
+ * then refused the create, which is the AGL-1716 shape exactly.
305
+ *
306
+ * The aggregate is deliberately UNFILTERED, which also closes a second,
307
+ * quieter disagreement: `api/hosts/resources` enforces this quota with a
308
+ * plain `collection('products').count()`, and `softDeletes` there governs
309
+ * only the flat per-host cap on webhooks — so the server has always
310
+ * counted soft-deleted products toward `productsPerHost` while this card
311
+ * excluded them. The card now asks the enforcing route's question, in the
312
+ * enforcing route's terms.
313
+ *
314
+ * THE LIST KEEPS ITS CAP, and the filtered `products` view above still
315
+ * drives the table, the export and the slug set. A one-shot goes stale
316
+ * where a listener refreshed for free, so the count is re-read after any
317
+ * mutation that moves it.
318
+ *
319
+ * No counting RULE moves: `checkQuota` is untouched and `report-usage`
320
+ * meters contacts, storage and API requests — never the catalog.
321
+ */ const [productCountEpoch, setProductCountEpoch] = useState(0);
322
+ const [serverProductCount, setServerProductCount] = useState(null);
323
+ useEffect(()=>{
324
+ let active = true;
325
+ void getCountFromServer(collection(firestore, 'hosts', hostId, 'products')).then((snapshot)=>{
326
+ if (active) setServerProductCount(snapshot.data().count);
327
+ }).catch(()=>{
328
+ // Falls back to the live-row count below — a LOWER bound, and this
329
+ // card's prior behaviour. Deliberately not 0: `checkQuota` answers
330
+ // from whatever it is handed, and 0 used is a confident wrong
331
+ // number in the flattering direction.
332
+ });
333
+ return ()=>{
334
+ active = false;
335
+ };
336
+ }, [
337
+ firestore,
338
+ hostId,
339
+ productCountEpoch
340
+ ]);
341
+ // Cap against ALL live products, not the filtered view (AGL-471). This is
342
+ // the fallback now: pending or denied, it can only UNDERSTATE, never
343
+ // overstate, so nothing it gates fires on a count larger than the truth.
344
+ const loadedProductCount = useMemo(()=>(productDocs != null ? productDocs : []).filter((product)=>!product.deletedAt).length, [
345
+ productDocs
346
+ ]);
347
+ const productCount = serverProductCount != null ? serverProductCount : loadedProductCount;
348
+ // Gate only once the org doc has loaded: an unresolved org reads as the
349
+ // free tier's 0-product cap, which swallowed every Add/Duplicate click.
350
+ // The resources API (AGL-473) stays the authoritative cap on create.
351
+ //
352
+ // `planReady` rather than `org` truthiness (AGL-1064): a host with no
353
+ // owning org never produces one, and waiting on the value alone would
354
+ // hold this open forever. The old fallback allowed UNLIMITED during the
355
+ // window — safe only because the API re-checks; the controls now disable
356
+ // instead, which does not lean on that backstop.
357
+ // NULL means "not known yet", which a boolean cannot say — the old
358
+ // fallback object claimed UNLIMITED and every consumer believed it.
359
+ const productQuota = useMemo(()=>planReady ? Aglyn.checkQuota(org, 'productsPerHost', productCount) : null, [
360
+ org,
361
+ planReady,
362
+ productCount
363
+ ]);
364
+ const handleDuplicate = useCallback((product)=>async ()=>{
365
+ // Plan unknown — the control is disabled; this guards the race.
366
+ if (!productQuota) return;
367
+ if (!productQuota.allowed) {
368
+ return void enqueueSnackbar(`Your plan includes ${productQuota.limit} products — upgrade for more`, {
369
+ variant: 'info',
370
+ persist: false
371
+ });
372
+ }
373
+ const { $id: _sourceId } = product, copy = _object_without_properties_loose(product, [
374
+ "$id"
375
+ ]);
376
+ try {
377
+ // Duplicate is a create — rides the quota-enforcing API (AGL-473).
378
+ await createHostResource({
379
+ hostId,
380
+ resource: 'product',
381
+ data: _extends({}, copy, {
382
+ name: `${product.name} (copy)`,
383
+ slug: CommerceModel.commerceSlug(`${product.slug}-copy`),
384
+ status: 'draft',
385
+ createdAtMs: Date.now(),
386
+ updatedAtMs: Date.now()
387
+ })
388
+ });
389
+ // A create moves the count and the aggregate is a one-shot — the
390
+ // listener refreshes the ROWS for free, the count has to be asked
391
+ // again or the cap drifts stale for the rest of the session.
392
+ setProductCountEpoch((epoch)=>epoch + 1);
393
+ enqueueSnackbar('Product duplicated as draft', {
394
+ variant: 'success',
395
+ persist: false
396
+ });
397
+ } catch (error) {
398
+ var _ref;
399
+ enqueueSnackbar((_ref = error == null ? void 0 : error.message) != null ? _ref : 'Could not duplicate product', {
400
+ variant: 'warning',
401
+ persist: false
402
+ });
403
+ }
404
+ }, [
405
+ hostId,
406
+ createHostResource,
407
+ enqueueSnackbar,
408
+ productQuota
409
+ ]);
410
+ const handleStatus = useCallback((product, status)=>async ()=>{
411
+ // A product with a variant nobody has priced (AGL-2916) stays off the
412
+ // storefront until the editor has a price for each.
413
+ if (status === 'active' && CommerceModel.productPriceMissing(product)) {
414
+ return void enqueueSnackbar(`Set a price for every variant of ${product.name} before activating it.`, {
415
+ variant: 'info',
416
+ persist: false
417
+ });
418
+ }
419
+ await updateDoc(doc(firestore, 'hosts', hostId, 'products', product.$id), {
420
+ status,
421
+ updatedAtMs: Date.now(),
422
+ updatedAt: Timestamp.now()
423
+ });
424
+ }, [
425
+ firestore,
426
+ hostId,
427
+ enqueueSnackbar
428
+ ]);
429
+ const handleDelete = useCallback((product)=>async ()=>{
430
+ const confirmed = await confirm({
431
+ title: 'Delete this product?',
432
+ description: `"${product.name}" stops being purchasable; blocks referencing ` + 'it show a checkout error until repointed.',
433
+ confirmationText: 'Delete',
434
+ confirmationButtonProps: {
435
+ color: 'error'
436
+ }
437
+ }).then(()=>true).catch(()=>false);
438
+ if (!confirmed) return;
439
+ await updateDoc(doc(firestore, 'hosts', hostId, 'products', product.$id), {
440
+ deletedAt: Timestamp.now()
441
+ });
442
+ enqueueSnackbar('Product deleted', {
443
+ variant: 'success',
444
+ persist: false
445
+ });
446
+ }, [
447
+ confirm,
448
+ firestore,
449
+ hostId,
450
+ enqueueSnackbar
451
+ ]);
452
+ // CSV import/export (AGL-282): Shopify-dialect columns, dry-run first.
453
+ const handleExport = useCallback(()=>{
454
+ const csv = CommerceModel.productsToCsv(products);
455
+ const blob = new Blob([
456
+ csv
457
+ ], {
458
+ type: 'text/csv'
459
+ });
460
+ const anchor = document.createElement('a');
461
+ anchor.href = URL.createObjectURL(blob);
462
+ anchor.download = `products-${hostId}.csv`;
463
+ anchor.click();
464
+ URL.revokeObjectURL(anchor.href);
465
+ }, [
466
+ products,
467
+ hostId
468
+ ]);
469
+ const handleImportApply = useCallback(async ()=>{
470
+ const parsed = importing == null ? void 0 : importing.parsed;
471
+ if (!parsed || parsed.products.length === 0) return;
472
+ // Batch-aware cap (AGL-471): the whole import must fit the plan.
473
+ // Not startable until the plan is known (AGL-1064) — the old `org ?`
474
+ // guard let an import begin against an unknown cap and leaned on the
475
+ // API rejecting it partway through, leaving a half-imported catalog.
476
+ if (!planReady) return;
477
+ const batchQuota = Aglyn.checkQuota(org, 'productsPerHost', productCount + parsed.products.length - 1);
478
+ if (!batchQuota.allowed) {
479
+ return void enqueueSnackbar(`This import needs ${parsed.products.length} product slots — your ` + `plan allows ${batchQuota.limit}. See Billing to upgrade.`, {
480
+ variant: 'info',
481
+ persist: false
482
+ });
483
+ }
484
+ const existingSlugs = new Set(products.map((product)=>product.slug));
485
+ const created = [];
486
+ try {
487
+ // Each create rides the quota-enforcing API (AGL-473); the batch cap
488
+ // above short-circuits before we start, so this loop stays bounded.
489
+ for (const product of parsed.products){
490
+ var _ref, _ref1;
491
+ var _product_variants_, _product_mediaUrls;
492
+ let slug = product.slug;
493
+ while(existingSlugs.has(slug))slug = `${product.slug}-${Date.now() % 1000}`;
494
+ existingSlugs.add(slug);
495
+ const { id } = await createHostResource({
496
+ hostId,
497
+ resource: 'product',
498
+ data: _extends({}, product, CommerceModel.productSearchFields(product), {
499
+ slug,
500
+ priceUsd: (_ref = (_product_variants_ = product.variants[0]) == null ? void 0 : _product_variants_.priceUsd) != null ? _ref : 0,
501
+ inventory: CommerceModel.productInventory(product),
502
+ imageUrl: (_ref1 = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref1 : null,
503
+ createdAtMs: Date.now(),
504
+ updatedAtMs: Date.now()
505
+ })
506
+ });
507
+ created.push(id);
508
+ }
509
+ } catch (error) {
510
+ var _ref2;
511
+ return void enqueueSnackbar((_ref2 = error == null ? void 0 : error.message) != null ? _ref2 : 'Import failed', {
512
+ variant: 'warning',
513
+ persist: false
514
+ });
515
+ }
516
+ setLastImport({
517
+ key: `${Date.now().toString(36)}-${created.length}`,
518
+ productIds: created,
519
+ options: importOptions
520
+ });
521
+ setImportOptions({});
522
+ setImporting(null);
523
+ setProductCountEpoch((epoch)=>epoch + 1);
524
+ enqueueSnackbar(`Imported ${parsed.products.length} products`, {
525
+ variant: 'success',
526
+ persist: false
527
+ });
528
+ }, [
529
+ importing,
530
+ products,
531
+ hostId,
532
+ createHostResource,
533
+ enqueueSnackbar,
534
+ org,
535
+ planReady,
536
+ productCount,
537
+ importOptions
538
+ ]);
539
+ const handleAdjustSave = useCallback(async ()=>{
540
+ if (!adjusting) return;
541
+ const delta = Math.round(Number(adjusting.delta));
542
+ if (!delta) return;
543
+ const variants = CommerceModel.adjustVariantInventory(adjusting.product, adjusting.variantId, delta, adjusting.locationId || undefined);
544
+ /**
545
+ * Refuse the adjustment when the seed is unconfirmed (AGL-1358).
546
+ *
547
+ * `adjustVariantInventory` reads counts off the seeded product and
548
+ * returns a whole new `variants` array, which is then written over the
549
+ * stored one — so this is a full replace of live stock, not a delta, and
550
+ * `merge` could not help. The refusal also has to cover the ADJUSTMENT
551
+ * LOG, which is why the guard wraps both writes: a logged adjustment
552
+ * whose stock write never happened is worse than neither, because the
553
+ * history then disagrees with the count it is supposed to explain.
554
+ */ const verdict = await writeGuardedBySeed({
555
+ subject: 'stock',
556
+ unreadable: productsStatus === 'error',
557
+ fromCache: productsFromCache
558
+ }, async ()=>{
559
+ await updateDoc(doc(firestore, 'hosts', hostId, 'products', adjusting.product.$id), {
560
+ variants,
561
+ inventory: CommerceModel.productInventory({
562
+ variants
563
+ }),
564
+ updatedAtMs: Date.now()
565
+ });
566
+ // Adjustment history (AGL-281): the same log the sale webhook writes.
567
+ await addDoc(collection(firestore, 'hosts', hostId, 'inventoryAdjustments'), _extends({
568
+ productId: adjusting.product.$id,
569
+ variantId: adjusting.variantId,
570
+ delta,
571
+ reason: adjusting.reason
572
+ }, adjusting.locationId ? {
573
+ locationId: adjusting.locationId
574
+ } : {}, {
575
+ atMs: Date.now()
576
+ }));
577
+ });
578
+ // Keep the dialog open with the typed delta rather than failing silently.
579
+ if (!verdict.ok) {
580
+ return void enqueueSnackbar(verdict.message, {
581
+ variant: 'warning',
582
+ persist: false
583
+ });
584
+ }
585
+ setAdjusting(null);
586
+ enqueueSnackbar('Stock adjusted', {
587
+ variant: 'success',
588
+ persist: false
589
+ });
590
+ }, [
591
+ adjusting,
592
+ firestore,
593
+ hostId,
594
+ enqueueSnackbar,
595
+ productsFromCache,
596
+ productsStatus
597
+ ]);
598
+ /** The hub's allowance check for a batch of `count` new products, for its zone. */ const roomFor = useCallback((count)=>planReady ? Aglyn.checkQuota(org, 'productsPerHost', productCount + count - 1) : null, [
599
+ org,
600
+ planReady,
601
+ productCount
602
+ ]);
603
+ const onCatalogCreated = useCallback(()=>setProductCountEpoch((epoch)=>epoch + 1), []);
604
+ const setImportOption = useCallback((key, on)=>setImportOptions((current)=>_extends({}, current, {
605
+ [key]: on
606
+ })), []);
607
+ const formatPrice = (product)=>{
608
+ const [min, max] = CommerceModel.productPriceRange(product);
609
+ return min === max ? `$${min}` : `$${min}–$${max}`;
610
+ };
611
+ const formatStock = (product)=>{
612
+ const total = CommerceModel.productInventory(product);
613
+ if (total == null) return '—';
614
+ return total > 0 ? String(total) : 'Sold out';
615
+ };
616
+ /**
617
+ * RESERVED UNITS, NAMED (AGL-2356).
618
+ *
619
+ * Checkout now takes a hold on the units a live session is about to buy, and
620
+ * that hold deliberately does NOT move `inventory` — the shelf count means
621
+ * units on the shelf, and half the product reads it that way. The
622
+ * consequence a merchant meets is that the storefront can refuse a sale of
623
+ * the third unit while this column says `3`, and without this caption the
624
+ * number is right and the behaviour looks broken.
625
+ *
626
+ * The house style of `stock-movements-card.component.tsx`: a trailing
627
+ * `caption` in `text.secondary`, rendered ONLY when there is something to
628
+ * say. Nothing is held on the overwhelming majority of products, so the
629
+ * column reads exactly as it does today.
630
+ */ const formatHeld = (product)=>{
631
+ const held = CommerceModel.heldProductUnits(product, nowMs);
632
+ return held > 0 ? ` (${held} reserved)` : '';
633
+ };
634
+ return /*#__PURE__*/ _jsxs(CardDisplay, {
635
+ /*
636
+ * The header count is the SITE'S catalog, not the rows in hand.
637
+ *
638
+ * It was `products.length`, which is the filtered, ceilinged, now-paged
639
+ * view — so it read 500 on a 25,000-product catalog and would read 10
640
+ * once the table paged. `productCount` is the same server aggregate the
641
+ * quota gate uses. Under a filter there is no honest number to put here:
642
+ * the aggregate counts the whole catalog and the view counts matches, so
643
+ * neither describes what the reader is looking at, and the footer's own
644
+ * count line answers it instead.
645
+ */ header: search || statusFilter !== 'all' ? 'Products' : `Products${productCount ? ` (${productCount})` : ''}`,
646
+ help: pluginDocsHelp('commerce', {
647
+ anchor: '#products-hub'
648
+ }),
649
+ contentGutterX: true,
650
+ contentGutterY: true,
651
+ children: [
652
+ /*#__PURE__*/ _jsxs(Stack, {
653
+ spacing: 2,
654
+ children: [
655
+ /*#__PURE__*/ _jsxs(Stack, {
656
+ direction: {
657
+ xs: 'column',
658
+ sm: 'row'
659
+ },
660
+ spacing: 1,
661
+ children: [
662
+ /*#__PURE__*/ _jsx(TextField, {
663
+ label: "Search",
664
+ placeholder: searchField === 'name' ? 'Product name' : searchField === 'skus' ? 'Whole SKU' : 'Whole barcode',
665
+ value: search,
666
+ onChange: (event)=>setSearch(event.target.value),
667
+ size: "small",
668
+ sx: {
669
+ flex: 1
670
+ }
671
+ }),
672
+ /*#__PURE__*/ _jsxs(TextField, {
673
+ label: "In",
674
+ value: searchField,
675
+ onChange: (event)=>setSearchField(event.target.value),
676
+ size: "small",
677
+ select: true,
678
+ sx: {
679
+ minWidth: 110
680
+ },
681
+ children: [
682
+ /*#__PURE__*/ _jsx(MenuItem, {
683
+ value: "name",
684
+ children: 'Name'
685
+ }),
686
+ /*#__PURE__*/ _jsx(MenuItem, {
687
+ value: "skus",
688
+ children: 'SKU'
689
+ }),
690
+ /*#__PURE__*/ _jsx(MenuItem, {
691
+ value: "barcodes",
692
+ children: 'Barcode'
693
+ })
694
+ ]
695
+ }),
696
+ /*#__PURE__*/ _jsxs(TextField, {
697
+ label: "Status",
698
+ value: statusFilter,
699
+ onChange: (event)=>setStatusFilter(event.target.value),
700
+ size: "small",
701
+ select: true,
702
+ sx: {
703
+ minWidth: 130
704
+ },
705
+ children: [
706
+ /*#__PURE__*/ _jsx(MenuItem, {
707
+ value: "all",
708
+ children: 'All'
709
+ }),
710
+ /*#__PURE__*/ _jsx(MenuItem, {
711
+ value: "active",
712
+ children: 'Active'
713
+ }),
714
+ /*#__PURE__*/ _jsx(MenuItem, {
715
+ value: "draft",
716
+ children: 'Draft'
717
+ }),
718
+ /*#__PURE__*/ _jsx(MenuItem, {
719
+ value: "archived",
720
+ children: 'Archived'
721
+ })
722
+ ]
723
+ }),
724
+ /*#__PURE__*/ _jsx(Button, {
725
+ variant: "contained",
726
+ color: "primary",
727
+ size: "small",
728
+ disabled: !productQuota,
729
+ onClick: ()=>{
730
+ if (!productQuota) return;
731
+ if (!productQuota.allowed) {
732
+ return void enqueueSnackbar(`Your plan includes ${productQuota.limit} products — ` + 'upgrade for more', {
733
+ variant: 'info',
734
+ persist: false
735
+ });
736
+ }
737
+ setCreating(true);
738
+ },
739
+ children: 'Add product'
740
+ }),
741
+ /*#__PURE__*/ _jsx(Button, {
742
+ size: "small",
743
+ disabled: !planReady,
744
+ onClick: ()=>{
745
+ setImportOptions({});
746
+ setImporting({
747
+ text: '',
748
+ parsed: null
749
+ });
750
+ },
751
+ children: 'Import'
752
+ }),
753
+ /*#__PURE__*/ _jsx(Button, {
754
+ size: "small",
755
+ disabled: products.length === 0,
756
+ onClick: handleExport,
757
+ children: 'Export'
758
+ })
759
+ ]
760
+ }),
761
+ /*#__PURE__*/ _jsx(QuotaReadoutComponent, {
762
+ ready: productQuota !== null,
763
+ used: productCount,
764
+ limit: (_ref2 = productQuota == null ? void 0 : productQuota.limit) != null ? _ref2 : 0,
765
+ noun: "product"
766
+ }),
767
+ /*#__PURE__*/ _jsx(ProductsHubZone, {
768
+ hostId: hostId,
769
+ products: products,
770
+ roomFor: roomFor,
771
+ lastImport: lastImport,
772
+ onCreated: onCatalogCreated
773
+ }),
774
+ products.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
775
+ variant: "body2",
776
+ color: "text.secondary",
777
+ children: search || statusFilter !== 'all' ? 'No products match the current filters.' : 'Build your catalog: add a product, then drop commerce ' + 'blocks on any screen in the besigner.'
778
+ }) : /*#__PURE__*/ _jsx(Box, {
779
+ children: /*#__PURE__*/ _jsxs(ScrollTable, {
780
+ size: "small",
781
+ children: [
782
+ /*#__PURE__*/ _jsx(TableHead, {
783
+ children: /*#__PURE__*/ _jsxs(TableRow, {
784
+ children: [
785
+ /*#__PURE__*/ _jsx(TableCell, {
786
+ children: 'Product'
787
+ }),
788
+ /*#__PURE__*/ _jsx(TableCell, {
789
+ children: 'Status'
790
+ }),
791
+ /*#__PURE__*/ _jsx(TableCell, {
792
+ children: 'Type'
793
+ }),
794
+ /*#__PURE__*/ _jsx(TableCell, {
795
+ children: 'Price'
796
+ }),
797
+ /*#__PURE__*/ _jsx(TableCell, {
798
+ children: 'Stock'
799
+ }),
800
+ /*#__PURE__*/ _jsx(TableCell, {
801
+ children: 'Variants'
802
+ }),
803
+ /*#__PURE__*/ _jsx(TableCell, {
804
+ align: "right",
805
+ children: 'Actions'
806
+ })
807
+ ]
808
+ })
809
+ }),
810
+ /*#__PURE__*/ _jsx(TableBody, {
811
+ children: visibleProducts.map((product)=>{
812
+ var _STATUS_COLOR_product_status;
813
+ return /*#__PURE__*/ _jsxs(TableRow, {
814
+ hover: true,
815
+ children: [
816
+ /*#__PURE__*/ _jsxs(TableCell, {
817
+ sx: {
818
+ maxWidth: 260
819
+ },
820
+ children: [
821
+ /*#__PURE__*/ _jsx(Typography, {
822
+ variant: "body2",
823
+ noWrap: true,
824
+ children: product.name
825
+ }),
826
+ /*#__PURE__*/ _jsx(Typography, {
827
+ variant: "caption",
828
+ color: "text.secondary",
829
+ noWrap: true,
830
+ sx: {
831
+ display: 'block'
832
+ },
833
+ children: `/${product.slug} · id: ${product.$id}`
834
+ })
835
+ ]
836
+ }),
837
+ /*#__PURE__*/ _jsx(TableCell, {
838
+ children: /*#__PURE__*/ _jsx(Chip, {
839
+ label: product.status,
840
+ size: "small",
841
+ color: (_STATUS_COLOR_product_status = STATUS_COLOR[product.status]) != null ? _STATUS_COLOR_product_status : 'default',
842
+ variant: "outlined"
843
+ })
844
+ }),
845
+ /*#__PURE__*/ _jsx(TableCell, {
846
+ children: product.type
847
+ }),
848
+ /*#__PURE__*/ _jsx(TableCell, {
849
+ children: CommerceModel.productPriceMissing(product) ? /*#__PURE__*/ _jsx(Chip, {
850
+ label: "Set a price",
851
+ size: "small",
852
+ color: "warning",
853
+ variant: "outlined"
854
+ }) : formatPrice(product)
855
+ }),
856
+ /*#__PURE__*/ _jsxs(TableCell, {
857
+ children: [
858
+ formatStock(product),
859
+ formatHeld(product) ? /*#__PURE__*/ _jsx(Typography, {
860
+ variant: "caption",
861
+ color: "text.secondary",
862
+ component: "span",
863
+ children: formatHeld(product)
864
+ }) : null
865
+ ]
866
+ }),
867
+ /*#__PURE__*/ _jsx(TableCell, {
868
+ children: product.variants.length
869
+ }),
870
+ /*#__PURE__*/ _jsxs(TableCell, {
871
+ align: "right",
872
+ sx: {
873
+ whiteSpace: 'nowrap'
874
+ },
875
+ children: [
876
+ /*#__PURE__*/ _jsx(Button, {
877
+ size: "small",
878
+ onClick: ()=>setEditing(product),
879
+ children: 'Edit'
880
+ }),
881
+ /*#__PURE__*/ _jsx(Button, {
882
+ size: "small",
883
+ onClick: handleDuplicate(product),
884
+ children: 'Duplicate'
885
+ }),
886
+ product.type === 'digital' ? /*#__PURE__*/ _jsx(Button, {
887
+ size: "small",
888
+ onClick: ()=>setKeysFor(product),
889
+ children: 'Keys'
890
+ }) : null,
891
+ CommerceModel.productInventory(product) != null ? /*#__PURE__*/ _jsx(Button, {
892
+ size: "small",
893
+ onClick: ()=>{
894
+ var _ref, _ref1, _ref2;
895
+ var _product_variants_find, _find, _;
896
+ return setAdjusting({
897
+ product,
898
+ variantId: (_ref = (_product_variants_find = product.variants.find((variant)=>variant.inventory != null)) == null ? void 0 : _product_variants_find.id) != null ? _ref : product.variants[0].id,
899
+ delta: '',
900
+ reason: 'restock',
901
+ locationId: (_ref1 = (_ref2 = (_find = (locationDocs != null ? locationDocs : []).find((location)=>location.isDefault)) == null ? void 0 : _find.$id) != null ? _ref2 : (_ = (locationDocs != null ? locationDocs : [])[0]) == null ? void 0 : _.$id) != null ? _ref1 : ''
902
+ });
903
+ },
904
+ children: 'Stock'
905
+ }) : null,
906
+ /*#__PURE__*/ _jsx(Button, {
907
+ size: "small",
908
+ onClick: handleStatus(product, product.status === 'archived' ? 'active' : 'archived'),
909
+ children: product.status === 'archived' ? 'Activate' : 'Archive'
910
+ }),
911
+ /*#__PURE__*/ _jsx(Button, {
912
+ size: "small",
913
+ color: "error",
914
+ onClick: handleDelete(product),
915
+ children: 'Delete'
916
+ })
917
+ ]
918
+ })
919
+ ]
920
+ }, product.$id);
921
+ })
922
+ })
923
+ ]
924
+ })
925
+ }),
926
+ products.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
927
+ page: page,
928
+ pageSize: pageSize,
929
+ rowCount: visibleProducts.length,
930
+ // The rows matching the current filters, which the card holds in
931
+ // full below the ceiling. NOT the catalog total — that is
932
+ // `productCount`, and it answers a different question.
933
+ count: products.length,
934
+ onPageChange: setPage,
935
+ onPageSizeChange: setPageSize
936
+ }),
937
+ catalogTruncated ? /*#__PURE__*/ _jsx(Alert, {
938
+ severity: "info",
939
+ children: `This table holds ${CATALOG_CEILING} products at a time and this ` + 'catalog is larger. Search reaches every product; the CSV ' + 'export covers what the table holds.'
940
+ }) : null
941
+ ]
942
+ }),
943
+ /*#__PURE__*/ _jsxs(Dialog, {
944
+ open: Boolean(keysFor),
945
+ onClose: ()=>setKeysFor(null),
946
+ maxWidth: "sm",
947
+ fullWidth: true,
948
+ children: [
949
+ /*#__PURE__*/ _jsx(DialogTitle, {
950
+ children: `License keys — ${(_ref3 = keysFor == null ? void 0 : keysFor.name) != null ? _ref3 : ''}`
951
+ }),
952
+ /*#__PURE__*/ _jsx(DialogContent, {
953
+ sx: {
954
+ display: 'flex',
955
+ flexDirection: 'column',
956
+ gap: 2
957
+ },
958
+ children: (()=>{
959
+ // The query already asked for this product's keys, so no second
960
+ // filter here: narrowing a window that was already narrowed is how
961
+ // a reader comes to believe the count covers the pool.
962
+ const productKeys = (keyDocs != null ? keyDocs : []).slice(0, KEY_POOL_CEILING);
963
+ const available = productKeys.filter((key)=>!key.assignedAtMs);
964
+ // RETIRED KEYS ARE COUNTED SEPARATELY (AGL-2454). A refund retires
965
+ // the key rather than returning it to the pool — the buyer already
966
+ // holds the string, so reissuing it would give two people one
967
+ // working key — and without this line the merchant simply watches
968
+ // "available" fall with nothing to explain where the key went.
969
+ const retired = productKeys.filter((key)=>key.revokedAtMs);
970
+ const assigned = productKeys.length - available.length - retired.length;
971
+ return /*#__PURE__*/ _jsxs(_Fragment, {
972
+ children: [
973
+ /*#__PURE__*/ _jsx(Typography, {
974
+ variant: "body2",
975
+ color: "text.secondary",
976
+ sx: {
977
+ mt: 1
978
+ },
979
+ children: `${available.length} available · ${assigned} assigned` + (retired.length ? ` · ${retired.length} retired (refunded or revoked — not reissued)` : '') + (keyPoolTruncated ? `, of the ${KEY_POOL_CEILING} keys read` : '') + '. Keys deliver automatically on purchase (receipt + account).'
980
+ }),
981
+ available.slice(0, 8).map((key)=>/*#__PURE__*/ _jsxs(Stack, {
982
+ direction: "row",
983
+ spacing: 1,
984
+ sx: {
985
+ alignItems: 'center'
986
+ },
987
+ children: [
988
+ /*#__PURE__*/ _jsx(Typography, {
989
+ variant: "caption",
990
+ sx: {
991
+ flex: 1,
992
+ fontFamily: 'monospace'
993
+ },
994
+ noWrap: true,
995
+ children: key.key
996
+ }),
997
+ /*#__PURE__*/ _jsx(Button, {
998
+ size: "small",
999
+ color: "error",
1000
+ onClick: ()=>updateDoc(doc(firestore, 'hosts', hostId, 'licenseKeys', key.$id), {
1001
+ revokedAtMs: Date.now(),
1002
+ assignedAtMs: Date.now()
1003
+ }),
1004
+ children: 'Revoke'
1005
+ })
1006
+ ]
1007
+ }, key.$id)),
1008
+ /*#__PURE__*/ _jsx(TextField, {
1009
+ label: "Add keys (one per line)",
1010
+ value: keysText,
1011
+ onChange: (event)=>setKeysText(event.target.value),
1012
+ size: "small",
1013
+ multiline: true,
1014
+ minRows: 3
1015
+ })
1016
+ ]
1017
+ });
1018
+ })()
1019
+ }),
1020
+ /*#__PURE__*/ _jsxs(DialogActions, {
1021
+ children: [
1022
+ /*#__PURE__*/ _jsx(Button, {
1023
+ onClick: ()=>setKeysFor(null),
1024
+ children: 'Close'
1025
+ }),
1026
+ /*#__PURE__*/ _jsx(Button, {
1027
+ variant: "contained",
1028
+ color: "primary",
1029
+ disabled: !keysText.trim(),
1030
+ onClick: async ()=>{
1031
+ const keys = keysText.split('\n').map((key)=>key.trim()).filter(Boolean).slice(0, 200);
1032
+ for (const key of keys){
1033
+ await addDoc(collection(firestore, 'hosts', hostId, 'licenseKeys'), {
1034
+ productId: keysFor.$id,
1035
+ key,
1036
+ assignedAtMs: null,
1037
+ createdAtMs: Date.now()
1038
+ });
1039
+ }
1040
+ setKeysText('');
1041
+ enqueueSnackbar(`Added ${keys.length} keys`, {
1042
+ variant: 'success',
1043
+ persist: false
1044
+ });
1045
+ },
1046
+ children: 'Add keys'
1047
+ })
1048
+ ]
1049
+ })
1050
+ ]
1051
+ }),
1052
+ /*#__PURE__*/ _jsxs(Dialog, {
1053
+ open: Boolean(importing),
1054
+ onClose: ()=>setImporting(null),
1055
+ maxWidth: "sm",
1056
+ fullWidth: true,
1057
+ children: [
1058
+ /*#__PURE__*/ _jsx(DialogTitle, {
1059
+ children: 'Import products (CSV)'
1060
+ }),
1061
+ /*#__PURE__*/ _jsxs(DialogContent, {
1062
+ sx: {
1063
+ display: 'flex',
1064
+ flexDirection: 'column',
1065
+ gap: 2
1066
+ },
1067
+ children: [
1068
+ /*#__PURE__*/ _jsx(Typography, {
1069
+ variant: "body2",
1070
+ color: "text.secondary",
1071
+ sx: {
1072
+ mt: 1
1073
+ },
1074
+ children: 'Shopify-compatible columns (Handle, Title, Option/Variant ' + 'columns, Image Src). Paste the file contents or choose a file.'
1075
+ }),
1076
+ /*#__PURE__*/ _jsxs(Button, {
1077
+ component: "label",
1078
+ size: "small",
1079
+ sx: {
1080
+ alignSelf: 'flex-start'
1081
+ },
1082
+ children: [
1083
+ 'Choose file',
1084
+ /*#__PURE__*/ _jsx("input", {
1085
+ type: "file",
1086
+ accept: ".csv,text/csv",
1087
+ hidden: true,
1088
+ onChange: async (event)=>{
1089
+ var _event_target_files;
1090
+ const file = (_event_target_files = event.target.files) == null ? void 0 : _event_target_files[0];
1091
+ if (!file) return;
1092
+ const text = await file.text();
1093
+ setImporting({
1094
+ text,
1095
+ parsed: CommerceModel.parseProductsCsv(text)
1096
+ });
1097
+ }
1098
+ })
1099
+ ]
1100
+ }),
1101
+ /*#__PURE__*/ _jsx(TextField, {
1102
+ label: "CSV",
1103
+ value: (_ref4 = importing == null ? void 0 : importing.text) != null ? _ref4 : '',
1104
+ onChange: (event)=>setImporting({
1105
+ text: event.target.value,
1106
+ parsed: event.target.value.trim() ? CommerceModel.parseProductsCsv(event.target.value) : null
1107
+ }),
1108
+ size: "small",
1109
+ multiline: true,
1110
+ minRows: 5,
1111
+ maxRows: 10
1112
+ }),
1113
+ (importing == null ? void 0 : importing.parsed) ? /*#__PURE__*/ _jsxs(_Fragment, {
1114
+ children: [
1115
+ /*#__PURE__*/ _jsx(Typography, {
1116
+ variant: "body2",
1117
+ children: `Ready to import ${importing.parsed.products.length} products` + (importing.parsed.errors.length ? ` — ${importing.parsed.errors.length} rows skipped:` : '')
1118
+ }),
1119
+ importing.parsed.errors.slice(0, 5).map((error)=>/*#__PURE__*/ _jsx(Typography, {
1120
+ variant: "caption",
1121
+ color: "warning.main",
1122
+ children: error
1123
+ }, error))
1124
+ ]
1125
+ }) : null,
1126
+ WidgetSlot && (importing == null ? void 0 : (_importing_parsed = importing.parsed) == null ? void 0 : _importing_parsed.products.length) ? /*#__PURE__*/ _jsx(WidgetSlot, {
1127
+ slot: "productImport",
1128
+ hostId: hostId,
1129
+ orgId: undefined,
1130
+ count: importing.parsed.products.length,
1131
+ options: importOptions,
1132
+ setOption: setImportOption
1133
+ }) : null
1134
+ ]
1135
+ }),
1136
+ /*#__PURE__*/ _jsxs(DialogActions, {
1137
+ children: [
1138
+ /*#__PURE__*/ _jsx(Button, {
1139
+ onClick: ()=>setImporting(null),
1140
+ children: 'Cancel'
1141
+ }),
1142
+ /*#__PURE__*/ _jsx(Button, {
1143
+ variant: "contained",
1144
+ color: "primary",
1145
+ disabled: !(importing == null ? void 0 : (_importing_parsed1 = importing.parsed) == null ? void 0 : _importing_parsed1.products.length),
1146
+ onClick: handleImportApply,
1147
+ children: `Import${(importing == null ? void 0 : (_importing_parsed2 = importing.parsed) == null ? void 0 : _importing_parsed2.products.length) ? ` ${importing.parsed.products.length}` : ''}`
1148
+ })
1149
+ ]
1150
+ })
1151
+ ]
1152
+ }),
1153
+ /*#__PURE__*/ _jsxs(Dialog, {
1154
+ open: Boolean(adjusting),
1155
+ onClose: ()=>setAdjusting(null),
1156
+ maxWidth: "xs",
1157
+ fullWidth: true,
1158
+ children: [
1159
+ /*#__PURE__*/ _jsx(DialogTitle, {
1160
+ children: `Adjust stock — ${(_ref5 = adjusting == null ? void 0 : adjusting.product.name) != null ? _ref5 : ''}`
1161
+ }),
1162
+ /*#__PURE__*/ _jsxs(DialogContent, {
1163
+ sx: {
1164
+ display: 'flex',
1165
+ flexDirection: 'column',
1166
+ gap: 2
1167
+ },
1168
+ children: [
1169
+ /*#__PURE__*/ _jsx(TextField, {
1170
+ label: "Variant",
1171
+ value: (_ref6 = adjusting == null ? void 0 : adjusting.variantId) != null ? _ref6 : '',
1172
+ onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
1173
+ variantId: event.target.value
1174
+ }) : prev),
1175
+ size: "small",
1176
+ select: true,
1177
+ sx: {
1178
+ mt: 1
1179
+ },
1180
+ children: ((_ref7 = adjusting == null ? void 0 : adjusting.product.variants) != null ? _ref7 : []).filter((variant)=>variant.inventory != null).map((variant)=>{
1181
+ var _variant_options;
1182
+ return /*#__PURE__*/ _jsx(MenuItem, {
1183
+ value: variant.id,
1184
+ children: `${Object.values((_variant_options = variant.options) != null ? _variant_options : {}).join(' / ') || 'Default'} — ${variant.inventory} in stock${CommerceModel.heldVariantUnits(adjusting == null ? void 0 : adjusting.product, variant.id, nowMs) > 0 ? `, ${CommerceModel.heldVariantUnits(adjusting == null ? void 0 : adjusting.product, variant.id, nowMs)} reserved in checkout` : ''}`
1185
+ }, variant.id);
1186
+ })
1187
+ }),
1188
+ ((_ref8 = locationDocs == null ? void 0 : locationDocs.length) != null ? _ref8 : 0) > 1 ? /*#__PURE__*/ _jsx(TextField, {
1189
+ label: "Location",
1190
+ value: (_ref9 = adjusting == null ? void 0 : adjusting.locationId) != null ? _ref9 : '',
1191
+ onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
1192
+ locationId: event.target.value
1193
+ }) : prev),
1194
+ size: "small",
1195
+ select: true,
1196
+ children: (locationDocs != null ? locationDocs : []).map((location)=>/*#__PURE__*/ _jsx(MenuItem, {
1197
+ value: location.$id,
1198
+ children: location.name
1199
+ }, location.$id))
1200
+ }) : null,
1201
+ /*#__PURE__*/ _jsx(TextField, {
1202
+ label: "Change",
1203
+ placeholder: "+10 or -3",
1204
+ value: (_ref10 = adjusting == null ? void 0 : adjusting.delta) != null ? _ref10 : '',
1205
+ onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
1206
+ delta: event.target.value.replace(/[^0-9+-]/g, '')
1207
+ }) : prev),
1208
+ size: "small"
1209
+ }),
1210
+ /*#__PURE__*/ _jsxs(TextField, {
1211
+ label: "Reason",
1212
+ value: (_ref11 = adjusting == null ? void 0 : adjusting.reason) != null ? _ref11 : 'restock',
1213
+ onChange: (event)=>setAdjusting((prev)=>prev ? _extends({}, prev, {
1214
+ reason: event.target.value
1215
+ }) : prev),
1216
+ size: "small",
1217
+ select: true,
1218
+ children: [
1219
+ /*#__PURE__*/ _jsx(MenuItem, {
1220
+ value: "restock",
1221
+ children: 'Restock'
1222
+ }),
1223
+ /*#__PURE__*/ _jsx(MenuItem, {
1224
+ value: "correction",
1225
+ children: 'Correction'
1226
+ }),
1227
+ /*#__PURE__*/ _jsx(MenuItem, {
1228
+ value: "damage",
1229
+ children: 'Damaged'
1230
+ }),
1231
+ /*#__PURE__*/ _jsx(MenuItem, {
1232
+ value: "refund",
1233
+ children: 'Refund return'
1234
+ })
1235
+ ]
1236
+ })
1237
+ ]
1238
+ }),
1239
+ /*#__PURE__*/ _jsxs(DialogActions, {
1240
+ children: [
1241
+ /*#__PURE__*/ _jsx(Button, {
1242
+ onClick: ()=>setAdjusting(null),
1243
+ children: 'Cancel'
1244
+ }),
1245
+ /*#__PURE__*/ _jsx(Button, {
1246
+ variant: "contained",
1247
+ color: "primary",
1248
+ disabled: !Math.round(Number(adjusting == null ? void 0 : adjusting.delta)),
1249
+ onClick: handleAdjustSave,
1250
+ children: 'Apply'
1251
+ })
1252
+ ]
1253
+ })
1254
+ ]
1255
+ }),
1256
+ /*#__PURE__*/ _jsx(ProductEditorDialog, {
1257
+ hostId: hostId,
1258
+ product: editing,
1259
+ // The editor is seeded from a row of THIS listener and replaces the
1260
+ // whole document, so the freshness verdict belongs here (AGL-1358) —
1261
+ // the dialog has no listener of its own to ask.
1262
+ seedFromCache: productsFromCache,
1263
+ seedUnreadable: productsStatus === 'error',
1264
+ open: creating || editing !== null,
1265
+ onClose: ()=>{
1266
+ setEditing(null);
1267
+ setCreating(false);
1268
+ // The dialog reports no verdict, so a create and a cancel look
1269
+ // the same from here. Re-reading on both is one aggregate and
1270
+ // keeps the cap off the dialog's shoulders (AGL-1716).
1271
+ setProductCountEpoch((epoch)=>epoch + 1);
1272
+ }
1273
+ }, (_ref12 = editing == null ? void 0 : editing.$id) != null ? _ref12 : creating ? 'new' : 'closed')
1274
+ ]
1275
+ });
1276
+ }
1277
+ ProductsHubCard.displayName = 'ProductsHubCard';
1278
+ export default ProductsHubCard;
1279
+
1280
+ //# sourceMappingURL=products-hub-card.component.js.map