@mohasinac/appkit 4.1.3 → 4.3.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 (276) hide show
  1. package/dist/_internal/server/features/auth/actions.d.ts +0 -1
  2. package/dist/_internal/server/features/auth/actions.js +0 -1
  3. package/dist/_internal/server/features/bundles/data.d.ts +22 -4
  4. package/dist/_internal/server/features/bundles/data.js +41 -4
  5. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  6. package/dist/_internal/server/features/bundles/index.js +1 -1
  7. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  8. package/dist/_internal/server/features/checkout/actions.js +249 -257
  9. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
  10. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
  11. package/dist/_internal/server/features/orders/adapters.js +1 -14
  12. package/dist/_internal/server/features/products/actions.js +8 -3
  13. package/dist/_internal/server/features/products/service.d.ts +15 -0
  14. package/dist/_internal/server/features/products/service.js +41 -0
  15. package/dist/_internal/server/functions/firestore.d.ts +3 -1
  16. package/dist/_internal/server/functions/firestore.js +17 -1
  17. package/dist/_internal/server/functions/scheduled.d.ts +3 -5
  18. package/dist/_internal/server/functions/scheduled.js +14 -30
  19. package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
  20. package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
  21. package/dist/_internal/server/jobs/core/index.d.ts +4 -4
  22. package/dist/_internal/server/jobs/core/index.js +4 -4
  23. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  24. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  25. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  26. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  27. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  28. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
  29. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
  30. package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
  31. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +3 -2
  32. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  33. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
  34. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
  35. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
  36. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
  37. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
  38. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
  39. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  40. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  41. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  42. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  43. package/dist/_internal/server/jobs/handlers/index.d.ts +4 -4
  44. package/dist/_internal/server/jobs/handlers/index.js +9 -6
  45. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
  46. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
  47. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
  48. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
  49. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
  50. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
  51. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  52. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  53. package/dist/_internal/shared/actions/action-registry.js +40 -10
  54. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
  55. package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
  56. package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
  57. package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
  58. package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
  59. package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
  60. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  61. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  62. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
  63. package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
  64. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
  65. package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
  66. package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
  67. package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
  68. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  69. package/dist/_internal/shared/features/checkout/config.js +7 -0
  70. package/dist/_internal/shared/fees/calculator.d.ts +26 -0
  71. package/dist/_internal/shared/fees/calculator.js +25 -0
  72. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  73. package/dist/constants/api-endpoints.d.ts +6 -9
  74. package/dist/constants/api-endpoints.js +5 -5
  75. package/dist/constants/field-names.d.ts +17 -1
  76. package/dist/constants/field-names.js +17 -1
  77. package/dist/features/about/components/FeesView.js +18 -0
  78. package/dist/features/account/hooks/useNotifications.js +5 -0
  79. package/dist/features/admin/actions/admin-actions.js +6 -0
  80. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  81. package/dist/features/admin/actions/notification-actions.js +57 -12
  82. package/dist/features/admin/components/AdminAdsView.js +1 -1
  83. package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
  84. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  85. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  86. package/dist/features/admin/components/AdminSiteSettingsView.js +115 -11
  87. package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
  88. package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
  89. package/dist/features/admin/hooks/useAdminListing.js +4 -2
  90. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  91. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  92. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  93. package/dist/features/admin/schemas/firestore.d.ts +54 -5
  94. package/dist/features/admin/schemas/firestore.js +63 -26
  95. package/dist/features/auctions/actions/bid-actions.js +3 -1
  96. package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
  97. package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
  98. package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
  99. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
  100. package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
  101. package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
  102. package/dist/features/auctions/components/LiveBidPrice.js +17 -0
  103. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  104. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  105. package/dist/features/auctions/hooks/useBids.d.ts +33 -0
  106. package/dist/features/auctions/hooks/useBids.js +32 -0
  107. package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
  108. package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
  109. package/dist/features/auth/checkout-value-otp.d.ts +7 -10
  110. package/dist/features/auth/checkout-value-otp.js +14 -10
  111. package/dist/features/auth/server.d.ts +0 -1
  112. package/dist/features/auth/server.js +0 -1
  113. package/dist/features/blog/api/[slug]/route.d.ts +5 -0
  114. package/dist/features/blog/api/[slug]/route.js +12 -3
  115. package/dist/features/blog/components/BlogPostView.d.ts +2 -0
  116. package/dist/features/blog/components/BlogPostView.js +5 -4
  117. package/dist/features/blog/hooks/useBlog.d.ts +2 -0
  118. package/dist/features/blog/hooks/useBlog.js +2 -0
  119. package/dist/features/blog/repository/blog.repository.d.ts +7 -0
  120. package/dist/features/blog/repository/blog.repository.js +44 -0
  121. package/dist/features/blog/schemas/firestore.d.ts +3 -0
  122. package/dist/features/blog/schemas/firestore.js +1 -0
  123. package/dist/features/blog/types/index.d.ts +2 -0
  124. package/dist/features/cart/components/index.d.ts +0 -2
  125. package/dist/features/cart/components/index.js +0 -1
  126. package/dist/features/cart/hooks/useCartCount.js +22 -5
  127. package/dist/features/cart/hooks/useGuestCart.js +11 -1
  128. package/dist/features/cart/utils/guest-cart.d.ts +4 -0
  129. package/dist/features/cart/utils/guest-cart.js +10 -0
  130. package/dist/features/cart/utils/pending-ops.d.ts +17 -0
  131. package/dist/features/cart/utils/pending-ops.js +46 -5
  132. package/dist/features/categories/components/BrandDetailPageView.js +6 -2
  133. package/dist/features/categories/components/BundleDetailView.js +4 -2
  134. package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
  135. package/dist/features/categories/schemas/firestore.d.ts +7 -0
  136. package/dist/features/checkout/actions/index.d.ts +0 -1
  137. package/dist/features/checkout/actions/index.js +0 -1
  138. package/dist/features/checkout/schemas/firestore.d.ts +2 -2
  139. package/dist/features/events/actions/event-actions.d.ts +2 -0
  140. package/dist/features/events/actions/event-actions.js +5 -0
  141. package/dist/features/events/components/EventCard.js +1 -2
  142. package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
  143. package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
  144. package/dist/features/events/components/index.d.ts +2 -0
  145. package/dist/features/events/components/index.js +1 -0
  146. package/dist/features/events/repository/events.repository.d.ts +6 -0
  147. package/dist/features/events/repository/events.repository.js +21 -0
  148. package/dist/features/history/repository/user-history.repository.d.ts +1 -1
  149. package/dist/features/history/utils/guest-history.d.ts +1 -1
  150. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
  151. package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
  152. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  153. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  154. package/dist/features/layout/AppLayoutShell.js +1 -1
  155. package/dist/features/layout/BackToTop.d.ts +9 -5
  156. package/dist/features/layout/BackToTop.js +17 -21
  157. package/dist/features/layout/NavbarLayout.js +1 -1
  158. package/dist/features/layout/TitleBarLayout.js +2 -2
  159. package/dist/features/media/MediaVideo.d.ts +7 -0
  160. package/dist/features/media/MediaVideo.js +36 -5
  161. package/dist/features/orders/actions/refund-actions.js +1 -0
  162. package/dist/features/orders/components/OrdersList.js +1 -5
  163. package/dist/features/orders/schemas/firestore.d.ts +16 -8
  164. package/dist/features/orders/schemas/index.d.ts +3 -32
  165. package/dist/features/orders/schemas/index.js +1 -4
  166. package/dist/features/orders/types/index.d.ts +6 -9
  167. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  168. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
  169. package/dist/features/products/actions/product-actions.js +13 -10
  170. package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
  171. package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
  172. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  173. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  174. package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
  175. package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
  176. package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
  177. package/dist/features/products/components/PrizeRevealModal.js +18 -147
  178. package/dist/features/products/components/ProductDetailPageView.js +72 -30
  179. package/dist/features/products/components/ProductForm.js +7 -14
  180. package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
  181. package/dist/features/products/components/ProductGalleryClient.js +29 -10
  182. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  183. package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
  184. package/dist/features/products/components/ShowGroupSection.js +2 -2
  185. package/dist/features/products/components/SublistingCarouselSection.js +1 -1
  186. package/dist/features/products/components/index.d.ts +4 -2
  187. package/dist/features/products/components/index.js +1 -0
  188. package/dist/features/products/constants/action-defs.d.ts +28 -4
  189. package/dist/features/products/constants/action-defs.js +76 -3
  190. package/dist/features/products/repository/products.repository.d.ts +6 -0
  191. package/dist/features/products/repository/products.repository.js +20 -1
  192. package/dist/features/products/schemas/firestore.d.ts +26 -2
  193. package/dist/features/products/schemas/index.d.ts +11 -3
  194. package/dist/features/products/schemas/index.js +10 -2
  195. package/dist/features/products/types/index.d.ts +2 -1
  196. package/dist/features/reviews/actions/review-actions.js +1 -0
  197. package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
  198. package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
  199. package/dist/features/reviews/components/ReviewsList.js +2 -1
  200. package/dist/features/reviews/schemas/firestore.d.ts +3 -0
  201. package/dist/features/reviews/schemas/index.d.ts +6 -0
  202. package/dist/features/reviews/schemas/index.js +2 -0
  203. package/dist/features/reviews/types/index.d.ts +3 -0
  204. package/dist/features/scams/actions/scam-actions.d.ts +3 -0
  205. package/dist/features/scams/actions/scam-actions.js +3 -2
  206. package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
  207. package/dist/features/scams/components/ScamProfileView.js +2 -2
  208. package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
  209. package/dist/features/scams/repository/scammer.repository.js +24 -0
  210. package/dist/features/seller/actions/seller-actions.js +8 -0
  211. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  212. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
  213. package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
  214. package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
  215. package/dist/features/stores/components/StoreNavTabs.js +21 -9
  216. package/dist/features/tester/actions/index.d.ts +1 -0
  217. package/dist/features/tester/actions/index.js +1 -0
  218. package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
  219. package/dist/features/tester/actions/leaderboard-actions.js +5 -0
  220. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
  221. package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
  222. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
  223. package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
  224. package/dist/features/tester/components/index.d.ts +2 -0
  225. package/dist/features/tester/components/index.js +1 -0
  226. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
  227. package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
  228. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
  229. package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
  230. package/dist/features/tester/schemas/firestore.d.ts +22 -1
  231. package/dist/features/tester/schemas/firestore.js +8 -0
  232. package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
  233. package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
  234. package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
  235. package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
  236. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
  237. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  238. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  239. package/dist/features/whatsapp-bot/types/index.d.ts +19 -0
  240. package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
  241. package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
  242. package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
  243. package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
  244. package/dist/index.d.ts +23 -24
  245. package/dist/index.js +29 -60
  246. package/dist/jobs.d.ts +1 -1
  247. package/dist/jobs.js +2 -2
  248. package/dist/next/routing/route-map.d.ts +4 -0
  249. package/dist/next/routing/route-map.js +2 -0
  250. package/dist/repositories/index.d.ts +2 -0
  251. package/dist/repositories/index.js +1 -0
  252. package/dist/schemas/registry.d.ts +6 -26
  253. package/dist/seed/bids-seed-data.js +14 -2
  254. package/dist/seed/blog-posts-seed-data.js +2 -1
  255. package/dist/seed/categories-seed-data.js +11 -2
  256. package/dist/seed/events-seed-data.js +11 -11
  257. package/dist/seed/faq-seed-data.js +5 -1
  258. package/dist/seed/products-auctions-seed-data.js +2 -2
  259. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  260. package/dist/seed/products-prize-draws-seed-data.js +56 -10
  261. package/dist/seed/products-standard-seed-data.js +82 -3
  262. package/dist/seed/scammers-seed-data.js +75 -2
  263. package/dist/seed/site-settings-seed-data.js +12 -2
  264. package/dist/server-entry.d.ts +0 -1
  265. package/dist/server-entry.js +0 -1
  266. package/dist/server.d.ts +6 -22
  267. package/dist/server.js +12 -59
  268. package/dist/styles.css +1 -1
  269. package/dist/tailwind-utilities.css +1 -1
  270. package/dist/ui/components/BaseListingCard.js +1 -1
  271. package/dist/ui/components/ImageLightbox.d.ts +9 -0
  272. package/dist/ui/components/ImageLightbox.js +6 -3
  273. package/dist/ui/components/ListingToolbar.d.ts +18 -1
  274. package/dist/ui/components/ListingToolbar.js +5 -2
  275. package/dist/ui/components/StickyToolbar.js +13 -8
  276. package/package.json +1 -1
