@mohasinac/appkit 4.52.4 → 4.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (633) hide show
  1. package/dist/.build-stamp.json +4 -4
  2. package/dist/_internal/client/features/sticker-trace/canvas-codec.d.ts +2 -0
  3. package/dist/_internal/client/features/sticker-trace/canvas-codec.js +148 -0
  4. package/dist/_internal/server/features/auctions/actions.js +1 -1
  5. package/dist/_internal/server/features/categories/data.js +23 -7
  6. package/dist/_internal/server/features/checkout/actions.js +10 -3
  7. package/dist/_internal/server/features/checkout/data.d.ts +15 -0
  8. package/dist/_internal/server/features/checkout/data.js +20 -3
  9. package/dist/_internal/server/features/lottery/actions.js +9 -1
  10. package/dist/_internal/server/features/lottery/og.d.ts +40 -0
  11. package/dist/_internal/server/features/lottery/og.js +79 -0
  12. package/dist/_internal/server/features/messages/actions.js +4 -0
  13. package/dist/_internal/server/features/offers/adapters.js +17 -3
  14. package/dist/_internal/server/features/orders/actions.js +7 -2
  15. package/dist/_internal/server/features/raffle/actions.d.ts +19 -0
  16. package/dist/_internal/server/features/raffle/actions.js +25 -0
  17. package/dist/_internal/server/features/reviews/actions.js +1 -1
  18. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  19. package/dist/_internal/server/features/search/index-build/artifact-store.d.ts +89 -0
  20. package/dist/_internal/server/features/search/index-build/artifact-store.js +208 -0
  21. package/dist/_internal/server/features/search/index-build/build.d.ts +57 -0
  22. package/dist/_internal/server/features/search/index-build/build.js +285 -0
  23. package/dist/_internal/server/features/search/index-build/sources.d.ts +53 -0
  24. package/dist/_internal/server/features/search/index-build/sources.js +385 -0
  25. package/dist/_internal/server/features/search/search-sources.d.ts +0 -15
  26. package/dist/_internal/server/features/search/search-sources.js +142 -7
  27. package/dist/_internal/server/features/seo/robots.js +21 -1
  28. package/dist/_internal/server/features/seo/sitemap.js +1 -1
  29. package/dist/_internal/server/features/shared/listing-data-factory.js +16 -4
  30. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  31. package/dist/_internal/server/features/site-settings/adapters.js +26 -0
  32. package/dist/_internal/server/features/sticker-trace/actions.d.ts +109 -0
  33. package/dist/_internal/server/features/sticker-trace/actions.js +172 -0
  34. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +80 -0
  35. package/dist/_internal/server/features/sticker-trace/adapters.js +174 -0
  36. package/dist/_internal/server/features/sticker-trace/build-cut-file.d.ts +26 -0
  37. package/dist/_internal/server/features/sticker-trace/build-cut-file.js +127 -0
  38. package/dist/_internal/server/features/sticker-trace/coordinator.d.ts +27 -0
  39. package/dist/_internal/server/features/sticker-trace/coordinator.js +169 -0
  40. package/dist/_internal/server/features/sticker-trace/fanout.d.ts +43 -0
  41. package/dist/_internal/server/features/sticker-trace/fanout.js +180 -0
  42. package/dist/_internal/server/features/sticker-trace/finalizer.d.ts +25 -0
  43. package/dist/_internal/server/features/sticker-trace/finalizer.js +123 -0
  44. package/dist/_internal/server/features/sticker-trace/mosaic-inputs.d.ts +30 -0
  45. package/dist/_internal/server/features/sticker-trace/mosaic-inputs.js +112 -0
  46. package/dist/_internal/server/features/sticker-trace/progress.d.ts +81 -0
  47. package/dist/_internal/server/features/sticker-trace/progress.js +198 -0
  48. package/dist/_internal/server/features/sticker-trace/promote.d.ts +23 -0
  49. package/dist/_internal/server/features/sticker-trace/promote.js +193 -0
  50. package/dist/_internal/server/features/sticker-trace/shard.d.ts +50 -0
  51. package/dist/_internal/server/features/sticker-trace/shard.js +174 -0
  52. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +141 -0
  53. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +279 -0
  54. package/dist/_internal/server/features/sticker-trace/storage.d.ts +135 -0
  55. package/dist/_internal/server/features/sticker-trace/storage.js +282 -0
  56. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  57. package/dist/_internal/server/functions/scheduled.js +25 -1
  58. package/dist/_internal/server/jobs/core/_event-ref.d.ts +51 -0
  59. package/dist/_internal/server/jobs/core/_event-ref.js +63 -0
  60. package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +18 -0
  61. package/dist/_internal/server/jobs/core/assignSpinPrize.js +77 -16
  62. package/dist/_internal/server/jobs/core/auctionSettlement.js +13 -4
  63. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +44 -15
  64. package/dist/_internal/server/jobs/core/bundleStockSync.js +3 -4
  65. package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +44 -0
  66. package/dist/_internal/server/jobs/core/event-draw-reasons.d.ts +78 -0
  67. package/dist/_internal/server/jobs/core/event-draw-reasons.js +133 -0
  68. package/dist/_internal/server/jobs/core/listingProcessor.js +28 -8
  69. package/dist/_internal/server/jobs/core/onOrderCreate.js +12 -1
  70. package/dist/_internal/server/jobs/core/onScamReportCreate.js +13 -2
  71. package/dist/_internal/server/jobs/core/onScamReportRejected.js +8 -2
  72. package/dist/_internal/server/jobs/core/onScamReportVerified.js +8 -2
  73. package/dist/_internal/server/jobs/core/onStoreWrite.js +11 -4
  74. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +5 -4
  75. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -4
  76. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -2
  77. package/dist/_internal/server/jobs/core/promotions.js +7 -4
  78. package/dist/_internal/server/jobs/core/searchIndexBuild.d.ts +7 -0
  79. package/dist/_internal/server/jobs/core/searchIndexBuild.js +211 -0
  80. package/dist/_internal/server/jobs/core/triggerEventRaffle.d.ts +35 -0
  81. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +213 -31
  82. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +20 -14
  83. package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +8 -0
  84. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +15 -15
  85. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  86. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  87. package/dist/_internal/server/jobs/handlers/onCategoryWrite.js +18 -0
  88. package/dist/_internal/server/jobs/handlers/onProductWrite.js +18 -0
  89. package/dist/_internal/server/jobs/handlers/onReviewWrite.js +18 -0
  90. package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +18 -0
  91. package/dist/_internal/server/jobs/handlers/onStoreWrite.js +18 -0
  92. package/dist/_internal/server/jobs/handlers/searchIndexBuild.d.ts +2 -0
  93. package/dist/_internal/server/jobs/handlers/searchIndexBuild.js +2 -0
  94. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +8 -0
  95. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -6
  96. package/dist/_internal/shared/actions/action-registry.js +29 -0
  97. package/dist/_internal/shared/cache/revalidate-targets.d.ts +0 -1
  98. package/dist/_internal/shared/cache/revalidate-targets.js +10 -8
  99. package/dist/_internal/shared/features/events/config.d.ts +14 -0
  100. package/dist/_internal/shared/features/events/config.js +14 -0
  101. package/dist/_internal/shared/features/notifications/action-url.js +8 -0
  102. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  103. package/dist/_internal/shared/features/sticker-trace/codec.d.ts +81 -0
  104. package/dist/_internal/shared/features/sticker-trace/codec.js +30 -0
  105. package/dist/_internal/shared/features/sticker-trace/config.d.ts +81 -0
  106. package/dist/_internal/shared/features/sticker-trace/config.js +177 -0
  107. package/dist/_internal/shared/features/sticker-trace/cut-contour.d.ts +20 -0
  108. package/dist/_internal/shared/features/sticker-trace/cut-contour.js +60 -0
  109. package/dist/_internal/shared/features/sticker-trace/export/build-export-set.d.ts +65 -0
  110. package/dist/_internal/shared/features/sticker-trace/export/build-export-set.js +176 -0
  111. package/dist/_internal/shared/features/sticker-trace/export/cut-svg.d.ts +56 -0
  112. package/dist/_internal/shared/features/sticker-trace/export/cut-svg.js +120 -0
  113. package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.d.ts +92 -0
  114. package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.js +153 -0
  115. package/dist/_internal/shared/features/sticker-trace/export/nesting.d.ts +51 -0
  116. package/dist/_internal/shared/features/sticker-trace/export/nesting.js +110 -0
  117. package/dist/_internal/shared/features/sticker-trace/export/page-sizes.d.ts +68 -0
  118. package/dist/_internal/shared/features/sticker-trace/export/page-sizes.js +223 -0
  119. package/dist/_internal/shared/features/sticker-trace/export/part-tiling.d.ts +51 -0
  120. package/dist/_internal/shared/features/sticker-trace/export/part-tiling.js +178 -0
  121. package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.d.ts +26 -0
  122. package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.js +134 -0
  123. package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.d.ts +40 -0
  124. package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.js +195 -0
  125. package/dist/_internal/shared/features/sticker-trace/index.d.ts +2 -0
  126. package/dist/_internal/shared/features/sticker-trace/index.js +59 -0
  127. package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.d.ts +11 -0
  128. package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.js +66 -0
  129. package/dist/_internal/shared/features/sticker-trace/math/edt.d.ts +27 -0
  130. package/dist/_internal/shared/features/sticker-trace/math/edt.js +165 -0
  131. package/dist/_internal/shared/features/sticker-trace/math/label.d.ts +15 -0
  132. package/dist/_internal/shared/features/sticker-trace/math/label.js +156 -0
  133. package/dist/_internal/shared/features/sticker-trace/math/marching-squares.d.ts +16 -0
  134. package/dist/_internal/shared/features/sticker-trace/math/marching-squares.js +242 -0
  135. package/dist/_internal/shared/features/sticker-trace/math/morphology.d.ts +46 -0
  136. package/dist/_internal/shared/features/sticker-trace/math/morphology.js +168 -0
  137. package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.d.ts +88 -0
  138. package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.js +286 -0
  139. package/dist/_internal/shared/features/sticker-trace/math/rdp.d.ts +10 -0
  140. package/dist/_internal/shared/features/sticker-trace/math/rdp.js +74 -0
  141. package/dist/_internal/shared/features/sticker-trace/resume.d.ts +50 -0
  142. package/dist/_internal/shared/features/sticker-trace/resume.js +160 -0
  143. package/dist/_internal/shared/features/sticker-trace/trace/cluster.d.ts +20 -0
  144. package/dist/_internal/shared/features/sticker-trace/trace/cluster.js +132 -0
  145. package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.d.ts +28 -0
  146. package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.js +120 -0
  147. package/dist/_internal/shared/features/sticker-trace/trace/flood.d.ts +21 -0
  148. package/dist/_internal/shared/features/sticker-trace/trace/flood.js +104 -0
  149. package/dist/_internal/shared/features/sticker-trace/trace/geometry.d.ts +3 -0
  150. package/dist/_internal/shared/features/sticker-trace/trace/geometry.js +140 -0
  151. package/dist/_internal/shared/features/sticker-trace/trace/line-filter.d.ts +15 -0
  152. package/dist/_internal/shared/features/sticker-trace/trace/line-filter.js +138 -0
  153. package/dist/_internal/shared/features/sticker-trace/trace/merge.d.ts +27 -0
  154. package/dist/_internal/shared/features/sticker-trace/trace/merge.js +187 -0
  155. package/dist/_internal/shared/features/sticker-trace/trace/paper.d.ts +32 -0
  156. package/dist/_internal/shared/features/sticker-trace/trace/paper.js +147 -0
  157. package/dist/_internal/shared/features/sticker-trace/trace/prefilter.d.ts +30 -0
  158. package/dist/_internal/shared/features/sticker-trace/trace/prefilter.js +125 -0
  159. package/dist/_internal/shared/features/sticker-trace/trace/quantise.d.ts +10 -0
  160. package/dist/_internal/shared/features/sticker-trace/trace/quantise.js +153 -0
  161. package/dist/_internal/shared/features/sticker-trace/trace/ridge.d.ts +88 -0
  162. package/dist/_internal/shared/features/sticker-trace/trace/ridge.js +344 -0
  163. package/dist/_internal/shared/features/sticker-trace/trace/segment.d.ts +10 -0
  164. package/dist/_internal/shared/features/sticker-trace/trace/segment.js +251 -0
  165. package/dist/_internal/shared/features/sticker-trace/types.d.ts +197 -0
  166. package/dist/_internal/shared/features/sticker-trace/types.js +49 -0
  167. package/dist/_internal/shared/listing-types/_registry.d.ts +11 -0
  168. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  169. package/dist/_internal/shared/listing-types/art/config.js +1 -0
  170. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  171. package/dist/_internal/shared/listing-types/auction/config.js +1 -0
  172. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  173. package/dist/_internal/shared/listing-types/classified/config.js +1 -0
  174. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  175. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -0
  176. package/dist/_internal/shared/listing-types/feature-flags.d.ts +7 -0
  177. package/dist/_internal/shared/listing-types/feature-flags.js +9 -0
  178. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  179. package/dist/_internal/shared/listing-types/live/config.js +1 -0
  180. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  181. package/dist/_internal/shared/listing-types/pre-order/config.js +1 -0
  182. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  183. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -0
  184. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  185. package/dist/_internal/shared/listing-types/standard/config.js +1 -0
  186. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  187. package/dist/_internal/shared/listing-types/stickers/config.js +1 -0
  188. package/dist/_internal/shared/media/limits.d.ts +12 -2
  189. package/dist/_internal/shared/media/limits.js +12 -2
  190. package/dist/_internal/shared/search/artifact-url.d.ts +35 -0
  191. package/dist/_internal/shared/search/artifact-url.js +119 -0
  192. package/dist/_internal/shared/search/index-record.d.ts +68 -0
  193. package/dist/_internal/shared/search/index-record.js +96 -0
  194. package/dist/_internal/shared/search/match.d.ts +65 -0
  195. package/dist/_internal/shared/search/match.js +263 -0
  196. package/dist/client.d.ts +8 -7
  197. package/dist/client.js +12 -5
  198. package/dist/configs/eslint.js +22 -2
  199. package/dist/constants/api-endpoints.d.ts +68 -0
  200. package/dist/constants/api-endpoints.js +32 -0
  201. package/dist/constants/field-names.d.ts +7 -1
  202. package/dist/constants/field-names.js +36 -1
  203. package/dist/contracts/repository.d.ts +8 -7
  204. package/dist/contracts/search.d.ts +2 -2
  205. package/dist/contracts/table.d.ts +3 -3
  206. package/dist/contracts/table.js +3 -3
  207. package/dist/core/contact-submissions.repository.js +2 -2
  208. package/dist/core/integration-keys.js +7 -1
  209. package/dist/core/newsletter.repository.js +2 -2
  210. package/dist/core/site-config.js +1 -1
  211. package/dist/errors/error-codes.d.ts +30 -0
  212. package/dist/errors/error-codes.js +30 -0
  213. package/dist/errors/error-display-map.js +40 -1
  214. package/dist/features/account/api/[userId]/addresses/route.js +1 -1
  215. package/dist/features/account/components/UserBidsView.js +1 -1
  216. package/dist/features/account/components/index.d.ts +0 -8
  217. package/dist/features/account/components/index.js +0 -4
  218. package/dist/features/addresses/repository/addresses.repository.d.ts +17 -6
  219. package/dist/features/addresses/repository/addresses.repository.js +65 -9
  220. package/dist/features/addresses/schemas/firestore.d.ts +17 -6
  221. package/dist/features/admin/actions/notification-actions.js +15 -6
  222. package/dist/features/admin/api/bids/route.js +1 -1
  223. package/dist/features/admin/api/coupons/route.js +1 -1
  224. package/dist/features/admin/api/products/route.js +1 -1
  225. package/dist/features/admin/api/reviews/route.js +1 -1
  226. package/dist/features/admin/components/AdminAdEditorView.js +19 -5
  227. package/dist/features/admin/components/AdminAuditLogView.js +1 -1
  228. package/dist/features/admin/components/AdminBlogEditorView.js +0 -1
  229. package/dist/features/admin/components/AdminBlogView.js +1 -1
  230. package/dist/features/admin/components/AdminBrandsView.js +17 -1
  231. package/dist/features/admin/components/AdminCategoryEditorView.js +0 -1
  232. package/dist/features/admin/components/AdminContactView.js +1 -1
  233. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -3
  234. package/dist/features/admin/components/AdminNewsletterView.js +1 -1
  235. package/dist/features/admin/components/AdminOrdersView.js +9 -2
  236. package/dist/features/admin/components/AdminPayoutsView.js +1 -1
  237. package/dist/features/admin/components/AdminPrizeDrawsView.js +11 -0
  238. package/dist/features/admin/components/AdminProductEditorView.d.ts +10 -1
  239. package/dist/features/admin/components/AdminProductEditorView.js +24 -37
  240. package/dist/features/admin/components/AdminProductsView.js +10 -3
  241. package/dist/features/admin/components/AdminScammersView.js +1 -1
  242. package/dist/features/admin/components/AdminSectionsView.js +21 -5
  243. package/dist/features/admin/components/AdminSessionsView.js +1 -1
  244. package/dist/features/admin/components/AdminSublistingCategoriesView.js +22 -1
  245. package/dist/features/admin/components/AdminTeamView.js +1 -1
  246. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +0 -1
  247. package/dist/features/admin/components/AdminUsersView.js +1 -1
  248. package/dist/features/admin/components/DataListingView.d.ts +2 -1
  249. package/dist/features/admin/components/DataListingView.js +9 -4
  250. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +0 -2
  251. package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
  252. package/dist/features/admin/repository/site-settings.repository.js +8 -3
  253. package/dist/features/admin/schemas/firestore.d.ts +62 -1
  254. package/dist/features/admin/schemas/firestore.js +32 -0
  255. package/dist/features/admin/types/index.d.ts +1 -1
  256. package/dist/features/auctions/actions/bid-actions.d.ts +2 -2
  257. package/dist/features/auctions/actions/bid-actions.js +16 -4
  258. package/dist/features/auctions/api/route.js +1 -1
  259. package/dist/features/auctions/components/AuctionBottomActions.js +4 -0
  260. package/dist/features/auctions/repository/bid.repository.d.ts +46 -4
  261. package/dist/features/auctions/repository/bid.repository.js +114 -13
  262. package/dist/features/auctions/schemas/firestore.d.ts +24 -4
  263. package/dist/features/auctions/schemas/firestore.js +1 -0
  264. package/dist/features/auth/actions/profile-actions.js +30 -4
  265. package/dist/features/auth/components/index.d.ts +0 -1
  266. package/dist/features/auth/components/index.js +0 -1
  267. package/dist/features/auth/repository/session.repository.d.ts +17 -3
  268. package/dist/features/auth/repository/session.repository.js +46 -5
  269. package/dist/features/auth/repository/token.repository.d.ts +27 -4
  270. package/dist/features/auth/repository/token.repository.js +65 -11
  271. package/dist/features/auth/repository/user.repository.d.ts +33 -7
  272. package/dist/features/auth/repository/user.repository.js +100 -15
  273. package/dist/features/auth/schemas/firestore.d.ts +63 -12
  274. package/dist/features/auth/schemas/firestore.js +11 -0
  275. package/dist/features/before-after/api/route.js +1 -1
  276. package/dist/features/blog/components/BlogListView.js +16 -3
  277. package/dist/features/blog/components/BlogPostView.js +1 -1
  278. package/dist/features/blog/schemas/index.d.ts +2 -2
  279. package/dist/features/cart/api/route.js +1 -1
  280. package/dist/features/catalogue/repository/catalogue.repository.d.ts +7 -7
  281. package/dist/features/catalogue/repository/catalogue.repository.js +28 -10
  282. package/dist/features/catalogue/schemas/firestore.d.ts +11 -0
  283. package/dist/features/categories/api/route.js +10 -10
  284. package/dist/features/categories/components/CategoryDetailPageView.js +35 -5
  285. package/dist/features/categories/components/CategorySelectorCreate.js +0 -1
  286. package/dist/features/categories/components/index.d.ts +0 -2
  287. package/dist/features/categories/components/index.js +0 -1
  288. package/dist/features/categories/repository/categories.repository.d.ts +1 -0
  289. package/dist/features/categories/repository/categories.repository.js +31 -1
  290. package/dist/features/categories/schemas/index.d.ts +2 -2
  291. package/dist/features/categories/utils/taxonomy-visual.d.ts +21 -0
  292. package/dist/features/categories/utils/taxonomy-visual.js +58 -0
  293. package/dist/features/checkout/actions/checkout-value-otp-actions.js +8 -1
  294. package/dist/features/collections/api/[slug]/route.js +3 -3
  295. package/dist/features/collections/api/route.js +1 -1
  296. package/dist/features/consultation/api/route.js +1 -1
  297. package/dist/features/consultation/repository/consultations.repository.js +1 -1
  298. package/dist/features/consultation/types/index.d.ts +1 -1
  299. package/dist/features/contact/components/ContactForm.js +1 -3
  300. package/dist/features/corporate/api/route.js +1 -1
  301. package/dist/features/corporate/repository/corporate.repository.js +1 -1
  302. package/dist/features/corporate/types/index.d.ts +1 -1
  303. package/dist/features/cron/jobs/auction-expiry.job.js +1 -1
  304. package/dist/features/cron/jobs/preorder-reminder.job.js +1 -1
  305. package/dist/features/events/actions/event-actions.d.ts +14 -0
  306. package/dist/features/events/actions/event-actions.js +82 -3
  307. package/dist/features/events/api/[id]/route.js +2 -2
  308. package/dist/features/events/components/AdminEventEditorView.js +22 -3
  309. package/dist/features/events/components/AdminEventsView.js +32 -8
  310. package/dist/features/events/components/EventRafflesSection.d.ts +34 -1
  311. package/dist/features/events/components/EventRafflesSection.js +52 -3
  312. package/dist/features/events/components/SpinWheelView.d.ts +2 -2
  313. package/dist/features/events/components/SpinWheelView.js +41 -4
  314. package/dist/features/events/repository/event-entry.repository.d.ts +14 -2
  315. package/dist/features/events/repository/event-entry.repository.js +41 -7
  316. package/dist/features/events/schemas/firestore.d.ts +17 -4
  317. package/dist/features/events/types/index.d.ts +37 -0
  318. package/dist/features/faq/repository/faqs.repository.js +1 -1
  319. package/dist/features/faq/schemas/index.d.ts +5 -2
  320. package/dist/features/faq/schemas/index.js +6 -0
  321. package/dist/features/faq/types/index.d.ts +0 -1
  322. package/dist/features/grouped/components/GroupedListingEditorView.js +17 -2
  323. package/dist/features/homepage/api/carousel/[id]/route.js +1 -1
  324. package/dist/features/homepage/api/carousel/route.js +4 -4
  325. package/dist/features/homepage/api/route.js +2 -2
  326. package/dist/features/homepage/api/sections/route.js +1 -1
  327. package/dist/features/homepage/components/AnnouncementBar.d.ts +1 -9
  328. package/dist/features/homepage/components/AnnouncementBar.js +13 -19
  329. package/dist/features/homepage/components/MarketplaceHomepageView.js +6 -1
  330. package/dist/features/homepage/components/index.d.ts +0 -2
  331. package/dist/features/homepage/components/index.js +0 -1
  332. package/dist/features/homepage/repository/homepage.repository.js +1 -1
  333. package/dist/features/homepage/schemas/firestore.d.ts +6 -0
  334. package/dist/features/homepage/schemas/index.d.ts +10 -10
  335. package/dist/features/layout/AppLayoutShell.d.ts +12 -3
  336. package/dist/features/layout/AppLayoutShell.js +65 -13
  337. package/dist/features/layout/BottomActionsContext.d.ts +16 -0
  338. package/dist/features/layout/BottomActionsContext.js +5 -0
  339. package/dist/features/layout/BottomChrome.js +48 -9
  340. package/dist/features/layout/BottomNavbar.d.ts +14 -0
  341. package/dist/features/layout/BottomNavbar.js +43 -3
  342. package/dist/features/layout/MainNavbar.d.ts +11 -0
  343. package/dist/features/layout/NavbarLayout.d.ts +3 -0
  344. package/dist/features/layout/NavbarLayout.js +88 -11
  345. package/dist/features/layout/SideActionDock.d.ts +1 -0
  346. package/dist/features/layout/SideActionDock.js +219 -0
  347. package/dist/features/layout/TitleBarLayout.d.ts +1 -3
  348. package/dist/features/layout/TitleBarLayout.js +3 -4
  349. package/dist/features/layout/cta-anchor.d.ts +31 -0
  350. package/dist/features/layout/cta-anchor.js +31 -0
  351. package/dist/features/layout/hooks/useBottomActions.d.ts +7 -0
  352. package/dist/features/layout/hooks/useBottomActions.js +6 -1
  353. package/dist/features/layout/index.d.ts +9 -1
  354. package/dist/features/layout/index.js +9 -1
  355. package/dist/features/layout/useCtaReveal.d.ts +24 -0
  356. package/dist/features/layout/useCtaReveal.js +82 -0
  357. package/dist/features/layout/useHoldToOpen.d.ts +41 -0
  358. package/dist/features/layout/useHoldToOpen.js +84 -0
  359. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +18 -3
  360. package/dist/features/lottery/repository/lottery-entry.repository.js +48 -6
  361. package/dist/features/lottery/schemas/firestore.d.ts +33 -5
  362. package/dist/features/loyalty/api/balance/route.js +1 -1
  363. package/dist/features/loyalty/api/history/route.js +1 -1
  364. package/dist/features/media/MediaImage.js +0 -1
  365. package/dist/features/media/MediaPickerModal.js +7 -1
  366. package/dist/features/media/modals/ImageEditor.js +5 -1
  367. package/dist/features/media/modals/VideoThumbnailSelector.js +17 -1
  368. package/dist/features/media/repository/media-assets.repository.d.ts +19 -0
  369. package/dist/features/media/upload/CameraCapture.js +16 -1
  370. package/dist/features/media/upload/ImageUpload.js +42 -4
  371. package/dist/features/media/upload/MediaUploadField.js +69 -13
  372. package/dist/features/media/upload/downscale-image.d.ts +56 -0
  373. package/dist/features/media/upload/downscale-image.js +150 -0
  374. package/dist/features/messages/components/ConversationThreadView.js +45 -2
  375. package/dist/features/messages/components/MessageSellerButton.d.ts +15 -1
  376. package/dist/features/messages/components/MessageSellerButton.js +10 -2
  377. package/dist/features/messages/schemas/firestore.d.ts +15 -0
  378. package/dist/features/orders/hooks/useOrders.js +4 -2
  379. package/dist/features/orders/repository/orders.repository.d.ts +15 -4
  380. package/dist/features/orders/repository/orders.repository.js +52 -20
  381. package/dist/features/orders/schemas/firestore.d.ts +15 -4
  382. package/dist/features/orders/schemas/index.d.ts +8 -8
  383. package/dist/features/orders/types/index.d.ts +2 -2
  384. package/dist/features/payments/repository/payments.repository.js +2 -2
  385. package/dist/features/payments/repository/payout.repository.d.ts +23 -6
  386. package/dist/features/payments/repository/payout.repository.js +63 -23
  387. package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +17 -6
  388. package/dist/features/payments/repository/saved-payment-methods.repository.js +59 -11
  389. package/dist/features/payments/schemas/firestore.d.ts +14 -3
  390. package/dist/features/payments/schemas/saved-methods-firestore.d.ts +12 -1
  391. package/dist/features/pre-orders/api/[slug]/route.js +1 -1
  392. package/dist/features/pre-orders/api/admin/route.js +1 -1
  393. package/dist/features/pre-orders/api/route.js +1 -1
  394. package/dist/features/pre-orders/repository/pre-orders.repository.d.ts +3 -3
  395. package/dist/features/pre-orders/repository/pre-orders.repository.js +6 -6
  396. package/dist/features/products/api/[id]/route.js +1 -1
  397. package/dist/features/products/components/ListingBottomActions.js +4 -0
  398. package/dist/features/products/components/ProductDetailPageView.js +2 -1
  399. package/dist/features/products/components/ProductGalleryClient.js +2 -2
  400. package/dist/features/products/components/ProductGrid.js +12 -1
  401. package/dist/features/products/components/index.d.ts +0 -2
  402. package/dist/features/products/components/index.js +0 -1
  403. package/dist/features/products/config/listing-type-listing-config.d.ts +0 -3
  404. package/dist/features/products/config/listing-type-listing-config.js +27 -2
  405. package/dist/features/products/constants/listing-tabs.d.ts +9 -0
  406. package/dist/features/products/constants/listing-tabs.js +3 -2
  407. package/dist/features/products/hooks/useProducts.js +2 -2
  408. package/dist/features/products/repository/products.repository.js +13 -1
  409. package/dist/features/products/schemas/index.d.ts +2 -2
  410. package/dist/features/products/types/index.d.ts +1 -1
  411. package/dist/features/promotions/api/route.js +1 -1
  412. package/dist/features/reviews/actions/review-actions.d.ts +6 -6
  413. package/dist/features/reviews/actions/review-actions.js +22 -8
  414. package/dist/features/reviews/components/ReviewDetailPageView.js +13 -1
  415. package/dist/features/reviews/components/ReviewsIndexPageView.js +21 -4
  416. package/dist/features/reviews/repository/reviews.repository.d.ts +19 -7
  417. package/dist/features/reviews/repository/reviews.repository.js +63 -12
  418. package/dist/features/reviews/schemas/firestore.d.ts +13 -2
  419. package/dist/features/reviews/schemas/index.d.ts +2 -2
  420. package/dist/features/scams/actions/scam-actions.js +16 -4
  421. package/dist/features/search/components/SearchResultsPageView.js +2 -1
  422. package/dist/features/search/hooks/useSearchIndex.d.ts +34 -0
  423. package/dist/features/search/hooks/useSearchIndex.js +172 -0
  424. package/dist/features/search/repository/search.repository.js +2 -2
  425. package/dist/features/seller/actions/offer-actions.d.ts +5 -5
  426. package/dist/features/seller/actions/offer-actions.js +38 -8
  427. package/dist/features/seller/actions/seller-actions.js +34 -7
  428. package/dist/features/seller/api/coupons/route.js +1 -1
  429. package/dist/features/seller/api/products/route.js +1 -1
  430. package/dist/features/seller/api/store/route.js +1 -1
  431. package/dist/features/seller/components/SellerBidsView.js +1 -1
  432. package/dist/features/seller/components/SellerCouponsView.js +1 -1
  433. package/dist/features/seller/components/SellerListingTypeView.js +9 -1
  434. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  435. package/dist/features/seller/components/SellerPayoutSettingsView.js +0 -1
  436. package/dist/features/seller/components/SellerPayoutsView.js +1 -1
  437. package/dist/features/seller/components/SellerProductShell.js +0 -1
  438. package/dist/features/seller/components/SellerProductsView.js +1 -1
  439. package/dist/features/seller/components/SellerShippingView.js +0 -1
  440. package/dist/features/seller/components/SellerStorefrontView.js +0 -1
  441. package/dist/features/seller/components/index.d.ts +0 -4
  442. package/dist/features/seller/components/index.js +0 -2
  443. package/dist/features/seller/repository/offer.repository.d.ts +15 -3
  444. package/dist/features/seller/repository/offer.repository.js +43 -7
  445. package/dist/features/seller/schemas/firestore.d.ts +14 -3
  446. package/dist/features/seller/schemas/index.d.ts +4 -4
  447. package/dist/features/seller/schemas/order-forms.d.ts +39 -0
  448. package/dist/features/seller/schemas/order-forms.js +37 -0
  449. package/dist/features/shell/FormShell.js +6 -2
  450. package/dist/features/shell/QuickFormDrawer.js +15 -1
  451. package/dist/features/shipments/components/AdminShipmentEditorView.js +0 -1
  452. package/dist/features/shipments/components/AdminShipmentsView.js +1 -1
  453. package/dist/features/sticker-trace/components/StickerCommitPanel.d.ts +30 -0
  454. package/dist/features/sticker-trace/components/StickerCommitPanel.js +93 -0
  455. package/dist/features/sticker-trace/components/StickerPrintPanel.d.ts +33 -0
  456. package/dist/features/sticker-trace/components/StickerPrintPanel.js +104 -0
  457. package/dist/features/sticker-trace/components/StickerRetryPanel.d.ts +20 -0
  458. package/dist/features/sticker-trace/components/StickerRetryPanel.js +128 -0
  459. package/dist/features/sticker-trace/components/StickerRunHistoryPanel.d.ts +45 -0
  460. package/dist/features/sticker-trace/components/StickerRunHistoryPanel.js +84 -0
  461. package/dist/features/sticker-trace/components/StickerTraceStudio.d.ts +27 -0
  462. package/dist/features/sticker-trace/components/StickerTraceStudio.js +171 -0
  463. package/dist/features/sticker-trace/constants/help-content.d.ts +23 -0
  464. package/dist/features/sticker-trace/constants/help-content.js +172 -0
  465. package/dist/features/sticker-trace/hooks/useLiveTrace.d.ts +27 -0
  466. package/dist/features/sticker-trace/hooks/useLiveTrace.js +160 -0
  467. package/dist/features/sticker-trace/hooks/useStickerTraceProgress.d.ts +22 -0
  468. package/dist/features/sticker-trace/hooks/useStickerTraceProgress.js +106 -0
  469. package/dist/features/sticker-trace/repository/sticker-sheets.repository.d.ts +113 -0
  470. package/dist/features/sticker-trace/repository/sticker-sheets.repository.js +146 -0
  471. package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.d.ts +132 -0
  472. package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.js +174 -0
  473. package/dist/features/sticker-trace/schemas/firestore.d.ts +248 -0
  474. package/dist/features/sticker-trace/schemas/firestore.js +79 -0
  475. package/dist/features/sticker-trace/server.d.ts +8 -0
  476. package/dist/features/sticker-trace/server.js +51 -0
  477. package/dist/features/stores/actions/store-query-actions.d.ts +2 -2
  478. package/dist/features/stores/actions/store-query-actions.js +7 -2
  479. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -3
  480. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -3
  481. package/dist/features/stores/api/[storeSlug]/reviews/route.js +1 -1
  482. package/dist/features/stores/api/[storeSlug]/route.js +1 -1
  483. package/dist/features/stores/components/StoreNavTabs.js +17 -2
  484. package/dist/features/stores/manifest.js +8 -4
  485. package/dist/features/support/repository/support.repository.d.ts +14 -3
  486. package/dist/features/support/repository/support.repository.js +93 -13
  487. package/dist/features/support/schemas/firestore.d.ts +16 -5
  488. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +61 -19
  489. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +19 -3
  490. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  491. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +243 -0
  492. package/dist/features/tester/seed-data/authored/content-discovery__events.js +36 -10
  493. package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.d.ts +2 -0
  494. package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.js +209 -0
  495. package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.d.ts +2 -0
  496. package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.js +90 -0
  497. package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.d.ts +2 -0
  498. package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.js +85 -0
  499. package/dist/features/tester/seed-data/authored/index.js +10 -0
  500. package/dist/features/tester/seed-data/authored/selling__media-limits.js +19 -17
  501. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +10 -5
  502. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.d.ts +2 -0
  503. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +283 -0
  504. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -22
  505. package/dist/index.d.ts +10 -23
  506. package/dist/index.js +27 -19
  507. package/dist/next/api/routeHandler.js +9 -0
  508. package/dist/next/routing/require-record.d.ts +13 -0
  509. package/dist/next/routing/require-record.js +62 -0
  510. package/dist/next/routing/route-map.d.ts +4 -4
  511. package/dist/next/routing/route-map.js +25 -3
  512. package/dist/providers/db-firebase/base.d.ts +4 -2
  513. package/dist/providers/db-firebase/base.js +21 -5
  514. package/dist/providers/db-firebase/base.repository.d.ts +11 -9
  515. package/dist/providers/db-firebase/base.repository.js +19 -0
  516. package/dist/providers/db-firebase/rtdb-paths.d.ts +12 -0
  517. package/dist/providers/db-firebase/rtdb-paths.js +12 -0
  518. package/dist/react/ErrorBoundary.js +6 -1
  519. package/dist/react/hooks/realtime-event-constants.d.ts +1 -0
  520. package/dist/react/hooks/realtime-event-constants.js +8 -0
  521. package/dist/react/hooks/useEntityDelete.js +5 -1
  522. package/dist/react/hooks/useMessage.js +5 -1
  523. package/dist/repositories/index.d.ts +2 -2
  524. package/dist/repositories/index.js +6 -1
  525. package/dist/schemas/registry.d.ts +13 -20
  526. package/dist/schemas/registry.js +31 -14
  527. package/dist/schemas/webhooks/google-oauth.d.ts +2 -2
  528. package/dist/security/encrypted.d.ts +117 -0
  529. package/dist/security/encrypted.js +100 -0
  530. package/dist/security/index.d.ts +5 -2
  531. package/dist/security/index.js +16 -2
  532. package/dist/security/pii-accessors.d.ts +71 -0
  533. package/dist/security/pii-accessors.js +150 -0
  534. package/dist/security/pii-encrypt.d.ts +0 -8
  535. package/dist/security/pii-encrypt.js +22 -15
  536. package/dist/security/pii-mask.d.ts +18 -0
  537. package/dist/security/pii-mask.js +27 -0
  538. package/dist/seed/_helpers/search-txt-wrappers.d.ts +6 -6
  539. package/dist/seed/_helpers/search-txt-wrappers.js +22 -0
  540. package/dist/seed/addresses-seed-data.d.ts +2 -2
  541. package/dist/seed/authenticity-knowledge-seed-data.d.ts +34 -0
  542. package/dist/seed/authenticity-knowledge-seed-data.js +1277 -0
  543. package/dist/seed/bids-seed-data.d.ts +2 -2
  544. package/dist/seed/bids-seed-data.js +23 -2
  545. package/dist/seed/events-seed-data.d.ts +2 -2
  546. package/dist/seed/events-seed-data.js +5 -0
  547. package/dist/seed/faq-seed-data.js +175 -5
  548. package/dist/seed/homepage-sections-seed-data.js +39 -23
  549. package/dist/seed/index.d.ts +1 -0
  550. package/dist/seed/index.js +1 -0
  551. package/dist/seed/lottery-entries-seed-data.d.ts +2 -2
  552. package/dist/seed/offers-seed-data.d.ts +2 -2
  553. package/dist/seed/offers-seed-data.js +1 -1
  554. package/dist/seed/orders-seed-data.d.ts +2 -2
  555. package/dist/seed/payouts-seed-data.d.ts +2 -2
  556. package/dist/seed/payouts-seed-data.js +1 -1
  557. package/dist/seed/reviews-seed-data.d.ts +1 -2
  558. package/dist/seed/seed-purpose.js +7 -0
  559. package/dist/seed/sessions-seed-data.d.ts +2 -2
  560. package/dist/seed/sessions-seed-data.js +5 -0
  561. package/dist/seed/site-settings-seed-data.js +1 -1
  562. package/dist/seed/support-tickets-seed-data.d.ts +2 -2
  563. package/dist/seed/support-tickets-seed-data.js +1 -1
  564. package/dist/seed/users-seed-data.d.ts +2 -2
  565. package/dist/seed/users-seed-data.js +1 -1
  566. package/dist/seo/json-ld.js +5 -1
  567. package/dist/server.d.ts +18 -3
  568. package/dist/server.js +59 -3
  569. package/dist/styles.css +237 -1
  570. package/dist/tailwind-utilities.css +1 -1
  571. package/dist/ui/components/Button.js +7 -1
  572. package/dist/ui/components/FilterChipGroup.d.ts +11 -0
  573. package/dist/ui/components/FilterChipGroup.js +1 -1
  574. package/dist/ui/components/ImageGallery.js +6 -1
  575. package/dist/ui/components/ListingLayout.js +14 -8
  576. package/dist/ui/components/OverflowRow.d.ts +92 -0
  577. package/dist/ui/components/OverflowRow.js +203 -0
  578. package/dist/ui/components/OverflowRow.style.css +235 -0
  579. package/dist/ui/components/RecordDetailModal.js +14 -1
  580. package/dist/ui/components/RowActionMenu.js +5 -1
  581. package/dist/ui/components/SideDrawer.js +5 -1
  582. package/dist/ui/components/SideModal.js +9 -1
  583. package/dist/ui/components/TabBarShell.d.ts +12 -1
  584. package/dist/ui/components/TabBarShell.js +3 -2
  585. package/dist/ui/components/Tabs.js +36 -24
  586. package/dist/ui/components/Toast.d.ts +3 -24
  587. package/dist/ui/components/Toast.js +2 -19
  588. package/dist/ui/components/TopBannerPortal.d.ts +15 -0
  589. package/dist/ui/components/TopBannerPortal.js +19 -0
  590. package/dist/ui/components/UnsavedChangesModal.js +5 -1
  591. package/dist/ui/components/index.style.css +1 -0
  592. package/dist/ui/components/row-collapse.d.ts +15 -0
  593. package/dist/ui/components/row-collapse.js +127 -0
  594. package/dist/ui/components/toast-context.d.ts +63 -0
  595. package/dist/ui/components/toast-context.js +48 -0
  596. package/dist/ui/components/top-banner-slot.d.ts +37 -0
  597. package/dist/ui/components/top-banner-slot.js +45 -0
  598. package/dist/ui/icons/icon-registry.d.ts +6 -0
  599. package/dist/ui/icons/icon-registry.js +17 -1
  600. package/dist/ui/index.d.ts +2 -0
  601. package/dist/ui/index.js +1 -0
  602. package/dist/utils/array.helper.d.ts +2 -2
  603. package/dist/utils/array.helper.js +5 -5
  604. package/dist/utils/id-generators.d.ts +6 -0
  605. package/dist/utils/id-generators.js +10 -0
  606. package/dist/utils/media-url.js +0 -2
  607. package/dist/utils/search-txt-builders.d.ts +28 -4
  608. package/dist/utils/search-txt-builders.js +41 -1
  609. package/dist/validation/schemas.d.ts +8 -8
  610. package/package.json +6 -2
  611. package/scripts/seed-cli.mjs +3 -0
  612. package/dist/features/account/components/UserAddressesView.d.ts +0 -13
  613. package/dist/features/account/components/UserAddressesView.js +0 -5
  614. package/dist/features/account/components/UserNotificationsView.d.ts +0 -15
  615. package/dist/features/account/components/UserNotificationsView.js +0 -12
  616. package/dist/features/account/components/UserSettingsView.d.ts +0 -16
  617. package/dist/features/account/components/UserSettingsView.js +0 -13
  618. package/dist/features/account/components/UserSupportView.d.ts +0 -3
  619. package/dist/features/account/components/UserSupportView.js +0 -166
  620. package/dist/features/admin/components/DemoSeedView.d.ts +0 -11
  621. package/dist/features/admin/components/DemoSeedView.js +0 -5
  622. package/dist/features/auth/components/AuthStatusPanel.d.ts +0 -10
  623. package/dist/features/auth/components/AuthStatusPanel.js +0 -22
  624. package/dist/features/categories/components/CategoryProductsView.d.ts +0 -45
  625. package/dist/features/categories/components/CategoryProductsView.js +0 -21
  626. package/dist/features/homepage/components/HowItWorksInfoView.d.ts +0 -36
  627. package/dist/features/homepage/components/HowItWorksInfoView.js +0 -10
  628. package/dist/features/layout/BottomActions.d.ts +0 -1
  629. package/dist/features/layout/BottomActions.js +0 -311
  630. package/dist/features/products/components/ProductsView.d.ts +0 -6
  631. package/dist/features/products/components/ProductsView.js +0 -5
  632. package/dist/features/seller/components/SellerStoreView.d.ts +0 -8
  633. package/dist/features/seller/components/SellerStoreView.js +0 -8
