@mohasinac/appkit 3.0.4 → 3.1.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 (320) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +42 -0
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +86 -0
  3. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.d.ts +14 -0
  4. package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +46 -0
  5. package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +28 -0
  6. package/dist/_internal/client/features/lottery/LotteryDetailView.js +17 -0
  7. package/dist/_internal/client/features/lottery/LotteryEntriesView.d.ts +23 -0
  8. package/dist/_internal/client/features/lottery/LotteryEntriesView.js +58 -0
  9. package/dist/_internal/client/features/lottery/LotteryListView.d.ts +21 -0
  10. package/dist/_internal/client/features/lottery/LotteryListView.js +25 -0
  11. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +19 -0
  12. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -0
  13. package/dist/_internal/client/features/lottery/LotterySlotGrid.d.ts +13 -0
  14. package/dist/_internal/client/features/lottery/LotterySlotGrid.js +21 -0
  15. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.d.ts +28 -0
  16. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +18 -0
  17. package/dist/_internal/server/features/auctions/__tests__/service.test.d.ts +1 -0
  18. package/dist/_internal/server/features/auctions/__tests__/service.test.js +140 -0
  19. package/dist/_internal/server/features/auctions/data.d.ts +2 -4
  20. package/dist/_internal/server/features/auctions/data.js +3 -9
  21. package/dist/_internal/server/features/checkout/__tests__/actions.test.d.ts +1 -0
  22. package/dist/_internal/server/features/checkout/__tests__/actions.test.js +600 -0
  23. package/dist/_internal/server/features/classified/adapters.d.ts +2 -15
  24. package/dist/_internal/server/features/classified/adapters.js +2 -17
  25. package/dist/_internal/server/features/classified/data.d.ts +1 -6
  26. package/dist/_internal/server/features/classified/data.js +2 -10
  27. package/dist/_internal/server/features/digital-code/adapters.d.ts +2 -15
  28. package/dist/_internal/server/features/digital-code/adapters.js +2 -17
  29. package/dist/_internal/server/features/digital-code/data.d.ts +1 -6
  30. package/dist/_internal/server/features/digital-code/data.js +2 -10
  31. package/dist/_internal/server/features/live/adapters.d.ts +2 -15
  32. package/dist/_internal/server/features/live/adapters.js +2 -17
  33. package/dist/_internal/server/features/live/data.d.ts +1 -6
  34. package/dist/_internal/server/features/live/data.js +2 -10
  35. package/dist/_internal/server/features/lottery/actions.d.ts +43 -0
  36. package/dist/_internal/server/features/lottery/actions.js +243 -0
  37. package/dist/_internal/server/features/lottery/adapters.d.ts +9 -0
  38. package/dist/_internal/server/features/lottery/adapters.js +21 -0
  39. package/dist/_internal/server/features/lottery/data.d.ts +20 -0
  40. package/dist/_internal/server/features/lottery/data.js +41 -0
  41. package/dist/_internal/server/features/lottery/metadata.d.ts +6 -0
  42. package/dist/_internal/server/features/lottery/metadata.js +31 -0
  43. package/dist/_internal/server/features/pre-orders/data.d.ts +2 -4
  44. package/dist/_internal/server/features/pre-orders/data.js +3 -9
  45. package/dist/_internal/server/features/prize-draws/actions.d.ts +2 -0
  46. package/dist/_internal/server/features/prize-draws/actions.js +22 -0
  47. package/dist/_internal/server/features/prize-draws/data.d.ts +1 -17
  48. package/dist/_internal/server/features/prize-draws/data.js +2 -20
  49. package/dist/_internal/server/features/prize-draws/index.d.ts +3 -1
  50. package/dist/_internal/server/features/prize-draws/index.js +2 -0
  51. package/dist/_internal/server/features/prize-draws/service.d.ts +3 -0
  52. package/dist/_internal/server/features/prize-draws/service.js +19 -0
  53. package/dist/_internal/server/features/refunds/__tests__/actions.test.d.ts +1 -0
  54. package/dist/_internal/server/features/refunds/__tests__/actions.test.js +263 -0
  55. package/dist/_internal/server/features/shared/listing-adapters.d.ts +19 -0
  56. package/dist/_internal/server/features/shared/listing-adapters.js +20 -0
  57. package/dist/_internal/server/features/shared/listing-data-factory.d.ts +9 -0
  58. package/dist/_internal/server/features/shared/listing-data-factory.js +33 -0
  59. package/dist/_internal/server/features/stores/data.d.ts +3 -14
  60. package/dist/_internal/server/features/stores/data.js +6 -44
  61. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.d.ts +1 -0
  62. package/dist/_internal/server/jobs/core/__tests__/auctionSettlement.test.js +249 -0
  63. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.d.ts +1 -0
  64. package/dist/_internal/server/jobs/core/__tests__/onBidPlaced.test.js +201 -0
  65. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.d.ts +1 -0
  66. package/dist/_internal/server/jobs/core/__tests__/onOrderCreate.test.js +169 -0
  67. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.d.ts +1 -0
  68. package/dist/_internal/server/jobs/core/__tests__/onOrderStatusChange.test.js +148 -0
  69. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.d.ts +1 -0
  70. package/dist/_internal/server/jobs/core/__tests__/onProductStockChange.test.js +210 -0
  71. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.d.ts +1 -0
  72. package/dist/_internal/server/jobs/core/__tests__/onReviewWrite.test.js +190 -0
  73. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.d.ts +1 -0
  74. package/dist/_internal/server/jobs/core/__tests__/onUserBanChange.test.js +161 -0
  75. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.d.ts +1 -0
  76. package/dist/_internal/server/jobs/core/__tests__/payoutBatch.test.js +302 -0
  77. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.d.ts +1 -0
  78. package/dist/_internal/server/jobs/core/__tests__/triggerEventRaffle.test.js +252 -0
  79. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.d.ts +1 -0
  80. package/dist/_internal/server/jobs/core/__tests__/wrapJobHandler.test.js +129 -0
  81. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  82. package/dist/_internal/shared/actions/action-registry.js +46 -0
  83. package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
  84. package/dist/_internal/shared/features/cart/schema.js +3 -0
  85. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  86. package/dist/_internal/shared/listing-types/_registry.js +2 -0
  87. package/dist/_internal/shared/listing-types/auction/schema.d.ts +3 -1
  88. package/dist/_internal/shared/listing-types/auction/schema.js +7 -1
  89. package/dist/_internal/shared/listing-types/classified/schema.d.ts +2 -1
  90. package/dist/_internal/shared/listing-types/classified/schema.js +3 -1
  91. package/dist/_internal/shared/listing-types/digital-code/schema.d.ts +3 -1
  92. package/dist/_internal/shared/listing-types/digital-code/schema.js +7 -1
  93. package/dist/_internal/shared/listing-types/live/schema.d.ts +2 -1
  94. package/dist/_internal/shared/listing-types/live/schema.js +3 -1
  95. package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +3 -1
  96. package/dist/_internal/shared/listing-types/pre-order/schema.js +7 -1
  97. package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +3 -1
  98. package/dist/_internal/shared/listing-types/prize-draw/schema.js +13 -1
  99. package/dist/_internal/shared/listing-types/standard/schema.d.ts +2 -1
  100. package/dist/_internal/shared/listing-types/standard/schema.js +4 -1
  101. package/dist/_internal/shared/types/base-document.d.ts +15 -0
  102. package/dist/_internal/shared/types/base-document.js +1 -0
  103. package/dist/_internal/shared/types/index.d.ts +2 -0
  104. package/dist/_internal/shared/types/index.js +1 -0
  105. package/dist/client.d.ts +13 -4
  106. package/dist/client.js +11 -2
  107. package/dist/constants/api-endpoints.d.ts +12 -0
  108. package/dist/constants/api-endpoints.js +4 -0
  109. package/dist/constants/field-names.d.ts +5 -1
  110. package/dist/constants/field-names.js +5 -1
  111. package/dist/features/addresses/repository/__tests__/addresses.repository.test.d.ts +1 -0
  112. package/dist/features/addresses/repository/__tests__/addresses.repository.test.js +386 -0
  113. package/dist/features/addresses/schemas/firestore.d.ts +2 -4
  114. package/dist/features/admin/api/bids/route.d.ts +2 -2
  115. package/dist/features/admin/api/coupons/route.d.ts +2 -2
  116. package/dist/features/admin/api/products/route.d.ts +2 -2
  117. package/dist/features/admin/api/reviews/route.d.ts +2 -2
  118. package/dist/features/admin/components/AdminFulfillmentView.d.ts +6 -0
  119. package/dist/features/admin/components/AdminFulfillmentView.js +107 -0
  120. package/dist/features/admin/components/AdminProductEditorView.js +21 -9
  121. package/dist/features/admin/components/AdminProductsView.js +3 -0
  122. package/dist/features/admin/components/DataListingView.d.ts +1 -0
  123. package/dist/features/admin/components/index.d.ts +2 -0
  124. package/dist/features/admin/components/index.js +1 -0
  125. package/dist/features/admin/repository/__tests__/notification.repository.test.d.ts +1 -0
  126. package/dist/features/admin/repository/__tests__/notification.repository.test.js +356 -0
  127. package/dist/features/admin/repository/__tests__/site-settings.repository.test.d.ts +1 -0
  128. package/dist/features/admin/repository/__tests__/site-settings.repository.test.js +298 -0
  129. package/dist/features/admin/schemas/firestore.d.ts +3 -8
  130. package/dist/features/auctions/repository/__tests__/bid.repository.test.d.ts +1 -0
  131. package/dist/features/auctions/repository/__tests__/bid.repository.test.js +290 -0
  132. package/dist/features/auctions/schemas/firestore.d.ts +2 -4
  133. package/dist/features/auctions/schemas/index.d.ts +2 -2
  134. package/dist/features/auth/api/route.d.ts +2 -2
  135. package/dist/features/auth/repository/__tests__/session.repository.test.d.ts +1 -0
  136. package/dist/features/auth/repository/__tests__/session.repository.test.js +310 -0
  137. package/dist/features/auth/repository/__tests__/session.test.d.ts +1 -0
  138. package/dist/features/auth/repository/__tests__/session.test.js +146 -0
  139. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.d.ts +1 -0
  140. package/dist/features/auth/repository/__tests__/sms-counter.repository.test.js +171 -0
  141. package/dist/features/auth/repository/__tests__/sms-counter.test.d.ts +1 -0
  142. package/dist/features/auth/repository/__tests__/sms-counter.test.js +88 -0
  143. package/dist/features/auth/repository/__tests__/token.repository.test.d.ts +1 -0
  144. package/dist/features/auth/repository/__tests__/token.repository.test.js +260 -0
  145. package/dist/features/auth/repository/__tests__/token.test.d.ts +1 -0
  146. package/dist/features/auth/repository/__tests__/token.test.js +140 -0
  147. package/dist/features/auth/repository/__tests__/user.repository.test.d.ts +1 -0
  148. package/dist/features/auth/repository/__tests__/user.repository.test.js +352 -0
  149. package/dist/features/auth/repository/__tests__/user.test.d.ts +1 -0
  150. package/dist/features/auth/repository/__tests__/user.test.js +226 -0
  151. package/dist/features/auth/repository/session.repository.js +10 -1
  152. package/dist/features/before-after/schemas/index.d.ts +2 -2
  153. package/dist/features/blog/schemas/firestore.d.ts +2 -4
  154. package/dist/features/blog/schemas/index.d.ts +4 -4
  155. package/dist/features/cart/repository/__tests__/cart.repository.test.d.ts +1 -0
  156. package/dist/features/cart/repository/__tests__/cart.repository.test.js +371 -0
  157. package/dist/features/cart/repository/cart.repository.js +2 -2
  158. package/dist/features/cart/schemas/firestore.d.ts +2 -4
  159. package/dist/features/categories/api/[id]/route.d.ts +4 -4
  160. package/dist/features/categories/api/route.d.ts +2 -2
  161. package/dist/features/categories/repository/__tests__/categories.repository.test.d.ts +1 -0
  162. package/dist/features/categories/repository/__tests__/categories.repository.test.js +813 -0
  163. package/dist/features/categories/schemas/firestore.d.ts +2 -4
  164. package/dist/features/categories/schemas/index.d.ts +4 -4
  165. package/dist/features/events/components/EventCard.js +1 -0
  166. package/dist/features/events/repository/__tests__/event.repository.test.d.ts +1 -0
  167. package/dist/features/events/repository/__tests__/event.repository.test.js +299 -0
  168. package/dist/features/events/schemas/firestore.d.ts +5 -4
  169. package/dist/features/events/schemas/index.d.ts +836 -16
  170. package/dist/features/events/schemas/index.js +80 -1
  171. package/dist/features/events/types/index.d.ts +4 -1
  172. package/dist/features/faq/repository/__tests__/faqs.repository.test.d.ts +1 -0
  173. package/dist/features/faq/repository/__tests__/faqs.repository.test.js +311 -0
  174. package/dist/features/faq/schemas/firestore.d.ts +2 -4
  175. package/dist/features/faq/schemas/index.d.ts +2 -2
  176. package/dist/features/grouped/schemas/index.d.ts +2 -2
  177. package/dist/features/history/repository/__tests__/user-history.repository.test.d.ts +1 -0
  178. package/dist/features/history/repository/__tests__/user-history.repository.test.js +180 -0
  179. package/dist/features/homepage/api/[id]/route.d.ts +4 -4
  180. package/dist/features/homepage/api/carousel/[id]/route.d.ts +4 -4
  181. package/dist/features/homepage/api/carousel/route.d.ts +2 -2
  182. package/dist/features/homepage/api/route.d.ts +2 -2
  183. package/dist/features/homepage/repository/__tests__/carousel.repository.test.d.ts +1 -0
  184. package/dist/features/homepage/repository/__tests__/carousel.repository.test.js +182 -0
  185. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.d.ts +1 -0
  186. package/dist/features/homepage/repository/__tests__/homepage-sections.repository.test.js +185 -0
  187. package/dist/features/homepage/schemas/firestore.d.ts +4 -12
  188. package/dist/features/homepage/schemas/index.d.ts +2 -2
  189. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +33 -0
  190. package/dist/features/lottery/repository/lottery-entry.repository.js +130 -0
  191. package/dist/features/lottery/schemas/firestore.d.ts +49 -0
  192. package/dist/features/lottery/schemas/firestore.js +25 -0
  193. package/dist/features/lottery/schemas/zod.d.ts +113 -0
  194. package/dist/features/lottery/schemas/zod.js +39 -0
  195. package/dist/features/lottery/types/index.d.ts +67 -0
  196. package/dist/features/lottery/types/index.js +55 -0
  197. package/dist/features/messages/repository/__tests__/conversations.repository.test.d.ts +1 -0
  198. package/dist/features/messages/repository/__tests__/conversations.repository.test.js +257 -0
  199. package/dist/features/messages/schemas/firestore.d.ts +2 -4
  200. package/dist/features/messages/schemas/index.d.ts +2 -2
  201. package/dist/features/orders/repository/__tests__/orders.repository.test.d.ts +1 -0
  202. package/dist/features/orders/repository/__tests__/orders.repository.test.js +211 -0
  203. package/dist/features/orders/repository/orders.repository.d.ts +5 -0
  204. package/dist/features/orders/repository/orders.repository.js +26 -0
  205. package/dist/features/orders/schemas/firestore.d.ts +9 -5
  206. package/dist/features/orders/schemas/firestore.js +3 -0
  207. package/dist/features/payments/repository/__tests__/payout.repository.test.d.ts +1 -0
  208. package/dist/features/payments/repository/__tests__/payout.repository.test.js +292 -0
  209. package/dist/features/payments/schemas/firestore.d.ts +2 -4
  210. package/dist/features/pre-orders/schemas/index.d.ts +2 -2
  211. package/dist/features/products/api/[id]/route.d.ts +4 -4
  212. package/dist/features/products/api/route.d.ts +2 -2
  213. package/dist/features/products/constants/action-defs.d.ts +16 -0
  214. package/dist/features/products/constants/action-defs.js +16 -0
  215. package/dist/features/products/constants/listing-tabs.d.ts +48 -0
  216. package/dist/features/products/constants/listing-tabs.js +12 -0
  217. package/dist/features/products/repository/__tests__/product-features.repository.test.d.ts +1 -0
  218. package/dist/features/products/repository/__tests__/product-features.repository.test.js +329 -0
  219. package/dist/features/products/repository/__tests__/products.repository.test.d.ts +1 -0
  220. package/dist/features/products/repository/__tests__/products.repository.test.js +607 -0
  221. package/dist/features/products/repository/products.repository.d.ts +37 -0
  222. package/dist/features/products/repository/products.repository.js +28 -1
  223. package/dist/features/products/schemas/firestore.d.ts +23 -7
  224. package/dist/features/products/schemas/firestore.js +2 -0
  225. package/dist/features/products/schemas/index.d.ts +18 -18
  226. package/dist/features/products/schemas/index.js +4 -2
  227. package/dist/features/products/types/index.d.ts +3 -1
  228. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.d.ts +1 -0
  229. package/dist/features/promotions/repository/__tests__/claimed-coupons.repository.test.js +219 -0
  230. package/dist/features/promotions/repository/__tests__/coupons.repository.test.d.ts +1 -0
  231. package/dist/features/promotions/repository/__tests__/coupons.repository.test.js +480 -0
  232. package/dist/features/promotions/schemas/firestore.d.ts +2 -4
  233. package/dist/features/promotions/schemas/index.d.ts +4 -4
  234. package/dist/features/reviews/api/[id]/route.d.ts +4 -4
  235. package/dist/features/reviews/api/route.d.ts +2 -2
  236. package/dist/features/reviews/repository/__tests__/reviews.repository.test.d.ts +1 -0
  237. package/dist/features/reviews/repository/__tests__/reviews.repository.test.js +327 -0
  238. package/dist/features/reviews/schemas/firestore.d.ts +2 -4
  239. package/dist/features/reviews/schemas/index.d.ts +2 -2
  240. package/dist/features/scams/repository/__tests__/scammer.repository.test.d.ts +1 -0
  241. package/dist/features/scams/repository/__tests__/scammer.repository.test.js +385 -0
  242. package/dist/features/scams/schemas/firestore.d.ts +2 -4
  243. package/dist/features/scams/schemas/index.d.ts +4 -4
  244. package/dist/features/search/repository/__tests__/search.repository.test.d.ts +1 -0
  245. package/dist/features/search/repository/__tests__/search.repository.test.js +369 -0
  246. package/dist/features/search/schemas/index.d.ts +2 -2
  247. package/dist/features/seller/api/coupons/route.d.ts +2 -2
  248. package/dist/features/seller/api/offers/route.d.ts +2 -2
  249. package/dist/features/seller/api/products/route.d.ts +2 -2
  250. package/dist/features/seller/api/store/route.d.ts +2 -2
  251. package/dist/features/seller/components/BarcodeField.d.ts +17 -0
  252. package/dist/features/seller/components/BarcodeField.js +30 -0
  253. package/dist/features/seller/components/FulfillmentView.d.ts +5 -0
  254. package/dist/features/seller/components/FulfillmentView.js +120 -0
  255. package/dist/features/seller/components/PrintCenterView.d.ts +3 -1
  256. package/dist/features/seller/components/PrintCenterView.js +52 -4
  257. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  258. package/dist/features/seller/components/SellerProductShell.js +2 -1
  259. package/dist/features/seller/components/index.d.ts +4 -0
  260. package/dist/features/seller/components/index.js +2 -0
  261. package/dist/features/seller/repository/__tests__/offer.repository.test.d.ts +1 -0
  262. package/dist/features/seller/repository/__tests__/offer.repository.test.js +458 -0
  263. package/dist/features/seller/schemas/index.d.ts +2 -2
  264. package/dist/features/store-extensions/schemas/firestore.d.ts +12 -44
  265. package/dist/features/stores/components/StoreClassifiedsListing.d.ts +5 -0
  266. package/dist/features/stores/components/StoreClassifiedsListing.js +48 -0
  267. package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -0
  268. package/dist/features/stores/components/StoreClassifiedsPageView.js +20 -0
  269. package/dist/features/stores/components/StoreDetailLayoutView.js +23 -2
  270. package/dist/features/stores/components/StoreDigitalCodesListing.d.ts +5 -0
  271. package/dist/features/stores/components/StoreDigitalCodesListing.js +48 -0
  272. package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -0
  273. package/dist/features/stores/components/StoreDigitalCodesPageView.js +20 -0
  274. package/dist/features/stores/components/StoreLiveItemsListing.d.ts +5 -0
  275. package/dist/features/stores/components/StoreLiveItemsListing.js +48 -0
  276. package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -0
  277. package/dist/features/stores/components/StoreLiveItemsPageView.js +20 -0
  278. package/dist/features/stores/components/index.d.ts +9 -0
  279. package/dist/features/stores/components/index.js +3 -0
  280. package/dist/features/stores/repository/__tests__/store.repository.test.d.ts +1 -0
  281. package/dist/features/stores/repository/__tests__/store.repository.test.js +261 -0
  282. package/dist/features/stores/schemas/firestore.d.ts +2 -4
  283. package/dist/features/stores/schemas/index.d.ts +4 -4
  284. package/dist/features/support/repository/__tests__/support.repository.test.d.ts +1 -0
  285. package/dist/features/support/repository/__tests__/support.repository.test.js +312 -0
  286. package/dist/features/support/schemas/firestore.d.ts +2 -4
  287. package/dist/features/support/schemas/index.d.ts +2 -2
  288. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.d.ts +1 -0
  289. package/dist/features/wishlist/repository/__tests__/user-wishlist.repository.test.js +139 -0
  290. package/dist/index.d.ts +14 -0
  291. package/dist/index.js +17 -0
  292. package/dist/next/api/routeHandler.d.ts +1 -1
  293. package/dist/next/routing/route-map.d.ts +24 -0
  294. package/dist/next/routing/route-map.js +11 -0
  295. package/dist/repositories/index.d.ts +1 -0
  296. package/dist/repositories/index.js +2 -0
  297. package/dist/schemas/registry.d.ts +24 -24
  298. package/dist/security/index.d.ts +1 -1
  299. package/dist/security/index.js +1 -1
  300. package/dist/security/pii-schemas.d.ts +2 -0
  301. package/dist/security/pii-schemas.js +6 -0
  302. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  303. package/dist/seed/events-seed-data.js +85 -2
  304. package/dist/seed/grouped-listings-seed-data.d.ts +5 -2
  305. package/dist/seed/index.d.ts +1 -0
  306. package/dist/seed/index.js +1 -0
  307. package/dist/seed/lottery-entries-seed-data.d.ts +2 -0
  308. package/dist/seed/lottery-entries-seed-data.js +108 -0
  309. package/dist/seed/manifest.js +1 -0
  310. package/dist/seed/products-preorders-seed-data.d.ts +5 -2
  311. package/dist/seed/products-standard-seed-data.js +8 -0
  312. package/dist/server-entry.d.ts +3 -0
  313. package/dist/server-entry.js +3 -0
  314. package/dist/server.d.ts +10 -4
  315. package/dist/server.js +6 -0
  316. package/dist/styles.css +1 -1
  317. package/dist/tailwind-utilities.css +1 -1
  318. package/dist/utils/id-generators.d.ts +8 -0
  319. package/dist/utils/id-generators.js +14 -0
  320. package/package.json +2 -1
