@mohasinac/appkit 4.26.0 → 4.28.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 (277) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
  3. package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
  4. package/dist/_internal/server/features/bundles/data.d.ts +19 -0
  5. package/dist/_internal/server/features/bundles/data.js +32 -0
  6. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  7. package/dist/_internal/server/features/bundles/index.js +1 -1
  8. package/dist/_internal/server/features/checkout/actions.js +6 -4
  9. package/dist/_internal/server/features/classified/metadata.js +3 -1
  10. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  11. package/dist/_internal/server/features/live/metadata.js +3 -1
  12. package/dist/_internal/server/features/refunds/actions.js +3 -2
  13. package/dist/_internal/server/features/reviews/actions.js +3 -2
  14. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  15. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  16. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  17. package/dist/_internal/shared/actions/action-registry.js +14 -0
  18. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  19. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  20. package/dist/_internal/shared/features/checkout/config.js +6 -3
  21. package/dist/client.d.ts +32 -2
  22. package/dist/client.js +37 -2
  23. package/dist/constants/api-endpoints.d.ts +21 -3
  24. package/dist/constants/api-endpoints.js +14 -1
  25. package/dist/constants/field-names.d.ts +2 -2
  26. package/dist/constants/field-names.js +2 -2
  27. package/dist/core/newsletter.repository.js +14 -1
  28. package/dist/features/about/components/FAQPageView.js +1 -1
  29. package/dist/features/about/components/FeesView.js +1 -1
  30. package/dist/features/account/components/AddressForm.d.ts +2 -0
  31. package/dist/features/account/components/AddressForm.js +5 -1
  32. package/dist/features/account/components/UserBidsView.js +1 -1
  33. package/dist/features/account/components/UserSidebar.js +1 -1
  34. package/dist/features/account/components/UserSupportView.js +24 -2
  35. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  36. package/dist/features/account/hooks/useAddresses.js +4 -1
  37. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  38. package/dist/features/account/hooks/useProfile.js +1 -1
  39. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  40. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  41. package/dist/features/admin/actions/chat-actions.js +5 -1
  42. package/dist/features/admin/components/AdminAdsView.js +1 -1
  43. package/dist/features/admin/components/AdminBidsView.js +6 -1
  44. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  45. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  46. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  47. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  48. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  49. package/dist/features/admin/components/AdminSidebar.js +1 -1
  50. package/dist/features/admin/components/AdminViewCards.js +2 -2
  51. package/dist/features/admin/components/DataTable.js +2 -2
  52. package/dist/features/admin/schemas/firestore.js +9 -3
  53. package/dist/features/auctions/actions/bid-actions.js +45 -4
  54. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  55. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  56. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  57. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  58. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  59. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  60. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  61. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  62. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  63. package/dist/features/auth/hooks/useAuth.js +27 -1
  64. package/dist/features/auth/permissions/constants.d.ts +1 -1
  65. package/dist/features/auth/permissions/constants.js +3 -0
  66. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  67. package/dist/features/auth/repository/session.repository.js +23 -0
  68. package/dist/features/auth/repository/user.repository.js +13 -2
  69. package/dist/features/blog/components/BlogListView.js +2 -2
  70. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  71. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  72. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  73. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  74. package/dist/features/cart/components/index.d.ts +1 -1
  75. package/dist/features/cart/components/index.js +1 -1
  76. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  77. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  78. package/dist/features/cart/utils/line-total.d.ts +25 -0
  79. package/dist/features/cart/utils/line-total.js +26 -0
  80. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  81. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  82. package/dist/features/categories/components/CategoryTree.js +2 -2
  83. package/dist/features/contact/components/ContactForm.js +1 -1
  84. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  85. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  86. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  87. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  88. package/dist/features/events/components/EventRafflesSection.js +14 -2
  89. package/dist/features/events/components/EventStatusBadge.js +1 -1
  90. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  91. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  92. package/dist/features/faq/components/ContactCTA.js +1 -1
  93. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  94. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  95. package/dist/features/faq/repository/faqs.repository.js +64 -54
  96. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  97. package/dist/features/faq/schemas/firestore.js +2 -2
  98. package/dist/features/faq/schemas/index.d.ts +3 -3
  99. package/dist/features/faq/schemas/index.js +1 -1
  100. package/dist/features/filters/FilterFacetSection.js +2 -2
  101. package/dist/features/forms/Toggle.js +1 -1
  102. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  103. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  104. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  105. package/dist/features/homepage/components/BrandsSection.js +1 -1
  106. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  107. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  108. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  109. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  110. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  111. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  112. package/dist/features/homepage/lib/section-renderer.js +2 -1
  113. package/dist/features/layout/AppLayoutShell.js +5 -5
  114. package/dist/features/layout/BottomActions.js +5 -5
  115. package/dist/features/layout/BottomSheet.js +1 -1
  116. package/dist/features/layout/MainNavbar.js +1 -1
  117. package/dist/features/layout/NavbarLayout.js +1 -1
  118. package/dist/features/layout/TitleBarLayout.js +3 -3
  119. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  120. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  121. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  122. package/dist/features/lottery/schemas/config-write.js +139 -0
  123. package/dist/features/media/MediaPickerModal.js +3 -1
  124. package/dist/features/media/MediaVideo.js +4 -2
  125. package/dist/features/media/components/MediaSlider.js +1 -1
  126. package/dist/features/orders/components/OrdersList.js +2 -2
  127. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  128. package/dist/features/payments/repository/payout.repository.js +14 -6
  129. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  130. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  131. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  132. package/dist/features/products/columns/productTableColumns.js +3 -3
  133. package/dist/features/products/components/CompareOverlay.js +1 -1
  134. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  135. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  136. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  137. package/dist/features/products/components/ProductDetailActions.js +11 -7
  138. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  139. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  140. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  141. package/dist/features/products/components/ProductFilters.js +15 -8
  142. package/dist/features/products/components/ProductGrid.js +8 -17
  143. package/dist/features/products/components/ProductTabs.js +7 -4
  144. package/dist/features/products/components/ProductTabsShell.js +2 -7
  145. package/dist/features/products/constants/action-defs.d.ts +21 -4
  146. package/dist/features/products/constants/action-defs.js +7 -3
  147. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  148. package/dist/features/products/repository/products.repository.d.ts +1 -1
  149. package/dist/features/products/repository/products.repository.js +27 -13
  150. package/dist/features/products/schemas/firestore.d.ts +3 -2
  151. package/dist/features/products/schemas/firestore.js +1 -1
  152. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  153. package/dist/features/reviews/actions/review-actions.js +14 -3
  154. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  155. package/dist/features/reviews/components/ReviewModal.js +1 -1
  156. package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
  157. package/dist/features/reviews/repository/reviews.repository.js +53 -13
  158. package/dist/features/reviews/schemas/firestore.d.ts +6 -0
  159. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  160. package/dist/features/search/components/Search.js +3 -3
  161. package/dist/features/search/components/SearchResultsSection.js +1 -1
  162. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  163. package/dist/features/seller/components/SellerBidsView.js +6 -1
  164. package/dist/features/seller/components/SellerSidebar.js +1 -1
  165. package/dist/features/shell/SectionForm.d.ts +21 -1
  166. package/dist/features/shell/SectionForm.js +15 -3
  167. package/dist/features/shell/build-sections.js +10 -1
  168. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  169. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  170. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  171. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  172. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  173. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  174. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  175. package/dist/features/stores/actions/store-query-actions.js +25 -1
  176. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  177. package/dist/features/stores/components/StoreHeader.js +1 -1
  178. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  179. package/dist/features/support/repository/support.repository.d.ts +12 -0
  180. package/dist/features/support/repository/support.repository.js +15 -0
  181. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  182. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  183. package/dist/features/tester/seed-data/products-tester-seed-data.js +108 -2
  184. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -13
  185. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  186. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  187. package/dist/features/wishlist/components/index.d.ts +2 -1
  188. package/dist/features/wishlist/components/index.js +1 -1
  189. package/dist/index.d.ts +10 -3
  190. package/dist/index.js +19 -4
  191. package/dist/monitoring/error-tracking.js +39 -5
  192. package/dist/monitoring/server-logger.js +70 -32
  193. package/dist/next/components/ErrorView.js +1 -1
  194. package/dist/next/routing/route-map.d.ts +39 -7
  195. package/dist/next/routing/route-map.js +34 -4
  196. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  197. package/dist/providers/db-firebase/base.repository.js +56 -9
  198. package/dist/react/ErrorBoundary.js +9 -1
  199. package/dist/react/contexts/SessionContext.js +6 -2
  200. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  201. package/dist/react/hooks/useAuthGate.js +86 -7
  202. package/dist/schemas/registry.d.ts +3 -3
  203. package/dist/security/index.d.ts +1 -1
  204. package/dist/security/index.js +1 -1
  205. package/dist/security/pii-encrypt.d.ts +13 -5
  206. package/dist/security/pii-encrypt.js +132 -6
  207. package/dist/security/pii-schemas.d.ts +20 -4
  208. package/dist/security/pii-schemas.js +23 -5
  209. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  210. package/dist/seed/_helpers/product-search-txt.js +33 -0
  211. package/dist/seed/faq-seed-data.d.ts +16 -0
  212. package/dist/seed/faq-seed-data.js +22 -1
  213. package/dist/seed/products-art-seed-data.js +49 -7
  214. package/dist/seed/products-auctions-seed-data.js +12 -4
  215. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  216. package/dist/seed/products-classifieds-seed-data.js +65 -9
  217. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  218. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  219. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  220. package/dist/seed/products-live-items-seed-data.js +33 -5
  221. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  222. package/dist/seed/products-preorders-seed-data.js +6 -2
  223. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  224. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  225. package/dist/seed/products-standard-seed-data.js +6 -2
  226. package/dist/seed/products-stickers-seed-data.js +49 -7
  227. package/dist/seed/site-settings-seed-data.js +5 -3
  228. package/dist/seo/json-ld.d.ts +44 -2
  229. package/dist/seo/json-ld.js +57 -11
  230. package/dist/server.d.ts +1 -1
  231. package/dist/server.js +1 -1
  232. package/dist/styles.css +368 -90
  233. package/dist/tailwind-utilities.css +1 -1
  234. package/dist/tokens/index.d.ts +1 -1
  235. package/dist/tokens/index.js +1 -1
  236. package/dist/tokens/themes/default-dark.js +6 -0
  237. package/dist/tokens/themes/default-light.js +5 -0
  238. package/dist/tokens/tokens.css +84 -0
  239. package/dist/ui/DataTable.js +1 -1
  240. package/dist/ui/DataTable.style.css +3 -3
  241. package/dist/ui/components/BulkActionBar.style.css +1 -3
  242. package/dist/ui/components/Button.js +15 -6
  243. package/dist/ui/components/Dropdown.style.css +1 -4
  244. package/dist/ui/components/FilterChipGroup.js +1 -1
  245. package/dist/ui/components/FilterDrawer.style.css +1 -1
  246. package/dist/ui/components/Icon.d.ts +53 -0
  247. package/dist/ui/components/Icon.js +41 -0
  248. package/dist/ui/components/IconButton.d.ts +13 -0
  249. package/dist/ui/components/IconButton.js +13 -0
  250. package/dist/ui/components/IconButton.style.css +18 -10
  251. package/dist/ui/components/ListingLayout.style.css +2 -10
  252. package/dist/ui/components/ListingToolbar.js +6 -6
  253. package/dist/ui/components/Modal.js +1 -1
  254. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  255. package/dist/ui/components/Pagination.style.css +1 -6
  256. package/dist/ui/components/RichTextEditor.js +1 -1
  257. package/dist/ui/components/RowActionMenu.style.css +1 -1
  258. package/dist/ui/components/SideDrawer.style.css +1 -1
  259. package/dist/ui/components/Slider.js +1 -1
  260. package/dist/ui/components/TabBarShell.d.ts +78 -0
  261. package/dist/ui/components/TabBarShell.js +95 -0
  262. package/dist/ui/components/Tabs.js +12 -8
  263. package/dist/ui/components/Tabs.style.css +249 -41
  264. package/dist/ui/components/ViewToggle.style.css +1 -5
  265. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  266. package/dist/ui/components/tabs-overflow.js +142 -0
  267. package/dist/ui/icons/icon-registry.d.ts +38 -0
  268. package/dist/ui/icons/icon-registry.js +37 -0
  269. package/dist/ui/index.d.ts +6 -0
  270. package/dist/ui/index.js +3 -0
  271. package/dist/utils/index.d.ts +1 -1
  272. package/dist/utils/index.js +1 -1
  273. package/dist/utils/media-url.d.ts +27 -4
  274. package/dist/utils/media-url.js +37 -6
  275. package/dist/utils/search-txt.d.ts +71 -0
  276. package/dist/utils/search-txt.js +118 -0
  277. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import type { LotteryConfigWriteInput } from "../../../../features/lottery/schemas/config-write";