@@ -12,11 +12,11 @@ import { eventEntryRepository } from "../repository/event-entry.repository.js";
12
12
  import { ERROR_MESSAGES, AuthorizationError, ValidationError, NotFoundError, } from "../../../errors/index.js";
13
13
  import { resolveDate } from "../../../utils/index.js";
14
14
  import { hashGuestIdentity } from "../../../security/rate-limit.js";
15
+ import { RAFFLE_MAX_ENTRIES_PER_USER } from "../../../_internal/shared/features/events/config.js";
15
16
  const ERR_EVENT_NOT_FOUND = "Event not found";
16
17
  import { coerceMediaField, getMediaUrl } from "../../../utils/index.js";
17
18
  import { finalizeStagedMediaField, finalizeStagedMediaObject, finalizeStagedMediaObjectArray, } from "../../media/finalize.js";
18
19
  import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility.js";
19
- import { maskName } from "../../../security/pii-mask.js";
20
20
  // --- Admin: Create Event --------------------------------------------------
21
21
  export async function createEvent(adminId, input) {
22
22
  const finalizedCoverUrl = await finalizeStagedMediaField(getMediaUrl(input.coverImage) ??
@@ -193,6 +193,11 @@ export async function getEventSpinResults(eventId, limit = 10) {
193
193
  return [];
194
194
  const prizeTitleById = new Map((event.spinPrizes ?? []).map((p) => [p.id, p.label]));
195
195
  const entries = await eventEntryRepository.getRecentSpinResults(event.id, limit);
196
+ /*
197
+ * Spin results are shown publicly, so the entrant name is MASKED — and it
198
+ * has to be masked by `maskedEntry`, which decrypts first. `maskName` on the
199
+ * stored value answers "Anonymous" for every row by design.
200
+ */
196
201
  return entries.map((entry) => ({
197
202
  id: entry.id,
198
203
  /*
@@ -211,7 +216,15 @@ export async function getEventSpinResults(eventId, limit = 10) {
211
216
  * on for its own label. A caller's OWN spins are served unmasked by
212
217
  * `getUserSpinResultsForEvent` — masking is for other people's.
213
218
  */
214
- userDisplayName: entry.userId ? maskName(entry.userDisplayName) : undefined,
219
+ /*
220
+ * `maskedEntry`, not `maskName` on the stored value. `maskName`
221
+ * refuses ciphertext by design and answers "Anonymous", so this
222
+ * public list showed every entrant as Anonymous. `maskedEntry`
223
+ * decrypts first, then masks.
224
+ */
225
+ userDisplayName: entry.userId
226
+ ? eventEntryRepository.maskedEntry(entry).userDisplayName
227
+ : undefined,
215
228
  isGuest: !entry.userId,
216
229
  spinPrizeId: entry.spinPrizeId,
217
230
  spinPrizeTitle: entry.spinPrizeId ? prizeTitleById.get(entry.spinPrizeId) : undefined,
@@ -238,13 +251,35 @@ export async function getUserSpinResultsForEvent(eventId, userId) {
238
251
  const entries = await eventEntryRepository.getUserSpinResults(event.id, userId);
239
252
  return entries.map((entry) => ({
240
253
  id: entry.id,
241
- userDisplayName: entry.userDisplayName,
254
+ userDisplayName: eventEntryRepository.maskedEntry(entry).userDisplayName,
242
255
  isGuest: false,
243
256
  spinPrizeId: entry.spinPrizeId,
244
257
  spinPrizeTitle: entry.spinPrizeId ? prizeTitleById.get(entry.spinPrizeId) : undefined,
258
+ // Safe here and ONLY here — `userId` comes from a verified session, so
259
+ // this is the caller's own prize. The public feed must not carry it.
260
+ spinPrizeCouponCode: entry.spinPrizeCouponCode ?? undefined,
245
261
  spinWonAt: entry.spinWonAt ? new Date(entry.spinWonAt).toISOString() : undefined,
246
262
  }));
247
263
  }
264
+ /**
265
+ * Has this signed-in user already entered this event?
266
+ *
267
+ * Exists so the participate page can render a raffle's already-entered state on
268
+ * FIRST PAINT. The form tracked it in `useState(0)`, which a reload reset — so
269
+ * a returning entrant saw an inviting empty form, submitted, and was refused.
270
+ * A refusal the page could have predicted is a worse experience than a form
271
+ * that says so up front.
272
+ *
273
+ * Reuses `hasUserEntered` (one `(eventId, userId)` equality pair, an index that
274
+ * is already proven by `countUserEntries`) rather than counting, because the
275
+ * limit is one — see `RAFFLE_MAX_ENTRIES_PER_USER`.
276
+ */
277
+ export async function hasUserEnteredEvent(eventId, userId) {
278
+ const event = await eventRepository.findByIdOrSlug(eventId);
279
+ if (!event)
280
+ return false;
281
+ return eventEntryRepository.hasUserEntered(event.id, userId);
282
+ }
248
283
  /**
249
284
  * Per-option vote tally for a poll event — replaces the voter-ranked
250
285
  * leaderboard for event.type === "poll", where ranking "who happened to
@@ -398,6 +433,33 @@ export async function enterEvent(eventId, input, user, request) {
398
433
  throw new ValidationError(ERROR_MESSAGES.EVENT.ALREADY_ENTERED);
399
434
  }
400
435
  }
436
+ /*
437
+ * 🛑 The same hole, one event type over: a signed-in user could enter the
438
+ * SAME RAFFLE as many times as they liked, and every entry joined the
439
+ * winner pool.
440
+ *
441
+ * Guests were deduped by hashed IP, surveys enforced `maxEntriesPerUser`,
442
+ * polls got the guard documented above — and raffle, the one type where a
443
+ * duplicate entry is a direct and quantifiable unfairness, had none. The
444
+ * client-side guard was inert in two independent ways: `submissionCount` is
445
+ * `useState(0)` that is never hydrated from the server, so a reload resets
446
+ * it; and `maxEntriesPerUser` was not passed to `EventRaffleEntryForm` at
447
+ * all, so it defaulted to 1 while nothing enforced it.
448
+ *
449
+ * The limit is `RAFFLE_MAX_ENTRIES_PER_USER`, shared with the form that
450
+ * renders "you've used all N entry slots" — there is no raffle config to
451
+ * consult (`maxEntriesPerUser` lives on `SurveyConfig` only), and two
452
+ * mirrored literals is how the UI came to claim a limit nothing enforced.
453
+ *
454
+ * Reuses the same `countUserEntries` as the survey and poll paths, so the
455
+ * read cost and the `(eventId, userId)` index it needs are already proven.
456
+ */
457
+ if (user && event.type === "raffle") {
458
+ const priorEntries = await eventEntryRepository.countUserEntries(resolvedEventId, user.uid);
459
+ if (priorEntries >= RAFFLE_MAX_ENTRIES_PER_USER) {
460
+ throw new ValidationError(ERROR_MESSAGES.EVENT.ALREADY_ENTERED);
461
+ }
462
+ }
401
463
  if (event.type === "poll" && event.pollConfig) {
402
464
  const validOptionIds = event.pollConfig.options.map((o) => o.id);
403
465
  const votes = input.pollVotes ?? [];
@@ -421,8 +483,25 @@ export async function enterEvent(eventId, input, user, request) {
421
483
  }
422
484
  }
423
485
  }
486
+ /*
487
+ * 🛑 `raffle` was absent here, and that is what kept the raffle draw broken
488
+ * even after its pool query was fixed.
489
+ *
490
+ * `runTriggerEventRaffle` builds its pool from `reviewStatus == "approved"`.
491
+ * With raffle missing from this list every live entry was written `pending`,
492
+ * so a freshly-entered raffle still drew from an empty pool — the fix to the
493
+ * query alone would have changed nothing a user could see.
494
+ *
495
+ * A raffle entry is a name and an optional message. There is no submitted
496
+ * content to moderate, which is why it belongs with `poll` and `feedback`
497
+ * rather than behind `survey`'s `entryReviewRequired` opt-in. An admin can
498
+ * still FLAG an individual entry, and flagging correctly drops it from the
499
+ * pool — that is the moderation path, not a standing review queue of every
500
+ * entrant.
501
+ */
424
502
  const autoApprove = event.type === "poll" ||
425
503
  event.type === "feedback" ||
504
+ event.type === "raffle" ||
426
505
  (event.type === "survey" &&
427
506
  !event.surveyConfig?.entryReviewRequired);
428
507
  const reviewStatus = autoApprove ? "approved" : "pending";
@@ -47,7 +47,7 @@ export async function GET(_request, context) {
47
47
  event.pollConfig?.resultsVisibility === "always") {
48
48
  const allEntries = await entriesRepo.findAll({
49
49
  filters: `eventId==${id}`,
50
- perPage: 1000,
50
+ pageSize: 1000,
51
51
  });
52
52
  const voteCounts = {};
53
53
  for (const entry of allEntries.data) {
@@ -70,7 +70,7 @@ export async function GET(_request, context) {
70
70
  filters: `eventId==${id},reviewStatus==approved`,
71
71
  sort: "points",
72
72
  order: "desc",
73
- perPage: 10,
73
+ pageSize: 10,
74
74
  });
75
75
  leaderboard = leaderboardResult.data.map((entry) => {
76
76
  const { userId: _userId, userEmail: _userEmail, ...rest } = entry;
@@ -3,11 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { normalizeError } from "../../../errors/normalize.js";
4
4
  import { useApiMutation } from "@mohasinac/appkit/client";
5
5
  import React, { useEffect } from "react";
6
+ import { useTranslations } from "next-intl";
6
7
  import { useQuery } from "@tanstack/react-query";
7
8
  import { Alert, Button, Div, Grid, Heading, Input, Label, RichTextEditor, Row, Select, Stack, StackedViewShell, TagInput, Text, Textarea, Toggle, } from "../../../ui/index.js";
8
9
  import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary } from "../../../ui/forms/index.js";
9
10
  import { apiClient } from "../../../http/index.js";
10
11
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints.js";
12
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
13
+ import { toUserMessage } from "../../../errors/error-display-map.js";
11
14
  import { ALL_EVENT_TYPES, EVENT_TYPE_LABELS } from "../types/index.js";
12
15
  import { SectionForm, useSectionFormNav } from "../../shell/index.js";
13
16
  import { eventDraftSchema } from "../schemas/event-form.js";
@@ -214,13 +217,16 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
214
217
  const [slugManual, setSlugManual] = React.useState(false);
215
218
  const [saveMessage, setSaveMessage] = React.useState(null);
216
219
  const [triggerMessage, setTriggerMessage] = React.useState(null);
220
+ // Root translator (no namespace) — `toUserMessage` passes fully-qualified
221
+ // `errors.codes.*` keys and resolves them itself. Same precedent as
222
+ // `PlaceBidFormClient` and `SpinWheelView`.
223
+ const t = useTranslations();
217
224
  const [raffleWinnerName, setRaffleWinnerName] = React.useState("");
218
225
  const [raffleEntryCount, setRaffleEntryCount] = React.useState(null);
219
226
  const [raffleGithubUrl, setRaffleGithubUrl] = React.useState("");
220
227
  const { shellCtx, setFieldError, validate } = useFormShellState(eventDraftSchema);
221
228
  useEffect(() => {
222
229
  validate(draft);
223
- // eslint-disable-next-line react-hooks/exhaustive-deps
224
230
  }, [draft, validate]);
225
231
  const update = React.useCallback((partial) => {
226
232
  setDraft((prev) => ({ ...prev, ...partial }));
@@ -338,7 +344,20 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
338
344
  setRaffleEntryCount(data.raffleEntryCount);
339
345
  },
340
346
  onError: (err) => {
341
- setTriggerMessage(err instanceof Error ? err.message : "Trigger failed");
347
+ /*
348
+ * 🛑 `err.message` was printed straight into the panel — the shape Root
349
+ * Cause #86 is about, and the ternary was inverted besides: the authored
350
+ * sentence only showed when the value was NOT an Error, i.e. almost
351
+ * never, so the normal case rendered raw server text to an admin.
352
+ *
353
+ * The route now returns a stable `code` for every refusal, so the copy
354
+ * resolves through `errors.codes.*`. The one that matters is
355
+ * `RAFFLE_ALREADY_DRAWN` — "a winner has already been drawn" is a
356
+ * completely different instruction from the old generic failure, and it
357
+ * is what a second click gets now that the draw is transactional.
358
+ */
359
+ const e = normalizeError(err);
360
+ setTriggerMessage(toUserMessage(e.code, t, { fallback: "Could not draw a winner." }));
342
361
  },
343
362
  });
344
363
  const isLoading = saveMutation.isPending || eventQuery.isLoading;
@@ -392,7 +411,7 @@ export function AdminEventEditorView({ eventId, onSaved, embedded, allowedTypes,
392
411
  const isRaffleType = values.type === "raffle" || values.type === "spin_wheel";
393
412
  const showRaffleConfig = isRaffleType || values.hasRaffle;
394
413
  const isSpinMode = values.type === "spin_wheel" || values.raffleType === "spin_wheel";
395
- return (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Raffle & Spin Wheel" }), !isRaffleType && (_jsx(Toggle, { checked: values.hasRaffle, onChange: (v) => onChange({ hasRaffle: v }), label: "Attach a raffle to this event" })), !isRaffleType && !values.hasRaffle && (_jsx(Text, { size: "sm", color: "muted", children: "Enable the toggle above to attach a raffle draw to this event. Not applicable for this event type by default." })), showRaffleConfig && (_jsxs(Stack, { gap: "md", rounded: "xl", border: "default", padding: "md", children: [_jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(Select, { label: "Raffle type", value: values.type === "spin_wheel" ? "spin_wheel" : values.raffleType, options: RAFFLE_TYPE_OPTIONS, onChange: (e) => onChange({ raffleType: e.target.value }), disabled: values.type === "spin_wheel" }), _jsx(Input, { label: "Top N (for top-N raffle types)", type: "number", value: values.raffleTopN, onChange: (e) => onChange({ raffleTopN: e.target.value }), placeholder: "10" })] }), _jsx(Input, { label: "Prize description", value: values.rafflePrize, onChange: (e) => onChange({ rafflePrize: e.target.value }), placeholder: "\u20B92,000 store credit + exclusive Pikachu sticker" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Coupon for winner (optional)" }), _jsx(CouponInlineSelect, { scope: "admin", value: values.rafflePrizeCouponId, onChange: (id) => onChange({ rafflePrizeCouponId: id ?? "" }), placeholder: "Search coupons\u2026", allowCreate: false })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Prize products (optional)" }), _jsx(ProductInlineSelect, { scope: "admin", multiple: true, value: values.rafflePrizeProductIds, onChange: (ids) => onChange({ rafflePrizeProductIds: ids }), placeholder: "Search products to feature as prizes\u2026" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-1", children: "Products shown to winners or displayed as the prize showcase." })] }), isSpinMode && _jsx(SpinPrizesEditor, { values: values, onChange: onChange }), isEdit && (_jsxs(Stack, { gap: "xs", rounded: "lg", padding: "sm", className: CLS_RAFFLE_PANEL, children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", className: CLS_RAFFLE_HEADING, children: raffleWinnerName ? `Winner: ${raffleWinnerName}` : "Raffle not yet triggered" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => triggerRaffleMutation.mutate(), disabled: triggerRaffleMutation.isPending || Boolean(raffleWinnerName), children: triggerRaffleMutation.isPending ? "Triggering…" : raffleWinnerName ? "Already triggered" : "Trigger Raffle Now" })] }), raffleEntryCount !== null && _jsxs(Text, { size: "xs", className: CLS_RAFFLE_BODY, children: ["Pool size: ", raffleEntryCount] }), raffleGithubUrl && _jsxs(Text, { size: "xs", className: `break-all ${CLS_RAFFLE_BODY}`, children: ["Fairness proof: ", raffleGithubUrl] }), triggerMessage && _jsx(Text, { size: "xs", className: CLS_RAFFLE_HEADING, children: triggerMessage })] }))] }))] }));
414
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Raffle & Spin Wheel" }), !isRaffleType && (_jsx(Toggle, { checked: values.hasRaffle, onChange: (v) => onChange({ hasRaffle: v }), label: "Attach a raffle to this event" })), !isRaffleType && !values.hasRaffle && (_jsx(Text, { size: "sm", color: "muted", children: "Enable the toggle above to attach a raffle draw to this event. Not applicable for this event type by default." })), showRaffleConfig && (_jsxs(Stack, { gap: "md", rounded: "xl", border: "default", padding: "md", children: [_jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(Select, { label: "Raffle type", value: values.type === "spin_wheel" ? "spin_wheel" : values.raffleType, options: RAFFLE_TYPE_OPTIONS, onChange: (e) => onChange({ raffleType: e.target.value }), disabled: values.type === "spin_wheel" }), _jsx(Input, { label: "Top N (for top-N raffle types)", type: "number", value: values.raffleTopN, onChange: (e) => onChange({ raffleTopN: e.target.value }), placeholder: "10" })] }), _jsx(Input, { label: "Prize description", value: values.rafflePrize, onChange: (e) => onChange({ rafflePrize: e.target.value }), placeholder: "\u20B92,000 store credit + exclusive Pikachu sticker" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Coupon for winner (optional)" }), _jsx(CouponInlineSelect, { scope: "admin", value: values.rafflePrizeCouponId, onChange: (id) => onChange({ rafflePrizeCouponId: id ?? "" }), placeholder: "Search coupons\u2026", allowCreate: false })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "medium", className: "mb-1", children: "Prize products (optional)" }), _jsx(ProductInlineSelect, { scope: "admin", multiple: true, value: values.rafflePrizeProductIds, onChange: (ids) => onChange({ rafflePrizeProductIds: ids }), placeholder: "Search products to feature as prizes\u2026" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-1", children: "Products shown to winners or displayed as the prize showcase." })] }), isSpinMode && _jsx(SpinPrizesEditor, { values: values, onChange: onChange }), isEdit && (_jsxs(Stack, { gap: "xs", rounded: "lg", padding: "sm", className: CLS_RAFFLE_PANEL, children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", className: CLS_RAFFLE_HEADING, children: raffleWinnerName ? `Winner: ${raffleWinnerName}` : "Raffle not yet triggered" }), _jsx(Button, { type: "button", action: ACTIONS.EVENT["draw-raffle-winner"], size: "sm", onClick: () => triggerRaffleMutation.mutate(), disabled: triggerRaffleMutation.isPending || Boolean(raffleWinnerName), children: triggerRaffleMutation.isPending ? "Triggering…" : raffleWinnerName ? "Already triggered" : "Trigger Raffle Now" })] }), raffleEntryCount !== null && _jsxs(Text, { size: "xs", className: CLS_RAFFLE_BODY, children: ["Pool size: ", raffleEntryCount] }), raffleGithubUrl && _jsxs(Text, { size: "xs", className: `break-all ${CLS_RAFFLE_BODY}`, children: ["Fairness proof: ", raffleGithubUrl] }), triggerMessage && _jsx(Text, { size: "xs", className: CLS_RAFFLE_HEADING, children: triggerMessage })] }))] }))] }));
396
415
  },