@@ -1,7 +1,55 @@
1
1
  "use client";
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import JsBarcode from "jsbarcode";
3
5
  import { Heading, Text } from "../../../ui/components/Typography";
4
- import { Stack } from "@mohasinac/appkit";
5
- export function PrintCenterView({ store, brandName = "LetItRip" }) {
6
- return (_jsxs(Stack, { justify: "center", padding: "y-6xl", className: "text-center", align: "center", gap: "md", children: [_jsxs(Heading, { level: 2, children: [brandName, " Print Center"] }), store && _jsx(Text, { size: "sm", variant: "muted", children: store.storeName }), _jsx(Text, { size: "sm", variant: "muted", children: "Print center features are coming soon." })] }));
6
+ import { Stack, Row } from "../../../ui/components/Layout";
7
+ import { Button } from "../../../ui/components/Button";
8
+ import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
9
+ function ProductLabel({ product }) {
10
+ const svgRef = useRef(null);
11
+ useEffect(() => {
12
+ if (svgRef.current && product.barcodeId) {
13
+ try {
14
+ JsBarcode(svgRef.current, product.barcodeId, {
15
+ format: "CODE128",
16
+ width: 2,
17
+ height: 50,
18
+ displayValue: true,
19
+ fontSize: 12,
20
+ margin: 8,
21
+ });
22
+ }
23
+ catch {
24
+ // Invalid barcode value — render nothing
25
+ }
26
+ }
27
+ }, [product.barcodeId]);
28
+ return (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "sm", gap: "xs", className: "break-inside-avoid text-center w-[200px]", children: [product.barcodeId ? (_jsx("svg", { ref: svgRef })) : (_jsx(Text, { size: "xs", color: "muted", children: "No barcode" })), _jsx(Text, { size: "xs", weight: "medium", truncate: 1, children: product.name }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", (product.price / 100).toLocaleString("en-IN")] }), product.physicalLocation && (_jsx(Text, { size: "xs", color: "muted", children: product.physicalLocation }))] }));
29
+ }
30
+ function PackingSlip({ order, brandName }) {
31
+ return (_jsxs(Stack, { surface: "card", padding: "md", rounded: "sm", className: "break-inside-avoid mb-4 max-w-[480px]", children: [_jsxs(Row, { justify: "between", className: "mb-2", children: [_jsx(Heading, { level: 5, children: brandName }), _jsxs(Text, { size: "xs", color: "muted", children: ["#", order.id] })] }), _jsx(Text, { size: "xs", color: "muted", children: new Date(order.createdAt).toLocaleDateString("en-IN") }), order.buyerDisplayName && _jsx(Text, { size: "sm", weight: "medium", children: order.buyerDisplayName }), order.buyerCity && _jsx(Text, { size: "xs", color: "muted", children: order.buyerCity }), order.physicalLocation && (_jsxs(Text, { size: "xs", color: "muted", children: ["Staging: ", order.physicalLocation] })), _jsx(Stack, { className: "mt-3 border-t pt-2", children: order.items.map((item, i) => (
32
+ // audit-variant-ok: py-1 is vertical-only padding; border-b last:border-b-0 uses pseudo-selector — no Row variant covers these
33
+ _jsxs(Row, { justify: "between", className: "py-1 border-b last:border-b-0", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", children: item.productName }), item.barcodeId && _jsx(Text, { size: "xs", color: "muted", children: item.barcodeId })] }), _jsxs(Stack, { className: "text-right", children: [_jsxs(Text, { size: "xs", children: ["\u00D7", item.quantity] }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", (item.price / 100).toLocaleString("en-IN")] })] })] }, i))) })] }));
34
+ }
35
+ export function PrintCenterView({ store, brandName = "LetItRip", initialProducts = [], initialOrders = [], }) {
36
+ const [tab, setTab] = useState("labels");
37
+ const [selectedProducts, setSelectedProducts] = useState(new Set());
38
+ const [selectedOrders, setSelectedOrders] = useState(new Set());
39
+ const toggleProduct = (id) => setSelectedProducts((prev) => {
40
+ const next = new Set(prev);
41
+ next.has(id) ? next.delete(id) : next.add(id);
42
+ return next;
43
+ });
44
+ const toggleOrder = (id) => setSelectedOrders((prev) => {
45
+ const next = new Set(prev);
46
+ next.has(id) ? next.delete(id) : next.add(id);
47
+ return next;
48
+ });
49
+ const printSelected = () => window.print();
50
+ return (_jsxs(Stack, { gap: "md", padding: "md", children: [_jsx("style", { children: `@media print { .no-print { display: none !important; } }` }), _jsxs(Stack, { className: "no-print", children: [_jsxs(Heading, { level: 3, children: ["Print Center", store ? ` — ${store.storeName}` : ""] }), _jsxs(Row, { gap: "xs", className: "mt-3", children: [_jsx(Button, { onClick: () => setTab("labels"), variant: tab === "labels" ? "primary" : "secondary", size: "sm", children: "Product Labels" }), _jsx(Button, { onClick: () => setTab("slips"), variant: tab === "slips" ? "primary" : "secondary", size: "sm", children: "Packing Slips" })] })] }), tab === "labels" && (_jsxs(_Fragment, { children: [_jsx(Stack, { className: "no-print", children: _jsxs(Stack, { gap: "sm", children: [initialProducts.length === 0 ? (_jsx(Text, { color: "muted", children: "No products available." })) : (initialProducts.map((p) => (_jsx(FieldCheckbox, { name: `product-${p.id}`, label: p.name, checked: selectedProducts.has(p.id), onChange: () => toggleProduct(p.id) }, p.id)))), _jsxs(Button, { onClick: printSelected, disabled: selectedProducts.size === 0, children: ["Print Selected (", selectedProducts.size, ")"] })] }) }), _jsx(Row, { wrap: true, gap: "sm", className: "mt-4", children: initialProducts
51
+ .filter((p) => selectedProducts.has(p.id))
52
+ .map((p) => (_jsx(ProductLabel, { product: p }, p.id))) })] })), tab === "slips" && (_jsxs(_Fragment, { children: [_jsx(Stack, { className: "no-print", children: _jsxs(Stack, { gap: "sm", children: [initialOrders.length === 0 ? (_jsx(Text, { color: "muted", children: "No orders available." })) : (initialOrders.map((o) => (_jsx(FieldCheckbox, { name: `order-${o.id}`, label: `#${o.id}${o.buyerDisplayName ? ` — ${o.buyerDisplayName}` : ""}`, checked: selectedOrders.has(o.id), onChange: () => toggleOrder(o.id) }, o.id)))), _jsxs(Button, { onClick: printSelected, disabled: selectedOrders.size === 0, children: ["Print Selected (", selectedOrders.size, ")"] })] }) }), _jsx(Stack, { gap: "sm", children: initialOrders
53
+ .filter((o) => selectedOrders.has(o.id))
54
+ .map((o) => (_jsx(PackingSlip, { order: o, brandName: brandName }, o.id))) })] }))] }));
7
55
  }