2
3
  interface LotterySlotRow {
3
4
  slotNumber: number;
4
5
  name: string;
@@ -6,26 +7,25 @@ interface LotterySlotRow {
6
7
  /** Prize photo — rendered publicly in the prize collage on the detail page. */
7
8
  image?: string;
8
9
  }
10
+ /**
11
+ * What this form hands its caller.
12
+ *
13
+ * `lotteryConfig` is the WRITE input — deliberately not `LotteryConfig`. It
14
+ * carries no `isBooked`, no `bookedBy*` and no `weight`, because an admin
15
+ * editing a lottery is describing prizes, not attendance. The stored config
16
+ * gains those in `mergeLotteryConfig`, server-side, from the slots that are
17
+ * already there.
18
+ *
19
+ * `totalSlots` is absent for the same reason: derived from `slots.length` by
20
+ * the merge, never sent. A caller-supplied count that disagrees with the array
21
+ * is the mirror-drift trap (Root Cause #42), and the count is what the
22
+ * fullness check reads.
23
+ */
9
24
  interface LotteryEventFormData {
10
25
  title: string;
11
26
  description?: string;
12
27
  type: "lottery";
13
- lotteryConfig: {
14
- slots: Array<{
15
- slotNumber: number;
16
- name: string;
17
- image?: string;
18
- price: number;
19
- weight: number;
20
- isBooked: boolean;
21
- }>;
22
- totalSlots: number;
23
- pricingMode: "uniform" | "variable";
24
- uniformPrice?: number;
25
- drawWindowDurationMinutes: number;
26
- maxPullsPerTransaction: number;
27
- maxPullsPerUser: number;
28
- };
28
+ lotteryConfig: LotteryConfigWriteInput;
29
29
  }
30
30
  interface LotteryAdminEditViewProps {
31
31
  eventId?: string;
@@ -5,6 +5,7 @@ import { normalizeError } from "../../../../errors/normalize";
5
5
  import { Stack, Row, Text, Heading, Section, Container, Button, DataTable, } from "../../../../ui";
6
6
  import { Form, FieldInput, FieldSelect, Input } from "../../../../ui";
7
7
  import { lotteryConfigFormSchema } from "../../../shared/features/lottery/config-form";
8
+ import { lotteryConfigWriteSchema } from "../../../../features/lottery/schemas/config-write";
8
9
  import { FormErrorSummary } from "../../../../ui/forms/FormErrorSummary";
9
10
  export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
10
11
  const [isPending, startTransition] = useTransition();
@@ -35,23 +36,45 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
35
36
  setError(null);
36
37
  startTransition(async () => {
37
38
  try {
38
- const lotteryConfig = {
39
+ /*
40
+ * 🛑 This used to send `isBooked: false` and `weight: 0` on every slot,
41
+ * and drop `bookedByUserId` / `bookedByDisplayName` /
42
+ * `bookedByUserLotteryNumber` entirely — against a `.passthrough()`
43
+ * PATCH, which made the first save of a live lottery erase every
44
+ * purchased slot, silently, with a 200.
45
+ *
46
+ * The write shape now has NO booking fields at all and is `.strict()`,
47
+ * so a slot's buyer is not something this form can express, let alone
48
+ * overwrite. `PUT /lottery-config` merges by `slotNumber` against the
49
+ * stored config and is the only writer.
50
+ */
51
+ const candidate = {
39
52
  slots: slots.map((s) => ({
40
53
  slotNumber: s.slotNumber,
41
54
  name: s.name,
42
55
  image: s.image?.trim() || undefined,
43
56
  price: s.price,
44
- weight: 0,
45
- isBooked: false,
46
57
  })),
47
- totalSlots: slots.length,
48
58
  pricingMode,
49
- uniformPrice: pricingMode === "uniform" ? Math.round(parseFloat(uniformPrice) * 100) / 100 || 0 : undefined,
59
+ uniformPrice: pricingMode === "uniform"
60
+ ? Math.round(parseFloat(uniformPrice) * 100) / 100 || 0
61
+ : undefined,
50
62
  drawWindowDurationMinutes: parseInt(drawWindowMinutes, 10) || 5,
51
63
  maxPullsPerTransaction: parseInt(maxPullsPerTx, 10) || 1,
52
64
  maxPullsPerUser: parseInt(maxPullsPerUser, 10) || 1,
53
65
  };
54
- await onSubmit({ title, description, type: "lottery", lotteryConfig });
66
+ /*
67
+ * The schema was passed to `<Form>` and never actually run — so the
68
+ * `parseFloat(x) || 0` coercions above, which it exists to catch, were
69
+ * still live. Parsed here so a bad slot is refused on the client
70
+ * rather than discovered as a 400.
71
+ */
72
+ const parsed = lotteryConfigWriteSchema.safeParse(candidate);
73
+ if (!parsed.success) {
74
+ setError(parsed.error.issues[0]?.message ?? "Check the lottery settings.");
75
+ return;
76
+ }
77
+ await onSubmit({ title, description, type: "lottery", lotteryConfig: parsed.data });
55
78
  }
56
79
  catch (err) {
57
80
  void normalizeError(err);
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Stack, Row, Text, Heading, Badge, Card, Section, Container, Anchor, Div, Grid, } from "../../../../ui";
4
4
  import { MediaImage } from "../../../../features/media/MediaImage";
5
+ import { ROUTES } from "../../../../next/routing/route-map";
5
6
  /**
6
7
  * Card grid of lotteries.
7
8
  * Admin sees all statuses; public sees active only.
@@ -17,7 +18,18 @@ export function LotteryListView({ items, adminMode = false }) {
17
18
  const claimed = event.lotteryConfig?.slots.filter((s) => s.isBooked).length ?? 0;
18
19
  const total = event.lotteryConfig?.totalSlots ?? 0;
19
20
  const slotsLeft = total - claimed;
20
- return (_jsx(Anchor, { href: `/lottery/${event.id}`, tone: "brand", rounded: "2xl", className: "block focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": `${event.title} lottery`, children: _jsxs(Card, { variant: "default", padding: "none", animate: "hoverLift", className: "overflow-hidden", children: [event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { className: "relative aspect-video overflow-hidden", children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: event.title, size: "card" }) })) : (_jsx(Div, { className: "aspect-video", surface: "muted", align: "center", justify: "center", children: _jsx(Text, { size: "3xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: event.status === "active"
21
+ return (_jsx(Anchor, {
22
+ /*
23
+ * In adminMode this used to link to the PUBLIC lottery page,
24
+ * so an admin on the admin list had no route to slot
25
+ * configuration at all — which went unnoticed because the
26
+ * editor was orphaned and there was nowhere to link TO.
27
+ * Nested anchors are invalid, so the card itself changes
28
+ * target rather than gaining a second link.
29
+ */
30
+ href: adminMode
31
+ ? String(ROUTES.ADMIN.LOTTERY_CONFIG(event.id))
32
+ : `/lottery/${event.id}`, tone: "brand", rounded: "2xl", className: "block focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": `${event.title} lottery`, children: _jsxs(Card, { variant: "default", padding: "none", animate: "hoverLift", className: "overflow-hidden", children: [event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { className: "relative aspect-video overflow-hidden", children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: event.title, size: "card" }) })) : (_jsx(Div, { className: "aspect-video", surface: "muted", align: "center", justify: "center", children: _jsx(Text, { size: "3xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: event.status === "active"
21
33
  ? "success"
22
34
  : event.status === "ended"
23
35
  ? "danger"
@@ -40,6 +40,25 @@ export declare const listBundleMembers: (bundle: Pick<CategoryDocument, "bundleP
40
40
  * displayed discount.
41
41
  */
42
42
  export declare function resolveBundleOriginalTotal(productIds: string[]): Promise<number | undefined>;
43
+ /**
44
+ * Reject a bundle whose members belong to more than one seller.
45
+ *
46
+ * `storeId` is a SCALAR on the cart line, and it is simultaneously the
47
+ * order-splitting key and the key that per-store add-ons, coupons, shipping and
48
+ * payout all hang off. A bundle spanning two sellers therefore produces one
49
+ * order belonging to one of them while containing the other's products — the
50
+ * second seller gets no notification, no shipping resolution and no payout.
51
+ *
52
+ * Grouped cart lines refuse this at both the picker and the add-to-cart action.
53
+ * Bundles had no equivalent guard, so the only reason nothing was broken was
54
+ * that every bundle anyone had built happened to be single-store. This closes
55
+ * that at the point a bundle is SAVED, which is the cheapest place to say no:
56
+ * bundles already in the database keep working, and checkout is untouched.
57
+ *
58
+ * Returns the offending store ids (empty when fine) rather than throwing, so
59
+ * each route can phrase its own 400.
60
+ */
61
+ export declare function findBundleMemberStores(productIds: string[]): Promise<string[]>;
43
62
  /**
44
63
  * Union of the member products' `categorySlugs` — the `bundleCategorySlugs`
45
64
  * mirror that lets a category page scope its Bundles tab.
@@ -68,6 +68,38 @@ export async function resolveBundleOriginalTotal(productIds) {
68
68
  const total = prices.reduce((sum, p) => sum + p, 0);
69
69
  return Math.round(total * 100) / 100;
70
70
  }
71
+ /**
72
+ * Reject a bundle whose members belong to more than one seller.
73
+ *
74
+ * `storeId` is a SCALAR on the cart line, and it is simultaneously the
75
+ * order-splitting key and the key that per-store add-ons, coupons, shipping and
76
+ * payout all hang off. A bundle spanning two sellers therefore produces one
77
+ * order belonging to one of them while containing the other's products — the
78
+ * second seller gets no notification, no shipping resolution and no payout.
79
+ *
80
+ * Grouped cart lines refuse this at both the picker and the add-to-cart action.
81
+ * Bundles had no equivalent guard, so the only reason nothing was broken was
82
+ * that every bundle anyone had built happened to be single-store. This closes
83
+ * that at the point a bundle is SAVED, which is the cheapest place to say no:
84
+ * bundles already in the database keep working, and checkout is untouched.
85
+ *
86
+ * Returns the offending store ids (empty when fine) rather than throwing, so
87
+ * each route can phrase its own 400.
88
+ */
89
+ export async function findBundleMemberStores(productIds) {
90
+ if (productIds.length === 0)
91
+ return [];
92
+ const results = await Promise.all(productIds.map((id) => productRepository.findById(id).catch(() => null)));
93
+ const stores = new Set();
94
+ for (const p of results) {
95
+ // An unresolvable member says nothing about seller spread — the price
96
+ // helper above already refuses to guess in that case, and inventing a
97
+ // store here would produce a false positive that blocks a valid save.
98
+ if (p?.storeId)
99
+ stores.add(p.storeId);
100
+ }
101
+ return [...stores];
102
+ }
71
103
  /**
72
104
  * Union of the member products' `categorySlugs` — the `bundleCategorySlugs`
73
105
  * mirror that lets a category page scope its Bundles tab.
@@ -1,4 +1,4 @@
1
- export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, resolveBundleCategorySlugs, type BundleDataOptions, } from "./data";
1
+ export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, findBundleMemberStores, resolveBundleCategorySlugs, type BundleDataOptions, } from "./data";
2
2
  export { buildBundleMetadata, type BundleMetadataOptions } from "./metadata";
3
3
  export { renderBundleOg, renderBundleOgImage, type BundleOgData, } from "./og";
4
4
  export { addBundleToCartAction } from "./actions";
@@ -1,4 +1,4 @@
1
- export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, resolveBundleCategorySlugs, } from "./data";
1
+ export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, findBundleMemberStores, resolveBundleCategorySlugs, } from "./data";
2
2
  export { buildBundleMetadata } from "./metadata";
3
3
  // S-SBUNI-4 2026-05-13 — bundle OG renderer.
4
4
  export { renderBundleOg, renderBundleOgImage, } from "./og";
@@ -80,7 +80,7 @@ async function claimDigitalCodeForOrder(db, productId, orderId, userId, opts) {
80
80
  userName: opts.userName ?? "there",
81
81
  productTitle: opts.productTitle ?? "your product",
82
82
  orderId,
83
- }).catch((e) => serverLogger.error("claimDigitalCode: email failed", e));
83
+ }).catch((e) => serverLogger.error("claimDigitalCode: email failed", { error: normalizeError(e).message }));
84
84
  }
85
85
  }
86
86
  /**
@@ -550,7 +550,7 @@ async function createOrderForGroup(group, orderType, ctx) {
550
550
  userEmail: userEmail || undefined,
551
551
  userName,
552
552
  productTitle: firstItem.productTitle,
553
- }).catch((e) => serverLogger.error("claimDigitalCode", e));
553
+ }).catch((e) => serverLogger.error("claimDigitalCode", { error: normalizeError(e).message }));
554
554
  }
555
555
  for (const code of groupCouponCodes) {
556
556
  const entry = couponUsageAccumulator.get(code);
@@ -892,8 +892,10 @@ export async function previewCheckoutPricing(input) {
892
892
  // A preview never throws — it just shows the lane the buyer can actually
893
893
  // check out, matching what the checkout page's active tab displays.
894
894
  (!previewLane || laneOf(item) === previewLane));
895
+ // A fresh object, not the module singleton — an in-process caller mutating
896
+ // the returned preview would otherwise poison every later empty result.
895
897
  if (cartItems.length === 0)
896
- return EMPTY_PRICING_PREVIEW;
898
+ return { ...EMPTY_PRICING_PREVIEW, stores: [] };
897
899
  // Best-effort product lookup — a preview never throws on a since-unpublished
898
900
  // product, it just falls back to the item's cart-snapshot price for that
899
901
  // line (same fallback cartRepository.getSubtotal() implicitly relies on).
@@ -1268,7 +1270,7 @@ async function createRazorpayGroupOrder(group, orderType, ctx) {
1268
1270
  userEmail: userEmail || undefined,
1269
1271
  userName,
1270
1272
  productTitle: firstItem.productTitle,
1271
- }).catch((e) => serverLogger.error("claimDigitalCode", e));
1273
+ }).catch((e) => serverLogger.error("claimDigitalCode", { error: normalizeError(e).message }));
1272
1274
  }
1273
1275
  for (const code of groupCouponCodes) {
1274
1276
  const entry = couponUsageAccumulator.get(code);
@@ -21,7 +21,9 @@ export function buildClassifiedMetadata(product, opts) {
21
21
  : product.description?.slice(0, 160) ?? "";
22
22
  const image = opts?.ogImageUrl ?? (product.mainImage || product.images?.[0] || null);
23
23
  const siteUrl = opts?.siteUrl?.replace(/\/+$/, "");
24
- const canonical = siteUrl ? `${siteUrl}/classified/${product.id}` : undefined;
24
+ // Prefer `slug` — the route is `/classified/[slug]`, so a canonical built from
25
+ // `id` names a URL the router does not serve whenever the two differ.
26
+ const canonical = siteUrl ? `${siteUrl}/classified/${product.slug ?? product.id}` : undefined;
25
27
  return {
26
28
  title,
27
29
  description,
@@ -14,7 +14,9 @@ export function buildDigitalCodeMetadata(product, opts) {
14
14
  : product.description?.slice(0, 160) ?? "";
15
15
  const image = opts?.ogImageUrl ?? (product.mainImage || product.images?.[0] || null);
16
16
  const siteUrl = opts?.siteUrl?.replace(/\/+$/, "");
17
- const canonical = siteUrl ? `${siteUrl}/digital-codes/${product.id}` : undefined;
17
+ // Prefer `slug` — the route is `/digital-codes/[slug]`, so a canonical built
18
+ // from `id` names a URL the router does not serve whenever the two differ.
19
+ const canonical = siteUrl ? `${siteUrl}/digital-codes/${product.slug ?? product.id}` : undefined;
18
20
  return {
19
21
  title,
20
22
  description,
@@ -17,7 +17,9 @@ export function buildLiveItemMetadata(product, opts) {
17
17
  : product.description?.slice(0, 160) ?? "";
18
18
  const image = opts?.ogImageUrl ?? (product.mainImage || product.images?.[0] || null);
19
19
  const siteUrl = opts?.siteUrl?.replace(/\/+$/, "");
20
- const canonical = siteUrl ? `${siteUrl}/live/${product.id}` : undefined;
20
+ // Prefer `slug` — the route is `/live/[slug]`, so a canonical built from `id`
21
+ // names a URL the router does not serve whenever the two differ.
22
+ const canonical = siteUrl ? `${siteUrl}/live/${product.slug ?? product.id}` : undefined;
21
23
  return {
22
24
  title,
23
25
  description,
@@ -16,6 +16,7 @@ import { wrapAction } from "@mohasinac/appkit/server";
16
16
  */
17
17
  import { randomUUID } from "crypto";
18
18
  import { getProviders } from "../../../../contracts/registry";
19
+ import { normalizeError } from "../../../../errors/normalize";
19
20
  import { rupeesToPaise } from "../../../../providers/payment-razorpay/index";
20
21
  import { orderRepository } from "../../../..";
21
22
  import { ORDER_FIELDS } from "../../../../constants/field-names";
@@ -65,7 +66,7 @@ function revokeAvailableDigitalCodes(orderId, items) {
65
66
  .limit(1)
66
67
  .get()
67
68
  .then((snap) => revokeFirstSnapDoc(snap))
68
- .catch((e) => serverLogger.error("refund: code query failed", e));
69
+ .catch((e) => serverLogger.error("refund: code query failed", { error: normalizeError(e).message }));
69
70
  }
70
71
  }
71
72
  function revokeFirstSnapDoc(snap) {
@@ -73,7 +74,7 @@ function revokeFirstSnapDoc(snap) {
73
74
  return;
74
75
  snap.docs[0].ref
75
76
  .update({ status: "revoked", updatedAt: new Date() })
76
- .catch((e) => serverLogger.error("refund: code revoke failed", e));
77
+ .catch((e) => serverLogger.error("refund: code revoke failed", { error: normalizeError(e).message }));
77
78
  }
78
79
  export async function processRefundAction(input) {
79
80
  return wrapAction(async () => {
@@ -71,8 +71,9 @@ export async function deleteReviewAction(input) {
71
71
  }
72
72
  export async function markReviewHelpfulAction(reviewId) {
73
73
  return wrapAction(async () => {
74
- await requireRoleUser(["buyer", "seller", "admin"]);
74
+ const user = await requireRoleUser(["buyer", "seller", "admin"]);
75
75
  await getReviewOrThrow(reviewId);
76
- await reviewRepository.incrementHelpful(reviewId);
76
+ // Keyed on the voter so a repeated call is a no-op rather than another +1.
77
+ return reviewRepository.voteHelpful(reviewId, user.uid);
77
78
  });
78
79
  }