397
416
  },
398
417
  ];
@@ -10,7 +10,8 @@ import { ALL_EVENT_TYPES, EVENT_TYPE_LABELS } from "../types/index.js";
10
10
  import { toRecordArray, toRelativeDate, toStringValue, } from "../../admin/hooks/useAdminListingData.js";
11
11
  import { DataListingView } from "../../admin/components/DataListingView.js";
12
12
  import { AdminEventEditorView } from "./AdminEventEditorView.js";
13
- import { ADMIN_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs.js";
13
+ import { ADMIN_BULK_ACTIONS, ROW_ACTION_ID, ROW_ACTION_META } from "../../products/constants/action-defs.js";
14
+ import { apiClient } from "../../../http/index.js";
14
15
  /**
15
16
  * Derived from the `EventType` union (2026-08-24).
16
17
  *
@@ -74,13 +75,36 @@ export function AdminEventsView({ children, ...props }) {
74
75
  label: "Add Event",
75
76
  onClick: ({ openCreatePanel }) => openCreatePanel(),
76
77
  },
77
- // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
78
- buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.events.map((id) => ({
79
- id,
80
- label: ROW_ACTION_META[id].label,
81
- destructive: ROW_ACTION_META[id].destructive,
82
- onClick: () => selection.clearSelection(),
83
- })),
78
+ /*
79
+ * Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
80
+ *
81
+ * 🛑 This used to map every preset id to `onClick: () => clearSelection()`
82
+ * — so the bulk bar rendered a red "Delete" that selected rows, asked
83
+ * nothing, deleted nothing, and then cleared the checkboxes, which reads
84
+ * exactly like a successful delete until the list refreshes. A destructive
85
+ * control that silently no-ops is worse than an absent one (Rule #8).
86
+ *
87
+ * Wired self-contained against `DELETE /api/admin/events/[id]`, following
88
+ * `AdminBundlesView`; the `.filter` keeps `AdminFaqsView`'s discipline that
89
+ * an unwired preset id is OMITTED rather than shown inert, so the next id
90
+ * added to the preset cannot reintroduce this.
91
+ */
92
+ buildBulkActions: (selection) => {
93
+ const handlers = {
94
+ [ROW_ACTION_ID.DELETE]: async () => {
95
+ await Promise.all(selection.selectedIds.map((id) => apiClient.delete(ADMIN_ENDPOINTS.EVENT_BY_ID(id))));
96
+ selection.clearSelection();
97
+ },
98
+ };
99
+ return ADMIN_BULK_ACTIONS.events
100
+ .filter((id) => handlers[id])
101
+ .map((id) => ({
102
+ id,
103
+ label: ROW_ACTION_META[id].label,
104
+ destructive: ROW_ACTION_META[id].destructive,
105
+ onClick: handlers[id],
106
+ }));
107
+ },
84
108
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_EVENT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: TYPE_OPTIONS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })] })),
85
109
  renderEditor: ({ editId, closePanel }) => (_jsx(AdminEventEditorView, { eventId: editId ?? undefined, onSaved: closePanel, embedded: true })),