@@ -59,6 +59,8 @@ export interface SellerProductDraft {
59
59
  liveHandlingFee?: number;
60
60
  liveJurisdictions?: string[];
61
61
  liveCites?: boolean;
62
+ /** Barcode ID of the physical sticker on this item. Auto-generated on save if blank. */
63
+ barcodeId?: string;
62
64
  }
63
65
  export interface SellerProductShellProps {
64
66
  mode: "create" | "edit";
@@ -9,6 +9,7 @@ import { Alert, Button, Div, FormField, FormGroup, Heading, Section, Stack, Text
9
9
  import { ImageUpload, MediaUploadField, MediaUploadList, useMediaUpload } from "../../media";
10
10
  import { StoreAddressSelectorCreate } from "../../stores/components/StoreAddressSelectorCreate";
11
11
  import { QuickProductForm } from "./QuickProductForm";
12
+ import { BarcodeField } from "./BarcodeField";
12
13
  import { normalizeError } from "../../../errors/normalize";
13
14
  const sellerProductSchema = z.object({
14
15
  title: z.string().min(1, "Product title is required").max(200),
@@ -39,7 +40,7 @@ function toPaise(rupeeStr) {
39
40
  }
40
41
  // ── Step 1: Basic ─────────────────────────────────────────────────────────────
41
42
  function StepBasic({ values, onChange, renderCategorySelector, renderBrandSelector, renderTemplateSelector, }) {
42
- return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(FormField, { name: "tags", label: "Tags (comma-separated)", type: "text", value: (values.tags ?? []).join(", "), onChange: (v) => onChange({ tags: v.split(",").map((t) => t.trim()).filter(Boolean) }), placeholder: "pokemon, psa9, charizard, holo" })] }));
43
+ return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(FormField, { name: "tags", label: "Tags (comma-separated)", type: "text", value: (values.tags ?? []).join(", "), onChange: (v) => onChange({ tags: v.split(",").map((t) => t.trim()).filter(Boolean) }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
43
44
  }
44
45
  // ── Step 2: Media ─────────────────────────────────────────────────────────────
45
46
  function StepMedia({ values, onChange, storeSlug = "store", }) {
@@ -128,3 +128,7 @@ export { SellerDigitalCodesView } from "./SellerDigitalCodesView";
128
128
  export type { SellerDigitalCodesViewProps } from "./SellerDigitalCodesView";
129
129
  export { SellerLiveView } from "./SellerLiveView";
130
130
  export type { SellerLiveViewProps } from "./SellerLiveView";
131
+ export { BarcodeField } from "./BarcodeField";
132
+ export type { BarcodeFieldProps } from "./BarcodeField";
133
+ export { FulfillmentView } from "./FulfillmentView";
134
+ export type { FulfillmentViewProps } from "./FulfillmentView";
@@ -67,3 +67,5 @@ export { SellerBundlesView } from "./SellerBundlesView";
67
67
  export { SellerClassifiedView } from "./SellerClassifiedView";
68
68
  export { SellerDigitalCodesView } from "./SellerDigitalCodesView";
69
69
  export { SellerLiveView } from "./SellerLiveView";
70
+ export { BarcodeField } from "./BarcodeField";
71
+ export { FulfillmentView } from "./FulfillmentView";
@@ -0,0 +1,458 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { makeMockDb, makeSnap, makeQuerySnap } from "../../../../../tests/helpers/mock-firestore";
3
+ const { db, mockDocRef, mockCollection, mockQuery, mockBatch } = makeMockDb();
4
+ vi.mock("../../../../providers/db-firebase/admin", () => ({
5
+ getAdminDb: () => db,
6
+ }));
7
+ vi.mock("../../../../providers/db-firebase", async (importOriginal) => {
8
+ const actual = await importOriginal();
9
+ return {
10
+ ...actual,
11
+ prepareForFirestore: (d) => d,
12
+ };
13
+ });
14
+ vi.mock("../../../../security", () => ({
15
+ encryptPiiFields: (d) => d,
16
+ decryptPiiFields: (d) => d,
17
+ OFFER_PII_FIELDS: ["buyerName", "buyerEmail"],
18
+ }));
19
+ vi.mock("../../../../monitoring", () => ({
20
+ serverLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
21
+ }));
22
+ vi.mock("../../../../errors/normalize", () => ({
23
+ normalizeError: vi.fn(),
24
+ }));
25
+ import { OfferRepository } from "../offer.repository";
26
+ const repo = new OfferRepository();
27
+ function makeOfferDoc(overrides = {}) {
28
+ return {
29
+ id: "offer-product-charizard-user-ravi-20260627-abc123",
30
+ productId: "product-charizard-psa9",
31
+ storeId: "store-pokemon-palace",
32
+ buyerUid: "user-ravi",
33
+ buyerName: "Ravi Kumar",
34
+ buyerEmail: "ravi@test.com",
35
+ offerAmount: 45000, // paise
36
+ status: "pending",
37
+ expiresAt: new Date(Date.now() + 48 * 60 * 60 * 1000),
38
+ createdAt: new Date(),
39
+ updatedAt: new Date(),
40
+ ...overrides,
41
+ };
42
+ }
43
+ beforeEach(() => {
44
+ vi.clearAllMocks();
45
+ db.collection.mockReturnValue(mockCollection);
46
+ mockCollection.doc.mockReturnValue(mockDocRef);
47
+ mockCollection.where.mockReturnValue(mockQuery);
48
+ mockQuery.where.mockReturnValue(mockQuery);
49
+ mockQuery.orderBy.mockReturnValue(mockQuery);
50
+ mockQuery.limit.mockReturnValue(mockQuery);
51
+ mockQuery.select = vi.fn().mockReturnValue(mockQuery);
52
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
53
+ const offerDoc = makeOfferDoc();
54
+ mockDocRef.get.mockResolvedValue(makeSnap(offerDoc, offerDoc.id));
55
+ mockDocRef.set.mockResolvedValue(undefined);
56
+ mockDocRef.update.mockResolvedValue(undefined);
57
+ db.batch.mockReturnValue(mockBatch);
58
+ });
59
+ // ---------------------------------------------------------------------------
60
+ // create
61
+ // ---------------------------------------------------------------------------
62
+ describe("OfferRepository.create", () => {
63
+ it("sets status: pending on creation", async () => {
64
+ await repo.create({
65
+ productId: "product-charizard-psa9",
66
+ storeId: "store-pokemon-palace",
67
+ buyerUid: "user-ravi",
68
+ buyerName: "Ravi Kumar",
69
+ buyerEmail: "ravi@test.com",
70
+ offerAmount: 45000,
71
+ });
72
+ const setArg = mockDocRef.set.mock.calls[0][0];
73
+ expect(setArg.status).toBe("pending");
74
+ });
75
+ it("sets expiresAt to approximately 48 hours from now", async () => {
76
+ const before = Date.now();
77
+ await repo.create({
78
+ productId: "product-1",
79
+ storeId: "store-1",
80
+ buyerUid: "user-1",
81
+ buyerName: "Buyer",
82
+ buyerEmail: "b@test.com",
83
+ offerAmount: 10000,
84
+ });
85
+ const after = Date.now();
86
+ const setArg = mockDocRef.set.mock.calls[0][0];
87
+ const expiresAt = setArg.expiresAt.getTime();
88
+ const expectedMs = 48 * 60 * 60 * 1000;
89
+ expect(expiresAt).toBeGreaterThanOrEqual(before + expectedMs - 100);
90
+ expect(expiresAt).toBeLessThanOrEqual(after + expectedMs + 100);
91
+ });
92
+ it("persists to Firestore via doc.set", async () => {
93
+ await repo.create({
94
+ productId: "p-1",
95
+ storeId: "s-1",
96
+ buyerUid: "u-1",
97
+ buyerName: "Buyer",
98
+ buyerEmail: "b@test.com",
99
+ offerAmount: 20000,
100
+ });
101
+ expect(mockDocRef.set).toHaveBeenCalledOnce();
102
+ });
103
+ it("returns plaintext document (not encrypted) to caller", async () => {
104
+ const result = await repo.create({
105
+ productId: "p-1",
106
+ storeId: "s-1",
107
+ buyerUid: "u-1",
108
+ buyerName: "Ravi Kumar",
109
+ buyerEmail: "ravi@test.com",
110
+ offerAmount: 10000,
111
+ });
112
+ // Passthrough mock means plaintext === encrypted; verify field is accessible
113
+ expect(result.buyerName).toBe("Ravi Kumar");
114
+ });
115
+ it("sets createdAt and updatedAt timestamps", async () => {
116
+ const result = await repo.create({
117
+ productId: "p-1",
118
+ storeId: "s-1",
119
+ buyerUid: "u-1",
120
+ buyerName: "Buyer",
121
+ buyerEmail: "b@test.com",
122
+ offerAmount: 5000,
123
+ });
124
+ expect(result.createdAt).toBeInstanceOf(Date);
125
+ expect(result.updatedAt).toBeInstanceOf(Date);
126
+ });
127
+ });
128
+ // ---------------------------------------------------------------------------
129
+ // hasActiveOffer
130
+ // ---------------------------------------------------------------------------
131
+ describe("OfferRepository.hasActiveOffer", () => {
132
+ it("returns true when a pending offer exists for buyer + product", async () => {
133
+ const offer = makeOfferDoc({ status: "pending" });
134
+ mockQuery.get.mockResolvedValue({
135
+ empty: false,
136
+ size: 1,
137
+ docs: [makeSnap(offer, offer.id)],
138
+ });
139
+ const result = await repo.hasActiveOffer("user-ravi", "product-charizard-psa9");
140
+ expect(result).toBe(true);
141
+ });
142
+ it("returns true when a countered offer exists for buyer + product", async () => {
143
+ const offer = makeOfferDoc({ status: "countered" });
144
+ mockQuery.get.mockResolvedValue({
145
+ empty: false,
146
+ size: 1,
147
+ docs: [makeSnap(offer, offer.id)],
148
+ });
149
+ const result = await repo.hasActiveOffer("user-ravi", "product-charizard-psa9");
150
+ expect(result).toBe(true);
151
+ });
152
+ it("returns false when only expired/declined offers exist", async () => {
153
+ mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
154
+ const result = await repo.hasActiveOffer("user-ravi", "product-1");
155
+ expect(result).toBe(false);
156
+ });
157
+ it("returns false when no offers exist at all", async () => {
158
+ mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
159
+ const result = await repo.hasActiveOffer("user-nobody", "product-1");
160
+ expect(result).toBe(false);
161
+ });
162
+ it("queries by buyerUid, productId, and status in [pending, countered]", async () => {
163
+ mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
164
+ await repo.hasActiveOffer("user-xyz", "product-abc");
165
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("buyerUid"), "==", "user-xyz");
166
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("productId"), "==", "product-abc");
167
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("status"), "in", ["pending", "countered"]);
168
+ });
169
+ it("uses select() stub read for minimal Firestore cost", async () => {
170
+ mockQuery.get.mockResolvedValue({ empty: false, size: 1, docs: [] });
171
+ await repo.hasActiveOffer("user-ravi", "product-1");
172
+ expect(mockQuery.select).toHaveBeenCalled();
173
+ });
174
+ it("uses limit(1) to avoid scanning all offers", async () => {
175
+ mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
176
+ await repo.hasActiveOffer("user-ravi", "product-1");
177
+ expect(mockQuery.limit).toHaveBeenCalledWith(1);
178
+ });
179
+ });
180
+ // ---------------------------------------------------------------------------
181
+ // countByBuyerAndProduct
182
+ // ---------------------------------------------------------------------------
183
+ describe("OfferRepository.countByBuyerAndProduct", () => {
184
+ it("returns 0 when no offers match", async () => {
185
+ mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
186
+ const count = await repo.countByBuyerAndProduct("user-ravi", "product-1", new Date(Date.now() - 7 * 24 * 60 * 60 * 1000));
187
+ expect(count).toBe(0);
188
+ });
189
+ it("returns correct count when offers exist", async () => {
190
+ mockQuery.get.mockResolvedValue({ empty: false, size: 3, docs: [] });
191
+ const count = await repo.countByBuyerAndProduct("user-ravi", "product-1", new Date());
192
+ expect(count).toBe(3);
193
+ });
194
+ it("queries by buyerUid, productId, and createdAt >= since", async () => {
195
+ mockQuery.get.mockResolvedValue({ empty: false, size: 2, docs: [] });
196
+ const since = new Date(Date.now() - 86400000);
197
+ await repo.countByBuyerAndProduct("user-abc", "product-xyz", since);
198
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("buyerUid"), "==", "user-abc");
199
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("productId"), "==", "product-xyz");
200
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("createdAt"), ">=", since);
201
+ });
202
+ it("uses select() stub read for minimal Firestore cost", async () => {
203
+ mockQuery.get.mockResolvedValue({ empty: true, size: 0, docs: [] });
204
+ await repo.countByBuyerAndProduct("user-1", "product-1", new Date());
205
+ expect(mockQuery.select).toHaveBeenCalled();
206
+ });
207
+ it("returns snap.size (not docs.length)", async () => {
208
+ // size may differ from docs.length in real Firestore (SELECT stub reads)
209
+ mockQuery.get.mockResolvedValue({ empty: false, size: 5, docs: [] });
210
+ const count = await repo.countByBuyerAndProduct("user-1", "product-1", new Date());
211
+ expect(count).toBe(5);
212
+ });
213
+ });
214
+ // ---------------------------------------------------------------------------
215
+ // findExpiredActive
216
+ // ---------------------------------------------------------------------------
217
+ describe("OfferRepository.findExpiredActive", () => {
218
+ it("returns offers with status in [pending, countered] and expiresAt <= now", async () => {
219
+ const now = new Date();
220
+ const expiredOffer = makeOfferDoc({
221
+ status: "pending",
222
+ expiresAt: new Date(now.getTime() - 3600000),
223
+ });
224
+ mockQuery.get.mockResolvedValue(makeQuerySnap([{ id: expiredOffer.id, data: expiredOffer }]));
225
+ const results = await repo.findExpiredActive(now);
226
+ expect(results).toHaveLength(1);
227
+ });
228
+ it("queries status in [pending, countered]", async () => {
229
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
230
+ await repo.findExpiredActive(new Date());
231
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("status"), "in", ["pending", "countered"]);
232
+ });
233
+ it("queries expiresAt <= now", async () => {
234
+ const now = new Date();
235
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
236
+ await repo.findExpiredActive(now);
237
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("expiresAt"), "<=", now);
238
+ });
239
+ it("applies limit(500) to avoid unbounded scans", async () => {
240
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
241
+ await repo.findExpiredActive(new Date());
242
+ expect(mockQuery.limit).toHaveBeenCalledWith(500);
243
+ });
244
+ it("returns empty array when no expired active offers", async () => {
245
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
246
+ const results = await repo.findExpiredActive(new Date());
247
+ expect(results).toHaveLength(0);
248
+ });
249
+ });
250
+ // ---------------------------------------------------------------------------
251
+ // expireMany
252
+ // ---------------------------------------------------------------------------
253
+ describe("OfferRepository.expireMany", () => {
254
+ it("batch-updates each offer ID to status: expired", async () => {
255
+ await repo.expireMany(["offer-1", "offer-2", "offer-3"]);
256
+ expect(mockBatch.update).toHaveBeenCalledTimes(3);
257
+ const calls = mockBatch.update.mock.calls;
258
+ expect(calls.every((c) => c[1].status === "expired")).toBe(true);
259
+ });
260
+ it("sets updatedAt on every expired offer", async () => {
261
+ await repo.expireMany(["offer-1", "offer-2"]);
262
+ const calls = mockBatch.update.mock.calls;
263
+ expect(calls.every((c) => c[1].updatedAt instanceof Date)).toBe(true);
264
+ });
265
+ it("commits the batch exactly once", async () => {
266
+ await repo.expireMany(["offer-a", "offer-b"]);
267
+ expect(mockBatch.commit).toHaveBeenCalledOnce();
268
+ });
269
+ it("empty ID list → batch.commit called, no updates staged", async () => {
270
+ await repo.expireMany([]);
271
+ expect(mockBatch.update).not.toHaveBeenCalled();
272
+ expect(mockBatch.commit).toHaveBeenCalledOnce();
273
+ });
274
+ it("single offer → one batch.update call", async () => {
275
+ await repo.expireMany(["offer-only"]);
276
+ expect(mockBatch.update).toHaveBeenCalledTimes(1);
277
+ });
278
+ });
279
+ // ---------------------------------------------------------------------------
280
+ // accept
281
+ // ---------------------------------------------------------------------------
282
+ describe("OfferRepository.accept", () => {
283
+ it("sets status: accepted", async () => {
284
+ await repo.accept("offer-1", 40000);
285
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "accepted" }));
286
+ });
287
+ it("stores lockedPrice on accept", async () => {
288
+ await repo.accept("offer-1", 40000);
289
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ lockedPrice: 40000 }));
290
+ });
291
+ it("sets acceptedAt timestamp", async () => {
292
+ await repo.accept("offer-1", 40000);
293
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ acceptedAt: expect.any(Date) }));
294
+ });
295
+ it("sets respondedAt timestamp", async () => {
296
+ await repo.accept("offer-1", 40000);
297
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ respondedAt: expect.any(Date) }));
298
+ });
299
+ it("stores sellerNote when provided", async () => {
300
+ await repo.accept("offer-1", 40000, "Deal accepted!");
301
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ sellerNote: "Deal accepted!" }));
302
+ });
303
+ it("sets updatedAt timestamp", async () => {
304
+ await repo.accept("offer-1", 40000);
305
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ updatedAt: expect.any(Date) }));
306
+ });
307
+ });
308
+ // ---------------------------------------------------------------------------
309
+ // decline
310
+ // ---------------------------------------------------------------------------
311
+ describe("OfferRepository.decline", () => {
312
+ it("sets status: declined", async () => {
313
+ await repo.decline("offer-1");
314
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "declined" }));
315
+ });
316
+ it("sets respondedAt timestamp", async () => {
317
+ await repo.decline("offer-1");
318
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ respondedAt: expect.any(Date) }));
319
+ });
320
+ it("stores sellerNote when provided", async () => {
321
+ await repo.decline("offer-1", "Not interested");
322
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ sellerNote: "Not interested" }));
323
+ });
324
+ it("does NOT set lockedPrice on decline", async () => {
325
+ await repo.decline("offer-1");
326
+ const updateArg = mockDocRef.update.mock.calls[0][0];
327
+ expect(Object.keys(updateArg)).not.toContain("lockedPrice");
328
+ });
329
+ });
330
+ // ---------------------------------------------------------------------------
331
+ // counter
332
+ // ---------------------------------------------------------------------------
333
+ describe("OfferRepository.counter", () => {
334
+ it("sets status: countered", async () => {
335
+ await repo.counter("offer-1", 38000);
336
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "countered" }));
337
+ });
338
+ it("stores counterAmount", async () => {
339
+ await repo.counter("offer-1", 38000);
340
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ counterAmount: 38000 }));
341
+ });
342
+ it("sets respondedAt timestamp", async () => {
343
+ await repo.counter("offer-1", 38000);
344
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ respondedAt: expect.any(Date) }));
345
+ });
346
+ it("stores sellerNote when provided", async () => {
347
+ await repo.counter("offer-1", 38000, "Best I can do");
348
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ sellerNote: "Best I can do" }));
349
+ });
350
+ });
351
+ // ---------------------------------------------------------------------------
352
+ // acceptCounter
353
+ // ---------------------------------------------------------------------------
354
+ describe("OfferRepository.acceptCounter", () => {
355
+ it("sets lockedPrice = counterAmount from existing offer", async () => {
356
+ const offerDoc = makeOfferDoc({ status: "countered", counterAmount: 38000 });
357
+ mockDocRef.get.mockResolvedValue(makeSnap(offerDoc, "offer-1"));
358
+ await repo.acceptCounter("offer-1");
359
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ lockedPrice: 38000 }));
360
+ });
361
+ it("sets status: accepted", async () => {
362
+ const offerDoc = makeOfferDoc({ status: "countered", counterAmount: 38000 });
363
+ mockDocRef.get.mockResolvedValue(makeSnap(offerDoc, "offer-1"));
364
+ await repo.acceptCounter("offer-1");
365
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "accepted" }));
366
+ });
367
+ it("sets acceptedAt and respondedAt", async () => {
368
+ const offerDoc = makeOfferDoc({ status: "countered", counterAmount: 38000 });
369
+ mockDocRef.get.mockResolvedValue(makeSnap(offerDoc, "offer-1"));
370
+ await repo.acceptCounter("offer-1");
371
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({
372
+ acceptedAt: expect.any(Date),
373
+ respondedAt: expect.any(Date),
374
+ }));
375
+ });
376
+ it("throws when offer not found", async () => {
377
+ mockDocRef.get.mockResolvedValue(makeSnap(null));
378
+ await expect(repo.acceptCounter("offer-ghost")).rejects.toThrow(/offer.*not found|counter.*not found/i);
379
+ });
380
+ it("throws when offer has no counterAmount", async () => {
381
+ const offerDoc = makeOfferDoc({ status: "pending", counterAmount: undefined });
382
+ mockDocRef.get.mockResolvedValue(makeSnap(offerDoc, "offer-1"));
383
+ await expect(repo.acceptCounter("offer-1")).rejects.toThrow();
384
+ });
385
+ });
386
+ // ---------------------------------------------------------------------------
387
+ // withdraw
388
+ // ---------------------------------------------------------------------------
389
+ describe("OfferRepository.withdraw", () => {
390
+ it("sets status: withdrawn", async () => {
391
+ await repo.withdraw("offer-1");
392
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ status: "withdrawn" }));
393
+ });
394
+ it("sets respondedAt timestamp", async () => {
395
+ await repo.withdraw("offer-1");
396
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ respondedAt: expect.any(Date) }));
397
+ });
398
+ it("sets updatedAt timestamp", async () => {
399
+ await repo.withdraw("offer-1");
400
+ expect(mockDocRef.update).toHaveBeenCalledWith(expect.objectContaining({ updatedAt: expect.any(Date) }));
401
+ });
402
+ it("does NOT set lockedPrice on withdraw", async () => {
403
+ await repo.withdraw("offer-1");
404
+ const updateArg = mockDocRef.update.mock.calls[0][0];
405
+ expect(Object.keys(updateArg)).not.toContain("lockedPrice");
406
+ });
407
+ });
408
+ // ---------------------------------------------------------------------------
409
+ // findPendingByStore
410
+ // ---------------------------------------------------------------------------
411
+ describe("OfferRepository.findPendingByStore", () => {
412
+ it("returns only pending offers for the given storeId", async () => {
413
+ const offer = makeOfferDoc({ status: "pending", storeId: "store-pokemon-palace" });
414
+ mockQuery.get.mockResolvedValue(makeQuerySnap([{ id: offer.id, data: offer }]));
415
+ const results = await repo.findPendingByStore("store-pokemon-palace");
416
+ expect(results).toHaveLength(1);
417
+ });
418
+ it("queries by storeId and status == pending", async () => {
419
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
420
+ await repo.findPendingByStore("store-xyz");
421
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("storeId"), "==", "store-xyz");
422
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("status"), "==", "pending");
423
+ });
424
+ it("orders by createdAt ascending (oldest first for seller to act on)", async () => {
425
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
426
+ await repo.findPendingByStore("store-1");
427
+ expect(mockQuery.orderBy).toHaveBeenCalledWith(expect.stringContaining("createdAt"), "asc");
428
+ });
429
+ it("returns empty array when no pending offers", async () => {
430
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
431
+ const results = await repo.findPendingByStore("store-empty");
432
+ expect(results).toHaveLength(0);
433
+ });
434
+ });
435
+ // ---------------------------------------------------------------------------
436
+ // findExpired
437
+ // ---------------------------------------------------------------------------
438
+ describe("OfferRepository.findExpired", () => {
439
+ it("returns offers with status in [pending, countered] and expiresAt <= now", async () => {
440
+ const expired = makeOfferDoc({
441
+ status: "pending",
442
+ expiresAt: new Date(Date.now() - 1000),
443
+ });
444
+ mockQuery.get.mockResolvedValue(makeQuerySnap([{ id: expired.id, data: expired }]));
445
+ const results = await repo.findExpired();
446
+ expect(results).toHaveLength(1);
447
+ });
448
+ it("queries status in [pending, countered]", async () => {
449
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
450
+ await repo.findExpired();
451
+ expect(mockCollection.where).toHaveBeenCalledWith(expect.stringContaining("status"), "in", ["pending", "countered"]);
452
+ });
453
+ it("queries expiresAt <= current time", async () => {
454
+ mockQuery.get.mockResolvedValue(makeQuerySnap([]));
455
+ await repo.findExpired();
456
+ expect(mockQuery.where).toHaveBeenCalledWith(expect.stringContaining("expiresAt"), "<=", expect.any(Date));
457
+ });
458
+ });
@@ -87,8 +87,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
87
87
  }, "strip", z.ZodTypeAny, {
88
88
  status: "pending" | "active" | "rejected" | "suspended";
89
89
  createdAt: string;
90
- id: string;
91
90
  storeName: string;
91
+ id: string;
92
92
  updatedAt: string;
93
93
  isPublic: boolean;
94
94
  storeSlug: string;
@@ -119,8 +119,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
119
119
  }, {
120
120
  status: "pending" | "active" | "rejected" | "suspended";
121
121
  createdAt: string;
122
- id: string;
123
122
  storeName: string;
123
+ id: string;
124
124
  updatedAt: string;
125
125
  isPublic: boolean;
126
126
  storeSlug: string;