@@ -75,7 +75,11 @@ export const PRODUCT_FIELDS = {
75
75
  PRIZE_REVEAL_STATUS: "prizeRevealStatus",
76
76
  PRIZE_REVEAL_WINDOW_START: "prizeRevealWindowStart",
77
77
  PRIZE_REVEAL_WINDOW_END: "prizeRevealWindowEnd",
78
- PRIZE_REVEAL_DEADLINE: "prizeRevealDeadline",
78
+ PRIZE_DRAW_DURATION_DAYS: "prizeDrawDurationDays",
79
+ PRIZE_REVEAL_MODE: "prizeRevealMode",
80
+ PRIZE_CURRENT_ENTRIES: "prizeCurrentEntries",
81
+ PRIZE_MAX_ENTRIES: "prizeMaxEntries",
82
+ PRIZE_DRAW_MODE: "prizeDrawMode",
79
83
  IS_PART_OF_BUNDLE: "isPartOfBundle",
80
84
  SEARCH_TOKENS: "searchTokens",
81
85
  CREATED_AT: "createdAt",
@@ -116,6 +120,14 @@ export const PRODUCT_FIELDS = {
116
120
  OPEN: "open",
117
121
  CLOSED: "closed",
118
122
  },
123
+ PRIZE_REVEAL_MODE_VALUES: {
124
+ INSTANT: "instant",
125
+ SCHEDULED: "scheduled",
126
+ },
127
+ PRIZE_DRAW_MODE_VALUES: {
128
+ REVEAL: "reveal",
129
+ LOTTERY: "lottery",
130
+ },
119
131
  IS_ACTIVE: "isActive",
120
132
  DISPLAY_ORDER: "displayOrder",
121
133
  LISTING_TYPE_VALUES: {
@@ -185,6 +197,9 @@ export const ORDER_FIELDS = {
185
197
  PAYMENT_PROOF_URL: "paymentProofUrl",
186
198
  PAYMENT_PROOF_UPLOADED_AT: "paymentProofUploadedAt",
187
199
  PAYMENT_REVIEW_OUTCOME: "paymentReviewOutcome",
200
+ PRIZE_WON: "prizeWon",
201
+ PRIZE_DRAW_PRODUCT_ID: "prizeDrawProductId",
202
+ PRIZE_REVEAL_MODE: "prizeRevealMode",
188
203
  STATUS_VALUES: {
189
204
  PENDING: "pending",
190
205
  CONFIRMED: "confirmed",
@@ -466,6 +481,7 @@ export const CATEGORY_FIELDS = {
466
481
  BUNDLE_ITEM_DETAILS: "bundleItemDetails",
467
482
  BUNDLE_STOCK_STATUS: "bundleStockStatus",
468
483
  BUNDLE_QUERY_RESOLVED_AT: "bundleQueryResolvedAt",
484
+ BUNDLE_ORIGINAL_TOTAL: "bundleOriginalTotal",
469
485
  DISPLAY: "display",
470
486
  VIEW_COUNT: "viewCount",
471
487
  CREATED_AT: "createdAt",
@@ -65,6 +65,24 @@ export async function FeesView({} = {}) {
65
65
  who: t("paidByBuyer"),
66
66
  note: t("codHandlingFeeNote"),
67
67
  },
68
+ {
69
+ category: t("whatsappNotifyFeeTitle"),
70
+ rate: t("whatsappNotifyFeeRate"),
71
+ who: t("paidByBuyer"),
72
+ note: t("whatsappNotifyFeeNote"),
73
+ },
74
+ {
75
+ category: t("giftWrapFeeTitle"),
76
+ rate: t("giftWrapFeeRate"),
77
+ who: t("paidByBuyer"),
78
+ note: t("giftWrapFeeNote"),
79
+ },
80
+ {
81
+ category: t("shipmentProtectionFeeTitle"),
82
+ rate: t("shipmentProtectionFeeRate"),
83
+ who: t("paidByBuyer"),
84
+ note: t("shipmentProtectionFeeNote"),
85
+ },
68
86
  ];
69
87
  const OFFER_PAYOUT_ROWS = [
70
88
  { label: t("grossSale"), example: "₹1,000" },
@@ -9,6 +9,11 @@ export function useNotifications(limit = 10, options) {
9
9
  queryKey: ["notifications", "list", String(limit)],
10
10
  queryFn: () => apiClient.get(`${notificationsEndpoint}?limit=${limit}`),
11
11
  staleTime: 30000,
12
+ // Notifications originate server-side (order/bid/moderation events) with
13
+ // no client-driven local-first path to short-circuit, unlike cart/
14
+ // wishlist. Poll on the same ~30s cadence as useSyncManager so the bell
15
+ // badge picks up new notifications without requiring a window refocus.
16
+ refetchInterval: 30000,
12
17
  });
13
18
  const { mutate: markRead } = useMutation({
14
19
  mutationFn: (id) => apiClient.patch(readEndpoint(id), {}),
@@ -14,6 +14,7 @@ import { productRepository } from "../../products/repository/products.repository
14
14
  import { NotFoundError, ValidationError } from "../../../errors";
15
15
  import { finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, } from "../../media/finalize";
16
16
  import { getProviders } from "../../../contracts";
17
+ import { assertPrizeDrawNotLocked, assertPrizeDrawWonItemsImmutable, } from "../../../_internal/server/features/products/service";
17
18
  const ERR_UID_REQUIRED = "uid is required";
18
19
  export async function revokeSession(adminId, sessionId) {
19
20
  if (!sessionId?.trim()) {
@@ -153,6 +154,10 @@ export async function adminUpdateProduct(adminId, id, input) {
153
154
  if (!existing) {
154
155
  throw new NotFoundError("Product not found");
155
156
  }
157
+ if (input.status && input.status !== "published" && existing.status === "published") {
158
+ assertPrizeDrawNotLocked(existing, "unpublished or archived");
159
+ }
160
+ assertPrizeDrawWonItemsImmutable(existing, input.prizeDrawItems);
156
161
  const finalized = { ...input };
157
162
  if (typeof finalized.mainImage === "string" && finalized.mainImage) {
158
163
  finalized.mainImage = await finalizeStagedMediaUrl(finalized.mainImage);
@@ -194,6 +199,7 @@ export async function adminDeleteProduct(adminId, id) {
194
199
  if (!existing) {
195
200
  throw new NotFoundError("Product not found");
196
201
  }
202
+ assertPrizeDrawNotLocked(existing, "deleted");
197
203
  await productRepository.delete(id);
198
204
  serverLogger.info("adminDeleteProduct", {
199
205
  adminId,
@@ -14,11 +14,14 @@ export interface SendNotificationInput extends NotificationCreateInput {
14
14
  userPhone?: string;
15
15
  /** Pre-rendered email HTML — falls back to a plain-text <p> when absent. */
16
16
  emailHtml?: string;
17
+ /** Buyer paid the ₹10 WhatsApp order-updates addon — enables WhatsApp channel for this notification even when the user has no standing WhatsApp subscription. */
18
+ orderWhatsappAddonPaid?: boolean;
17
19
  }
18
20
  export interface SendNotificationResult {
19
21
  notification: NotificationDocument;
20
22
  email: "sent" | "skipped" | "failed";
21
- whatsapp: "sent" | "skipped" | "failed";
23
+ /** WhatsApp dispatch is now async (enqueued as a `whatsappNotify` job) — "queued" replaces "sent" as the immediate outcome; the real delivery result lands on `notification.whatsappStatus`. */
24
+ whatsapp: "queued" | "skipped" | "failed";
22
25
  }
23
26
  /**
24
27
  * Create an in-app notification and fan out to enabled external channels.
@@ -3,10 +3,39 @@ import { notificationRepository } from "../repository/notification.repository";
3
3
  import { siteSettingsRepository } from "../repository/site-settings.repository";
4
4
  import { userRepository } from "../../auth/repository/user.repository";
5
5
  import { createResendProvider } from "../../../providers/email-resend/provider";
6
- import { sendWhatsAppBusinessMessage } from "../../whatsapp-bot/helpers/whatsapp";
6
+ import { enqueueJob } from "../../jobs/actions/enqueue-job";
7
7
  import { serverLogger } from "../../../monitoring";
8
8
  import { decryptPii } from "../../../security/index";
9
9
  import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore";
10
+ /**
11
+ * Notification types whose WhatsApp send is gated behind the buyer's
12
+ * per-order ₹10 WhatsApp-updates addon (`OrderDocument.whatsappNotifyAddon`)
13
+ * — see `orderWhatsappAddonPaid` on `SendNotificationInput`. Every other
14
+ * type (bid_won, offers, scam reports, support tickets, admin alerts, …)
15
+ * keeps the original two-gate behavior (admin channel enabled + user hasn't
16
+ * opted out) unchanged.
17
+ */
18
+ const ORDER_GATED_TYPES = new Set([
19
+ "order_placed",
20
+ "order_confirmed",
21
+ "order_shipped",
22
+ "order_delivered",
23
+ "order_cancelled",
24
+ "refund_initiated",
25
+ ]);
26
+ /** Notification type → the flat `SiteSettingsCredentials` field holding its approved Meta template name. */
27
+ const WHATSAPP_TEMPLATE_FIELD = {
28
+ order_placed: "whatsappTemplateOrderPlaced",
29
+ order_confirmed: "whatsappTemplateOrderConfirmed",
30
+ order_shipped: "whatsappTemplateOrderShipped",
31
+ order_delivered: "whatsappTemplateOrderDelivered",
32
+ order_cancelled: "whatsappTemplateOrderCancelled",
33
+ refund_initiated: "whatsappTemplateRefundInitiated",
34
+ };
35
+ function resolveWhatsAppTemplateName(type, creds) {
36
+ const field = WHATSAPP_TEMPLATE_FIELD[type];
37
+ return (field ? creds[field] : undefined)?.trim() ?? "";
38
+ }
10
39
  export async function markNotificationRead(id) {
11
40
  await notificationRepository.markAsRead(id);
12
41
  }
@@ -84,9 +113,8 @@ export async function sendNotification(input) {
84
113
  offer_expired: "offers", offer_counter_accepted: "offers",
85
114
  refund_initiated: "orderUpdates",
86
115
  product_available: "system",
87
- prize_reveal_ready: "orderUpdates",
116
+ prize_won: "orderUpdates",
88
117
  prize_reveal_expired: "orderUpdates",
89
- prize_reveal_reminder: "orderUpdates",
90
118
  };
91
119
  const typeKey = typeToPrefsKey[type];
92
120
  // If the user has explicitly disabled this notification type, skip all external channels.
@@ -121,28 +149,45 @@ export async function sendNotification(input) {
121
149
  }
122
150
  }
123
151
  }
124
- // WhatsApp channel — admin enabled AND user hasn't opted out
152
+ // WhatsApp channel — admin enabled AND user hasn't opted out AND (for
153
+ // order-lifecycle types) the buyer paid the per-order WhatsApp addon.
125
154
  let whatsappStatus = "skipped";
126
155
  if (channels.whatsapp.enabled &&
127
156
  userChannelPrefs.whatsapp !== false &&
128
157
  resolvedPhone &&
129
158
  meetsMinPriority(priority, channels.whatsapp.minPriority) &&
130
- (!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type))) {
159
+ (!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type)) &&
160
+ (!ORDER_GATED_TYPES.has(type) || input.orderWhatsappAddonPaid === true)) {
131
161
  const phoneNumberId = creds.whatsappPhoneNumberId?.trim() ?? "";
132
162
  const accessToken = creds.whatsappCloudApiToken?.trim() ?? "";
133
163
  if (phoneNumberId && accessToken) {
134
164
  try {
135
- const ok = await sendWhatsAppBusinessMessage({
136
- toPhone: resolvedPhone,
137
- message: `*${title}*\n${message}`,
138
- phoneNumberId,
139
- accessToken,
165
+ const { jobId } = await enqueueJob({
166
+ jobType: "whatsappNotify",
167
+ payload: {
168
+ toPhone: resolvedPhone,
169
+ title,
170
+ message,
171
+ type,
172
+ templateName: resolveWhatsAppTemplateName(type, creds),
173
+ templateLanguage: creds.whatsappTemplateLanguage ?? "en",
174
+ notificationId: notification.id,
175
+ phoneNumberId,
176
+ accessToken,
177
+ },
178
+ requestedBy: input.userId,
179
+ });
180
+ whatsappStatus = "queued";
181
+ await notificationRepository
182
+ .update(notification.id, { whatsappStatus: "queued", whatsappJobId: jobId })
183
+ .catch((err) => {
184
+ void normalizeError(err);
185
+ serverLogger.warn("sendNotification: failed to write whatsappJobId onto notification doc", { notificationId: notification.id });
140
186
  });
141
- whatsappStatus = ok ? "sent" : "failed";
142
187
  }
143
188
  catch (err) {
144
189
  void normalizeError(err);
145
- serverLogger.error("sendNotification: WhatsApp dispatch failed", { userId: input.userId, err });
190
+ serverLogger.error("sendNotification: WhatsApp job enqueue failed", { userId: input.userId, err });
146
191
  whatsappStatus = "failed";
147
192
  }
148
193
  }
@@ -156,7 +156,7 @@ export function AdminAdsView({ endpoint = ADMIN_ENDPOINTS.ADS, labels = {}, crea
156
156
  settingsMutation.mutate(payload);
157
157
  };
158
158
  return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: labels.title ?? "Ad Inventory", sections: [
159
- _jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Text, { variant: "secondary", children: "Manage ad inventory, placement mapping, and publishing state." }), _jsx(TextLink, { variant: "bare", href: createHref, rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-neutral-900 text-white bg-[var(--appkit-color-surface)] dark:text-[var(--appkit-color-text)]", children: "New ad" })] }),
159
+ _jsxs(Row, { align: "center", justify: "between", gap: "3", children: [_jsx(Text, { variant: "secondary", children: "Manage ad inventory, placement mapping, and publishing state." }), _jsx(TextLink, { variant: "bare", href: createHref, rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)]", children: "New ad" })] }),
160
160
  adsQuery.error ? (_jsx(Alert, { variant: "error", title: "Could not load ads", children: adsQuery.error instanceof Error ? adsQuery.error.message : "Unknown error" })) : null,
161
161
  _jsx(AdsSettingsPanel, { adsenseClientId: adsenseClientId, setAdsenseClientId: setAdsenseClientId, thirdPartyScriptUrl: thirdPartyScriptUrl, setThirdPartyScriptUrl: setThirdPartyScriptUrl, consentRequired: consentRequired, setConsentRequired: setConsentRequired, serverCredentialIssues: serverCredentialIssues, localCredentialIssues: localCredentialIssues, credentialStatus: credentialStatus, providerCredentialsMasked: adsQuery.data?.providerCredentialsMasked, settingsMutation: settingsMutation, hasPendingCredentialInput: hasPendingCredentialInput, currentConsentRequired: Boolean(adsQuery.data?.consentRequired), settingsMessage: settingsMessage, onSave: saveSettings }),
162
162
  _jsx(AdsFilterRow, { q: q, setQ: setQ, status: status, setStatus: setStatus, provider: provider, setProvider: setProvider, placement: placement, setPlacement: setPlacement, placements: placements, onPageReset: () => setPage(1) }),
@@ -114,7 +114,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
114
114
  slug: draft.slug || toSlug(draft.title),
115
115
  excerpt: draft.excerpt,
116
116
  content: draft.content,
117
- coverImage: draft.coverImage || undefined,
117
+ coverImage: draft.coverImage ? { type: "image", url: draft.coverImage } : undefined,
118
118
  youtubeId: draft.youtubeId || undefined,
119
119
  category: draft.category,
120
120
  tags: draft.tags,
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
- import { useApiMutation } from "@mohasinac/appkit/client";
4
+ import { useApiMutation, useBulkEvent, RTDB_PATHS } from "@mohasinac/appkit/client";
5
5
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
6
6
  import { sortBy } from "@mohasinac/appkit";
7
- import React, { useCallback, useState } from "react";
7
+ import React, { useCallback, useEffect, useState } from "react";
8
8
  import { useQueryClient } from "@tanstack/react-query";
9
9
  import { Button, ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu, useToast } from "../../../ui";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -33,24 +33,55 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
33
33
  showToast(err?.message ?? "Failed to unsubscribe.", "error");
34
34
  },
35
35
  });
36
+ // Export runs as an async `newsletterExport` job (Async Job Primitive) —
37
+ // the route only enqueues; this subscribes to the same RTDB bulk-event
38
+ // channel every other async job in the admin panel uses.
39
+ const [exporting, setExporting] = useState(false);
40
+ const exportEvent = useBulkEvent({ rtdbPath: RTDB_PATHS.BULK_EVENTS });
36
41
  const handleExportCsv = useCallback(async () => {
42
+ setExporting(true);
37
43
  try {
38
44
  const response = await fetch(ADMIN_ENDPOINTS.NEWSLETTER_EXPORT);
39
45
  if (!response.ok)
40
46
  throw new Error("Export failed");
41
- const blob = await response.blob();
42
- const url = URL.createObjectURL(blob);
43
- const a = document.createElement("a");
44
- a.href = url;
45
- a.download = "newsletter-subscribers.csv";
46
- a.click();
47
- URL.revokeObjectURL(url);
47
+ const body = (await response.json());
48
+ const { jobId, customToken } = body.data ?? {};
49
+ if (jobId && customToken) {
50
+ exportEvent.subscribe(jobId, customToken);
51
+ }
52
+ else {
53
+ throw new Error("Export failed to start");
54
+ }
48
55
  }
49
56
  catch (_err) {
50
57
  void normalizeError(_err);
58
+ setExporting(false);
51
59
  showToast("Failed to export CSV.", "error");
52
60
  }
53
- }, [showToast]);
61
+ }, [showToast, exportEvent]);
62
+ useEffect(() => {
63
+ if (exportEvent.status === "success") {
64
+ setExporting(false);
65
+ const csv = exportEvent.result?.data?.csv;
66
+ if (csv) {
67
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
68
+ const url = URL.createObjectURL(blob);
69
+ const a = document.createElement("a");
70
+ a.href = url;
71
+ a.download = `newsletter-subscribers-${new Date().toISOString().slice(0, 10)}.csv`;
72
+ a.click();
73
+ URL.revokeObjectURL(url);
74
+ }
75
+ else {
76
+ showToast("Export finished with no data.", "error");
77
+ }
78
+ }
79
+ else if (exportEvent.status === "failed" || exportEvent.status === "timeout") {
80
+ setExporting(false);
81
+ showToast(exportEvent.error ?? "Failed to export CSV.", "error");
82
+ }
83
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
+ }, [exportEvent.status]);
54
85
  if (React.Children.count(children) > 0) {
55
86
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
56
87
  }
@@ -85,7 +116,7 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
85
116
  return mappedRows.length;
86
117
  },
87
118
  buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
88
- toolbarExtra: (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label })),
119
+ toolbarExtra: (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, isLoading: exporting, disabled: exporting, children: ACTIONS.ADMIN["export-csv"].label })),
89
120
  buildBulkActions: onBulkUnsubscribe
90
121
  ? (selection) => [
91
122
  buildBulkAction(ACTIONS.ADMIN["unsubscribe-newsletter"], async () => {
@@ -3,8 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React from "react";
6
- import { Pencil } from "lucide-react";
7
- import { Badge, Button, FilterChipGroup, ListingLayout, Span, Text, TextLink } from "../../../ui";
6
+ import { Pencil, ListChecks } from "lucide-react";
7
+ import { Badge, Button, FilterChipGroup, ListingLayout, Row, Span, Text, TextLink } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
9
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
10
10
  import { ADMIN_PRODUCT_STATUS_TABS } from "../constants/filter-tabs";
@@ -101,7 +101,7 @@ export function AdminPrizeDrawsView({ children, ...props }) {
101
101
  destructive: ROW_ACTION_META[id].destructive,
102
102
  onClick: () => selection.clearSelection(),
103
103
  })),
104
- renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(TextLink, { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) })),
104
+ renderRowActions: (row) => (_jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(TextLink, { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) }), _jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(TextLink, { href: String(ROUTES.ADMIN.PRIZE_DRAWS_ENTRIES(row.id)), "aria-label": "View entries", children: _jsx(ListChecks, { className: "w-4 h-4" }) }) })] })),
105
105
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PRODUCT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
106
106
  };
107
107
  return _jsx(DataListingView, { config: config });