86
110
  resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Event" : "Edit Event"),
@@ -4,6 +4,39 @@ export interface EventRafflesSectionProps {
4
4
  }
5
5
  /**
6
6
  * Renders a live-raffle/spin-wheel strip on the homepage.
7
- * W1-38 (2026-05-23): fetches events with hasRaffle=true and renders the EventCard grid.
7
+ *
8
+ * ## It was showing the wrong events, and nothing could tell
9
+ *
10
+ * The filter was `hasRaffle == "true"` — and **`hasRaffle` is not in
11
+ * `EventRepository.SIEVE_FIELDS`**. `sieve.ts` sets `throwExceptions: false`,
12
+ * so an unallowlisted field does not raise: `findField` returns undefined and
13
+ * the clause is **silently discarded** (Root Cause #62, EMITTED_BUT_UNFILTERABLE).
14
+ *
15
+ * So this did not return zero rows and it did not disappear. It ran
16
+ * `status == active` sorted `-startsAt` and rendered **the six most
17
+ * recently-started active events of ANY type** — sales, polls, feedback forms —
18
+ * under a heading reading "Live Raffles & Spin Wheels". Every card was a real,
19
+ * live event, which is exactly why it never looked broken.
20
+ *
21
+ * ## Why `type`, and why one query
22
+ *
23
+ * `config.raffleType` is already `"raffle" | "spin_wheel" | "all"` — the config
24
+ * has always spoken the `type` vocabulary and already answers "both belong
25
+ * here", so the title is truthful and `type` is the honest field to match.
26
+ * `type` IS in `SIEVE_FIELDS` and is indexed `(type, status, startsAt DESC)`,
27
+ * so this needs no new composite index.
28
+ *
29
+ * 🛑 The `"all"` case is a PIPE-joined value, not `sieveMultiEq`. Sieve parses
30
+ * `type==a|b` as one OR-group, and the Firebase adapter upgrades a same-field
31
+ * OR-group of equalities into a single Firestore `in` query. `sieveMultiEq`
32
+ * comma-joins instead, and a comma is an AND: `type==raffle,type==spin_wheel`
33
+ * is two equalities on one field, which no document can satisfy (Root
34
+ * Cause #59).
35
+ *
36
+ * Accepted limitation, deliberately: `hasRaffle` is an optional bolt-on that
37
+ * *could* sit on a `sale` event, and such an event would not appear here. No
38
+ * seeded event is shaped that way — all three `hasRaffle: true` rows are
39
+ * already `type: "raffle"` — and a sale with a side raffle belongs in the sale
40
+ * strip. If that changes, the fix is a second clause, not a wider title.
8
41
  */
9
42
  export declare function EventRafflesSection({ config, }: EventRafflesSectionProps): Promise<import("react").JSX.Element | null>;
@@ -12,18 +12,67 @@ import { Container, Grid, Heading, Section, Stack, Text } from "../../../ui/inde
12
12
  import { eventRepository } from "../repository/events.repository.js";
13
13
  import { EventCard } from "./EventCard.js";
14
14
  import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility.js";
15
+ import { EVENT_FIELDS } from "../../../constants/field-names.js";
16
+ /*
17
+ * `config.raffleType`'s two concrete values happen to equal the two
18
+ * `EventType` members this strip spans. Sourced from `EVENT_FIELDS.TYPE_VALUES`
19
+ * rather than written as literals so a rename of either type is a compile
20
+ * error here instead of a clause that matches nothing.
21
+ */
22
+ const EVENT_TYPE_RAFFLE = EVENT_FIELDS.TYPE_VALUES.RAFFLE;
23
+ const EVENT_TYPE_SPIN_WHEEL = EVENT_FIELDS.TYPE_VALUES.SPIN_WHEEL;
15
24
  /**
16
25
  * Renders a live-raffle/spin-wheel strip on the homepage.
17
- * W1-38 (2026-05-23): fetches events with hasRaffle=true and renders the EventCard grid.
26
+ *
27
+ * ## It was showing the wrong events, and nothing could tell
28
+ *
29
+ * The filter was `hasRaffle == "true"` — and **`hasRaffle` is not in
30
+ * `EventRepository.SIEVE_FIELDS`**. `sieve.ts` sets `throwExceptions: false`,
31
+ * so an unallowlisted field does not raise: `findField` returns undefined and
32
+ * the clause is **silently discarded** (Root Cause #62, EMITTED_BUT_UNFILTERABLE).
33
+ *
34
+ * So this did not return zero rows and it did not disappear. It ran
35
+ * `status == active` sorted `-startsAt` and rendered **the six most
36
+ * recently-started active events of ANY type** — sales, polls, feedback forms —
37
+ * under a heading reading "Live Raffles & Spin Wheels". Every card was a real,
38
+ * live event, which is exactly why it never looked broken.
39
+ *
40
+ * ## Why `type`, and why one query
41
+ *
42
+ * `config.raffleType` is already `"raffle" | "spin_wheel" | "all"` — the config
43
+ * has always spoken the `type` vocabulary and already answers "both belong
44
+ * here", so the title is truthful and `type` is the honest field to match.
45
+ * `type` IS in `SIEVE_FIELDS` and is indexed `(type, status, startsAt DESC)`,
46
+ * so this needs no new composite index.
47
+ *
48
+ * 🛑 The `"all"` case is a PIPE-joined value, not `sieveMultiEq`. Sieve parses
49
+ * `type==a|b` as one OR-group, and the Firebase adapter upgrades a same-field
50
+ * OR-group of equalities into a single Firestore `in` query. `sieveMultiEq`
51
+ * comma-joins instead, and a comma is an AND: `type==raffle,type==spin_wheel`
52
+ * is two equalities on one field, which no document can satisfy (Root
53
+ * Cause #59).
54
+ *
55
+ * Accepted limitation, deliberately: `hasRaffle` is an optional bolt-on that
56
+ * *could* sit on a `sale` event, and such an event would not appear here. No
57
+ * seeded event is shaped that way — all three `hasRaffle: true` rows are
58
+ * already `type: "raffle"` — and a sale with a side raffle belongs in the sale
59
+ * strip. If that changes, the fix is a second clause, not a wider title.
18
60
  */
19
61
  export async function EventRafflesSection({ config, }) {
20
62
  const title = config.title ?? "Live Raffles & Spin Wheels";
21
63
  const subtitle = config.subtitle ?? "Participate in community events and win prizes";
22
- const limit = 6;
64
+ // `maxItems` was declared in the config, set by the seed, and ignored here in
65
+ // favour of a hardcoded 6 (`audit-homepage-config-coverage` passed only
66
+ // because the component *mentions* `config`).
67
+ const limit = config.maxItems && config.maxItems > 0 ? config.maxItems : 6;
68
+ const raffleType = config.raffleType ?? "all";
69
+ const typeClause = raffleType === "all"
70
+ ? sieveFilter(EVENT_FIELDS.TYPE, SIEVE_OP.EQ, `${EVENT_TYPE_RAFFLE}|${EVENT_TYPE_SPIN_WHEEL}`)
71
+ : sieveFilter(EVENT_FIELDS.TYPE, SIEVE_OP.EQ, raffleType);
23
72
  let events = [];
24
73
  try {
25
74
  const result = await eventRepository.list({
26
- filters: sieveAnd(sieveFilter("hasRaffle", SIEVE_OP.EQ, "true"), sieveFilter("status", SIEVE_OP.EQ, "active")),
75
+ filters: sieveAnd(typeClause, sieveFilter(EVENT_FIELDS.STATUS, SIEVE_OP.EQ, EVENT_FIELDS.STATUS_VALUES.ACTIVE)),
27
76
  sorts: sortBy("startsAt", "DESC"),
28
77
  pageSize: limit,
29
78
  });
@@ -1,7 +1,7 @@
1
- import type { SpinPrize } from "../types";
1
+ import type { DisplaySpinPrize } from "../types";
2
2
  export interface SpinWheelViewProps {
3
3
  eventId: string;
4
- prizes: SpinPrize[];
4
+ prizes: DisplaySpinPrize[];
5
5
  alreadyUsed?: boolean;
6
6
  initialPrizeId?: string;
7
7
  initialCouponCode?: string;
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useMemo, useState } from "react";
4
+ import { useTranslations } from "next-intl";
4
5
  import { Button, ClaimCouponButton, Div, Heading, LoginRequiredModal, Row, Span, Stack, Text, useToast } from "../../../ui/index.js";
5
6
  import { isAuthError } from "../../../utils/auth-error.js";
6
7
  import { normalizeError } from "../../../errors/normalize.js";
@@ -32,6 +33,10 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
32
33
  const [error, setError] = useState(null);
33
34
  const [showLoginModal, setShowLoginModal] = useState(false);
34
35
  const { showToast } = useToast();
36
+ // Root translator (no namespace) — `toUserMessage` passes fully-qualified
37
+ // `errors.codes.*` keys and resolves them itself, so a missing key degrades
38
+ // to generic copy rather than throwing MISSING_MESSAGE at render time.
39
+ const t = useTranslations();
35
40
  const now = Date.now();
36
41
  const startMs = windowStart ? new Date(windowStart).getTime() : null;
37
42
  const endMs = windowEnd ? new Date(windowEnd).getTime() : null;
@@ -51,6 +56,12 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
51
56
  setResultCoupon(result.spinPrizeCouponCode);
52
57
  }
53
58
  else {
59
+ /*
60
+ * A 2xx with no prize. The server now refuses with a real status and
61
+ * code for every reason it knows (`reasonToHttp`), so reaching here
62
+ * means the response was shaped unexpectedly rather than refused —
63
+ * the generic fallback is the honest answer.
64
+ */
54
65
  setError(l.errorFallback);
55
66
  }
56
67
  }, ANIMATION_MS);
@@ -62,13 +73,39 @@ export function SpinWheelView({ eventId, prizes, alreadyUsed, initialPrizeId, in
62
73
  setShowLoginModal(true);
63
74
  }
64
75
  else {
65
- setError(l.errorFallback);
66
- showToast(toUserMessage(e.code, undefined, { fallback: l.errorFallback }), "error");
76
+ /*
77
+ * 🛑 Show the SERVER'S reason, not one sentence for all of them.
78
+ *
79
+ * Both lines below used to be `l.errorFallback` — "Spin failed. Please
80
+ * try again." — so a closed window, an exhausted per-user cap, a
81
+ * misconfigured wheel and a genuine fault were indistinguishable, and
82
+ * the retry that sentence invites could never succeed for most of them.
83
+ *
84
+ * `t` is the ROOT, unnamespaced translator: `toUserMessage` resolves
85
+ * absolute `errors.codes.*` keys, which a namespaced translator cannot
86
+ * reach. Omitting it (as this did) makes `toUserMessage` return the
87
+ * fallback unconditionally — so the per-reason copy existed in
88
+ * `messages/en.json` and was unreachable. Same reasoning as
89
+ * `PlaceBidFormClient`.
90
+ *
91
+ * This only resolves because `onSpin` now throws an `ApiError` carrying
92
+ * the response's `code` and `status`; while it threw a native `Error`,
93
+ * `normalizeError` produced no code and `isAuthError` saw no status, so
94
+ * a 401 never opened the login modal either.
95
+ */
96
+ const message = toUserMessage(e.code, t, { fallback: l.errorFallback });
97
+ setError(message);
98
+ showToast(message, "error");
67
99
  }
68
100
  }
69
- }, [disabled, eventId, l.errorFallback, onSpin, showToast]);
101
+ }, [disabled, eventId, l.errorFallback, onSpin, showToast, t]);
102
+ /*
103
+ * Looked up in the FULL prize list, not `activePrizes`. A prize deactivated
104
+ * after somebody won it would otherwise render neither the prize box nor an
105
+ * error — the win simply vanished from the page.
106
+ */
70
107
  const wonPrize = resultPrizeId
71
- ? activePrizes.find((p) => p.id === resultPrizeId)
108
+ ? prizes.find((p) => p.id === resultPrizeId)
72
109
  : undefined;
73
110
  return (_jsxs(Stack, { gap: "md", children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to spin the wheel. Please log in or create an account to continue." }), _jsxs(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: ["\uD83C\uDFA1 ", l.heading] }), _jsxs(Div, { className: CLS_WHEEL, "aria-label": "Spin wheel", children: [_jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", style: {
74
111
  animation: spinning
@@ -1,10 +1,22 @@
1
1
  import type { FirebaseSieveFields, FirebaseSieveResult, SieveModel } from "../../../providers/db-firebase";
2
2
  import { BaseRepository } from "../../../providers/db-firebase";
3
- import { type EventEntryCreateInput, type EventEntryDocument } from "../schemas";
3
+ import { type EventEntryCreateInput, type EventEntryDocument, type EventEntryPlain } from "../schemas";
4
4
  declare class EventEntryRepository extends BaseRepository<EventEntryDocument> {
5
5
  static readonly SIEVE_FIELDS: FirebaseSieveFields;
6
6
  constructor();
7
- protected mapDoc<D = EventEntryDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
7
+ /** Plaintext PII. **Opt-in and named** — shows up in review and in a grep. */
8
+ revealEntry(doc: EventEntryDocument): EventEntryPlain;
9
+ /** As `revealEntry`, for a list read. */
10
+ revealEntrys(docs: readonly EventEntryDocument[]): EventEntryPlain[];
11
+ /**
12
+ * Masked PII — **the default for any surface that only needs to identify a
13
+ * row to a human.** Decrypts first, then masks: the mask helpers refuse
14
+ * ciphertext by design, so passing a stored field straight in would return
15
+ * "Anonymous" for every row.
16
+ */
17
+ maskedEntry(doc: EventEntryDocument): EventEntryPlain;
18
+ /** As `maskedEntry`, for a list read. */
19
+ maskedEntrys(docs: readonly EventEntryDocument[]): EventEntryPlain[];
8
20
  listForEvent(eventId: string, model: SieveModel): Promise<FirebaseSieveResult<EventEntryDocument>>;
9
21
  listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<EventEntryDocument>>;
10
22
  /**
@@ -2,7 +2,7 @@ import { normalizeError } from "../../../errors/normalize.js";
2
2
  import { DatabaseError } from "../../../errors/index.js";
3
3
  import { serverLogger } from "../../../monitoring/index.js";
4
4
  import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase/index.js";
5
- import { decryptPiiFields, encryptPiiFields, EVENT_ENTRY_PII_FIELDS, } from "../../../security/index.js";
5
+ import { encryptPiiFields, maskPii, revealPii, EVENT_ENTRY_PII_FIELDS, } from "../../../security/index.js";
6
6
  import { EVENT_ENTRIES_COLLECTION, EVENT_ENTRY_FIELDS, } from "../schemas/index.js";
7
7
  /** Extracted from getExportReport's poll branch to stay under the max nesting depth. */
8
8
  function formatPollResultLines(pollOptions, countByOption, total) {
@@ -20,11 +20,38 @@ class EventEntryRepository extends BaseRepository {
20
20
  constructor() {
21
21
  super(EVENT_ENTRIES_COLLECTION);
22
22
  }
23
- mapDoc(snap) {
24
- const raw = super.mapDoc(snap);
25
- return decryptPiiFields(raw, [
26
- ...EVENT_ENTRY_PII_FIELDS,
27
- ]);
23
+ /*
24
+ * 🛑 `mapDoc` is NOT overridden, and that absence IS the change.
25
+ *
26
+ * It used to decrypt on every read, so ciphertext never left the database
27
+ * and every caller handled plaintext without ever saying so. Following
28
+ * `SiteSettingsRepository`: reads return what Firestore holds, and plaintext
29
+ * requires one of the two named methods below.
30
+ *
31
+ * A convention would not hold this. `EventEntryDocument`'s PII fields are
32
+ * `Encrypted<string>`, assignable to nothing that wants a plain `string`, so
33
+ * a reader that skips both accessors is a compile error naming file and line.
34
+ */
35
+ /** Plaintext PII. **Opt-in and named** — shows up in review and in a grep. */
36
+ revealEntry(doc) {
37
+ return revealPii(doc, EVENT_ENTRY_PII_FIELDS);
38
+ }
39
+ /** As `revealEntry`, for a list read. */
40
+ revealEntrys(docs) {
41
+ return docs.map((d) => this.revealEntry(d));
42
+ }
43
+ /**
44
+ * Masked PII — **the default for any surface that only needs to identify a
45
+ * row to a human.** Decrypts first, then masks: the mask helpers refuse
46
+ * ciphertext by design, so passing a stored field straight in would return
47
+ * "Anonymous" for every row.
48
+ */
49
+ maskedEntry(doc) {
50
+ return maskPii(doc, EVENT_ENTRY_PII_FIELDS);
51
+ }
52
+ /** As `maskedEntry`, for a list read. */
53
+ maskedEntrys(docs) {
54
+ return docs.map((d) => this.maskedEntry(d));
28
55
  }
29
56
  async listForEvent(eventId, model) {
30
57
  return this.sieveQuery(model, EventEntryRepository.SIEVE_FIELDS, {
@@ -150,7 +177,14 @@ class EventEntryRepository extends BaseRepository {
150
177
  // Aggregate per user: sum all approved entry points
151
178
  const byUser = new Map();
152
179
  for (const doc of snapshot.docs) {
153
- const raw = decryptPiiFields({ id: doc.id, ...doc.data() }, [...EVENT_ENTRY_PII_FIELDS]);
180
+ /*
181
+ * MASKED, not decrypted. This builds a public LEADERBOARD, so it needs
182
+ * a name to show and has no business holding the real one — the rows
183
+ * go straight into a response. `maskPii` decrypts and then masks, which
184
+ * is required here: `maskName` refuses ciphertext by design, so masking
185
+ * the stored value directly would render every competitor "Anonymous".
186
+ */
187
+ const raw = maskPii({ id: doc.id, ...doc.data() }, EVENT_ENTRY_PII_FIELDS);
154
188
  if (!raw.userId)
155
189
  continue;
156
190
  const existing = byUser.get(raw.userId);
@@ -6,6 +6,7 @@ import type { JsonValue } from "@mohasinac/appkit";
6
6
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
7
7
  import type { EventType, EventStatus, EntryReviewStatus, SaleConfig, OfferConfig, PollConfig, SurveyConfig, FeedbackConfig, RaffleType, SpinPrize } from "../types";
8
8
  import type { LotteryConfig } from "../../lottery/types";
9
+ import type { Encrypted, Plaintext } from "../../../security/encrypted";
9
10
  export declare const EVENTS_COLLECTION: "events";
10
11
  export declare const EVENT_ENTRIES_COLLECTION: "eventEntries";
11
12
  export interface EventDocument extends BaseDocument {
@@ -68,12 +69,24 @@ export interface EventDocument extends BaseDocument {
68
69
  };
69
70
  createdBy: string;
70
71
  }
72
+ /**
73
+ * `EventEntryDocument` with its PII as plain `string` — what the named repository
74
+ * accessors return, and what a WRITE path accepts (the repository encrypts).
75
+ *
76
+ * `Plaintext<T>` is the one definition of this shape; see `security/encrypted.ts`.
77
+ * It is deliberately NOT distinguishable from a MASKED value — `maskPii`
78
+ * returns plain strings too, and no type can express "these characters are
79
+ * asterisks". The distinction is which named method you called.
80
+ */
81
+ export type EventEntryPlain = Plaintext<EventEntryDocument>;
71
82
  export type EventEntryDocument = {
72
83
  id: string;
73
84
  eventId: string;
74
85
  userId?: string;
75
- userDisplayName?: string;
76
- userEmail?: string;
86
+ createdAt?: Date;
87
+ updatedAt?: Date;
88
+ userDisplayName?: Encrypted<string>;
89
+ userEmail?: Encrypted<string>;
77
90
  pollVotes?: string[];
78
91
  pollComment?: string;
79
92
  formResponses?: Record<string, JsonValue>;
@@ -91,7 +104,7 @@ export type EventEntryDocument = {
91
104
  spinPrizeId?: string;
92
105
  spinPrizeCouponCode?: string | null;
93
106
  spinWonAt?: Date;
94
- ipAddress?: string;
107
+ ipAddress?: Encrypted<string>;
95
108
  /**
96
109
  * HMAC-SHA256 blind index of a guest (non-logged-in) participant's IP,
97
110
  * scoped per event — `hmacBlindIndex(eventId + ":" + rawIp)`. Set only
@@ -182,7 +195,7 @@ export declare const EVENT_ENTRY_FIELDS: {
182
195
  };
183
196
  export type EventCreateInput = Omit<EventDocument, "id" | "createdAt" | "updatedAt" | "stats">;
184
197
  export type EventUpdateInput = Partial<Omit<EventDocument, "id" | "createdAt" | "createdBy">>;
185
- export type EventEntryCreateInput = Omit<EventEntryDocument, "id" | "submittedAt">;
198
+ export type EventEntryCreateInput = Omit<EventEntryPlain, "id" | "submittedAt">;
186
199
  export declare const eventQueryHelpers: {
187
200
  readonly active: () => readonly ["status", "==", EventStatus];
188
201
  readonly byType: (type: EventType) => readonly ["type", "==", EventType];