@mohasinac/appkit 2.6.1 → 2.6.3

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 (302) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +38 -0
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +75 -0
  3. package/dist/_internal/client/features/layout/RoleGuard.d.ts +15 -0
  4. package/dist/_internal/client/features/layout/RoleGuard.js +25 -0
  5. package/dist/_internal/client/features/layout/index.d.ts +5 -0
  6. package/dist/_internal/client/features/layout/index.js +4 -0
  7. package/dist/_internal/server/features/brands/actions.d.ts +3 -3
  8. package/dist/_internal/server/features/brands/actions.js +72 -5
  9. package/dist/_internal/server/features/brands/data.d.ts +8 -8
  10. package/dist/_internal/server/features/brands/data.js +10 -11
  11. package/dist/_internal/server/features/brands/service.d.ts +2 -2
  12. package/dist/_internal/server/features/brands/service.js +5 -5
  13. package/dist/_internal/server/features/categories/og.d.ts +33 -0
  14. package/dist/_internal/server/features/categories/og.js +75 -0
  15. package/dist/_internal/server/features/checkout/actions.d.ts +24 -0
  16. package/dist/_internal/server/features/checkout/actions.js +442 -13
  17. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  18. package/dist/_internal/server/features/checkout/index.js +1 -1
  19. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +59 -0
  20. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +99 -0
  21. package/dist/_internal/server/features/grouped/data.js +12 -5
  22. package/dist/_internal/server/features/homepage/data.d.ts +1 -1
  23. package/dist/_internal/server/features/homepage/data.js +2 -2
  24. package/dist/_internal/server/features/media/contextGuards.d.ts +52 -0
  25. package/dist/_internal/server/features/media/contextGuards.js +198 -0
  26. package/dist/_internal/server/features/orders/adapters.js +12 -0
  27. package/dist/_internal/server/features/products/data.d.ts +1 -1
  28. package/dist/_internal/server/features/sublisting-categories/data.d.ts +1 -1
  29. package/dist/_internal/server/features/sublisting-categories/data.js +2 -2
  30. package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +24 -0
  31. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +86 -0
  32. package/dist/_internal/server/jobs/handlers/bundleStockSync.d.ts +18 -0
  33. package/dist/_internal/server/jobs/handlers/bundleStockSync.js +80 -0
  34. package/dist/_internal/server/jobs/handlers/index.d.ts +8 -0
  35. package/dist/_internal/server/jobs/handlers/index.js +13 -0
  36. package/dist/_internal/server/jobs/handlers/listingProcessor.js +13 -3
  37. package/dist/_internal/server/jobs/handlers/onProductStockChange.d.ts +17 -0
  38. package/dist/_internal/server/jobs/handlers/onProductStockChange.js +136 -0
  39. package/dist/_internal/server/jobs/handlers/onProductWrite.js +17 -1
  40. package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +9 -0
  41. package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +29 -0
  42. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +10 -0
  43. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +58 -0
  44. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +10 -0
  45. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +65 -0
  46. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +9 -0
  47. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +45 -0
  48. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +30 -0
  49. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +94 -0
  50. package/dist/_internal/shared/features/brands/schema.d.ts +3 -3
  51. package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
  52. package/dist/_internal/shared/features/cart/schema.js +1 -1
  53. package/dist/_internal/shared/features/categories/bundle-config.d.ts +17 -0
  54. package/dist/_internal/shared/features/categories/bundle-config.js +17 -0
  55. package/dist/_internal/shared/features/layout/config.d.ts +35 -0
  56. package/dist/_internal/shared/features/layout/config.js +58 -0
  57. package/dist/_internal/shared/features/layout/index.d.ts +3 -0
  58. package/dist/_internal/shared/features/layout/index.js +2 -0
  59. package/dist/_internal/shared/features/layout/types.d.ts +137 -0
  60. package/dist/_internal/shared/features/layout/types.js +13 -0
  61. package/dist/_internal/shared/features/products/types.d.ts +1 -1
  62. package/dist/_internal/shared/listing-types/_registry.d.ts +57 -0
  63. package/dist/_internal/shared/listing-types/_registry.js +28 -0
  64. package/dist/_internal/shared/listing-types/auction/config.d.ts +7 -0
  65. package/dist/_internal/shared/listing-types/auction/config.js +8 -0
  66. package/dist/_internal/shared/listing-types/auction/ctas.d.ts +1 -0
  67. package/dist/_internal/shared/listing-types/auction/ctas.js +2 -0
  68. package/dist/_internal/shared/listing-types/auction/og.d.ts +1 -0
  69. package/dist/_internal/shared/listing-types/auction/og.js +1 -0
  70. package/dist/_internal/shared/listing-types/auction/schema.d.ts +1 -0
  71. package/dist/_internal/shared/listing-types/auction/schema.js +1 -0
  72. package/dist/_internal/shared/listing-types/auction/seed-factory.d.ts +1 -0
  73. package/dist/_internal/shared/listing-types/auction/seed-factory.js +1 -0
  74. package/dist/_internal/shared/listing-types/capabilities.d.ts +41 -0
  75. package/dist/_internal/shared/listing-types/capabilities.js +75 -0
  76. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +7 -0
  77. package/dist/_internal/shared/listing-types/pre-order/config.js +8 -0
  78. package/dist/_internal/shared/listing-types/pre-order/ctas.d.ts +1 -0
  79. package/dist/_internal/shared/listing-types/pre-order/ctas.js +2 -0
  80. package/dist/_internal/shared/listing-types/pre-order/og.d.ts +1 -0
  81. package/dist/_internal/shared/listing-types/pre-order/og.js +1 -0
  82. package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +1 -0
  83. package/dist/_internal/shared/listing-types/pre-order/schema.js +1 -0
  84. package/dist/_internal/shared/listing-types/pre-order/seed-factory.d.ts +1 -0
  85. package/dist/_internal/shared/listing-types/pre-order/seed-factory.js +1 -0
  86. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +7 -0
  87. package/dist/_internal/shared/listing-types/prize-draw/config.js +8 -0
  88. package/dist/_internal/shared/listing-types/prize-draw/ctas.d.ts +1 -0
  89. package/dist/_internal/shared/listing-types/prize-draw/ctas.js +2 -0
  90. package/dist/_internal/shared/listing-types/prize-draw/og.d.ts +1 -0
  91. package/dist/_internal/shared/listing-types/prize-draw/og.js +1 -0
  92. package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +1 -0
  93. package/dist/_internal/shared/listing-types/prize-draw/schema.js +1 -0
  94. package/dist/_internal/shared/listing-types/prize-draw/seed-factory.d.ts +1 -0
  95. package/dist/_internal/shared/listing-types/prize-draw/seed-factory.js +1 -0
  96. package/dist/_internal/shared/listing-types/standard/config.d.ts +7 -0
  97. package/dist/_internal/shared/listing-types/standard/config.js +8 -0
  98. package/dist/_internal/shared/listing-types/standard/ctas.d.ts +1 -0
  99. package/dist/_internal/shared/listing-types/standard/ctas.js +3 -0
  100. package/dist/_internal/shared/listing-types/standard/og.d.ts +1 -0
  101. package/dist/_internal/shared/listing-types/standard/og.js +1 -0
  102. package/dist/_internal/shared/listing-types/standard/schema.d.ts +1 -0
  103. package/dist/_internal/shared/listing-types/standard/schema.js +1 -0
  104. package/dist/_internal/shared/listing-types/standard/seed-factory.d.ts +1 -0
  105. package/dist/_internal/shared/listing-types/standard/seed-factory.js +1 -0
  106. package/dist/_internal/shared/media/limits.d.ts +33 -0
  107. package/dist/_internal/shared/media/limits.js +97 -0
  108. package/dist/_internal/shared/schema-versions.d.ts +76 -0
  109. package/dist/_internal/shared/schema-versions.js +82 -0
  110. package/dist/client.d.ts +9 -0
  111. package/dist/client.js +7 -0
  112. package/dist/constants/api-endpoints.d.ts +6 -3
  113. package/dist/constants/api-endpoints.js +2 -1
  114. package/dist/errors/messages.d.ts +1 -1
  115. package/dist/errors/messages.js +1 -1
  116. package/dist/features/account/migrations.d.ts +2 -0
  117. package/dist/features/account/migrations.js +10 -0
  118. package/dist/features/admin/components/AdminMediaView.js +1 -1
  119. package/dist/features/admin/components/AdminProductsView.js +7 -3
  120. package/dist/features/admin/migrations.d.ts +2 -0
  121. package/dist/features/admin/migrations.js +10 -0
  122. package/dist/features/admin/types/product.types.d.ts +1 -1
  123. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +1 -1
  124. package/dist/features/auctions/migrations.d.ts +2 -0
  125. package/dist/features/auctions/migrations.js +10 -0
  126. package/dist/features/auctions/schemas/index.d.ts +3 -3
  127. package/dist/features/auctions/schemas/index.js +1 -1
  128. package/dist/features/auth/migrations.d.ts +2 -0
  129. package/dist/features/auth/migrations.js +10 -0
  130. package/dist/features/blog/migrations.d.ts +2 -0
  131. package/dist/features/blog/migrations.js +10 -0
  132. package/dist/features/brands/migrations.d.ts +2 -0
  133. package/dist/features/brands/migrations.js +10 -0
  134. package/dist/features/bundles/components/BundlesByCategoryListing.d.ts +6 -0
  135. package/dist/features/bundles/components/BundlesByCategoryListing.js +50 -0
  136. package/dist/features/bundles/components/index.d.ts +2 -0
  137. package/dist/features/bundles/components/index.js +1 -0
  138. package/dist/features/bundles/migrations.d.ts +2 -0
  139. package/dist/features/bundles/migrations.js +10 -0
  140. package/dist/features/bundles/schemas/index.d.ts +1 -0
  141. package/dist/features/bundles/schemas/index.js +1 -0
  142. package/dist/features/bundles/schemas/zod.d.ts +377 -0
  143. package/dist/features/bundles/schemas/zod.js +71 -0
  144. package/dist/features/cart/migrations.d.ts +2 -0
  145. package/dist/features/cart/migrations.js +10 -0
  146. package/dist/features/cart/schemas/firestore.d.ts +2 -2
  147. package/dist/features/categories/components/BrandDetailPageView.js +35 -4
  148. package/dist/features/categories/components/BrandDetailTabs.d.ts +5 -1
  149. package/dist/features/categories/components/BrandDetailTabs.js +22 -8
  150. package/dist/features/categories/components/CategoryBundlesListing.d.ts +6 -0
  151. package/dist/features/categories/components/CategoryBundlesListing.js +74 -0
  152. package/dist/features/categories/components/CategoryDetailPageView.js +29 -4
  153. package/dist/features/categories/components/CategoryDetailTabs.d.ts +5 -1
  154. package/dist/features/categories/components/CategoryDetailTabs.js +22 -8
  155. package/dist/features/categories/migrations.d.ts +2 -0
  156. package/dist/features/categories/migrations.js +10 -0
  157. package/dist/features/categories/repository/categories.repository.d.ts +29 -0
  158. package/dist/features/categories/repository/categories.repository.js +83 -0
  159. package/dist/features/categories/schemas/firestore.d.ts +59 -2
  160. package/dist/features/categories/schemas/firestore.js +6 -0
  161. package/dist/features/categories/types/index.d.ts +11 -3
  162. package/dist/features/events/migrations.d.ts +2 -0
  163. package/dist/features/events/migrations.js +10 -0
  164. package/dist/features/faq/migrations.d.ts +2 -0
  165. package/dist/features/faq/migrations.js +10 -0
  166. package/dist/features/grouped/migrations.d.ts +2 -0
  167. package/dist/features/grouped/migrations.js +10 -0
  168. package/dist/features/grouped/schemas/firestore.d.ts +29 -10
  169. package/dist/features/grouped/schemas/firestore.js +10 -5
  170. package/dist/features/history/migrations.d.ts +2 -0
  171. package/dist/features/history/migrations.js +10 -0
  172. package/dist/features/homepage/hooks/useFeaturedAuctions.js +2 -2
  173. package/dist/features/homepage/hooks/useFeaturedPreOrders.js +2 -2
  174. package/dist/features/homepage/lib/section-renderer.js +5 -3
  175. package/dist/features/media/AvatarUpload.js +6 -28
  176. package/dist/features/media/hooks/useMedia.d.ts +31 -15
  177. package/dist/features/media/hooks/useMedia.js +48 -13
  178. package/dist/features/media/upload/ImageUpload.js +1 -1
  179. package/dist/features/media/upload/MediaUploadField.js +1 -1
  180. package/dist/features/messages/migrations.d.ts +2 -0
  181. package/dist/features/messages/migrations.js +10 -0
  182. package/dist/features/orders/components/OrdersList.js +10 -1
  183. package/dist/features/orders/migrations.d.ts +2 -0
  184. package/dist/features/orders/migrations.js +10 -0
  185. package/dist/features/orders/repository/orders.repository.d.ts +16 -0
  186. package/dist/features/orders/repository/orders.repository.js +49 -0
  187. package/dist/features/orders/schemas/firestore.d.ts +8 -0
  188. package/dist/features/orders/types/index.d.ts +12 -0
  189. package/dist/features/orders/utils/order-splitter.d.ts +2 -2
  190. package/dist/features/orders/utils/order-splitter.js +5 -0
  191. package/dist/features/payments/migrations.d.ts +2 -0
  192. package/dist/features/payments/migrations.js +10 -0
  193. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
  194. package/dist/features/products/actions/product-actions.d.ts +1 -1
  195. package/dist/features/products/api/[id]/route.js +34 -0
  196. package/dist/features/products/api/route.js +1 -19
  197. package/dist/features/products/components/CompareOverlay.d.ts +1 -1
  198. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +24 -0
  199. package/dist/features/products/components/MarketplacePrizeDrawCard.js +102 -0
  200. package/dist/features/products/components/PrizeDrawCollage.d.ts +32 -0
  201. package/dist/features/products/components/PrizeDrawCollage.js +22 -0
  202. package/dist/features/products/components/PrizeDrawDetailPageView.d.ts +27 -0
  203. package/dist/features/products/components/PrizeDrawDetailPageView.js +118 -0
  204. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +19 -0
  205. package/dist/features/products/components/PrizeDrawEntryActions.js +48 -0
  206. package/dist/features/products/components/PrizeDrawItemsEditor.d.ts +13 -0
  207. package/dist/features/products/components/PrizeDrawItemsEditor.js +97 -0
  208. package/dist/features/products/components/PrizeDrawsIndexListing.d.ts +8 -0
  209. package/dist/features/products/components/PrizeDrawsIndexListing.js +128 -0
  210. package/dist/features/products/components/PrizeDrawsListingView.d.ts +15 -0
  211. package/dist/features/products/components/PrizeDrawsListingView.js +49 -0
  212. package/dist/features/products/components/PrizeRevealModal.d.ts +34 -0
  213. package/dist/features/products/components/PrizeRevealModal.js +124 -0
  214. package/dist/features/products/components/ProductDetailPageView.js +13 -1
  215. package/dist/features/products/components/ProductForm.js +35 -2
  216. package/dist/features/products/components/ProductGrid.js +3 -1
  217. package/dist/features/products/components/index.d.ts +16 -0
  218. package/dist/features/products/components/index.js +8 -0
  219. package/dist/features/products/constants/listing-tabs.d.ts +113 -0
  220. package/dist/features/products/constants/listing-tabs.js +43 -0
  221. package/dist/features/products/index.d.ts +1 -0
  222. package/dist/features/products/index.js +1 -0
  223. package/dist/features/products/migrations.d.ts +2 -0
  224. package/dist/features/products/migrations.js +10 -0
  225. package/dist/features/products/repository/products.repository.d.ts +11 -7
  226. package/dist/features/products/repository/products.repository.js +49 -24
  227. package/dist/features/products/schemas/firestore.d.ts +3 -3
  228. package/dist/features/products/schemas/firestore.js +2 -2
  229. package/dist/features/products/schemas/index.d.ts +5 -5
  230. package/dist/features/products/schemas/index.js +3 -1
  231. package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
  232. package/dist/features/products/types/index.d.ts +17 -1
  233. package/dist/features/products/utils/listing-type.d.ts +7 -4
  234. package/dist/features/products/utils/listing-type.js +8 -4
  235. package/dist/features/promotions/actions/coupon-actions.d.ts +1 -1
  236. package/dist/features/promotions/hooks/useCouponValidate.d.ts +1 -1
  237. package/dist/features/promotions/migrations.d.ts +2 -0
  238. package/dist/features/promotions/migrations.js +10 -0
  239. package/dist/features/promotions/repository/coupons.repository.d.ts +1 -1
  240. package/dist/features/promotions/schemas/index.d.ts +2 -2
  241. package/dist/features/reviews/migrations.d.ts +2 -0
  242. package/dist/features/reviews/migrations.js +10 -0
  243. package/dist/features/scams/migrations.d.ts +2 -0
  244. package/dist/features/scams/migrations.js +10 -0
  245. package/dist/features/search/api/route.d.ts +1 -1
  246. package/dist/features/search/api/route.js +3 -3
  247. package/dist/features/search/components/Search.d.ts +1 -1
  248. package/dist/features/search/schemas/index.d.ts +3 -3
  249. package/dist/features/search/schemas/index.js +3 -1
  250. package/dist/features/search/types/index.d.ts +2 -2
  251. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  252. package/dist/features/seller/components/SellerProductsView.js +20 -6
  253. package/dist/features/seller/migrations.d.ts +2 -0
  254. package/dist/features/seller/migrations.js +10 -0
  255. package/dist/features/seller/schemas/index.d.ts +2 -2
  256. package/dist/features/stores/components/StoreBundlesPageView.d.ts +12 -0
  257. package/dist/features/stores/components/StoreBundlesPageView.js +24 -0
  258. package/dist/features/stores/components/StoreDetailLayoutView.js +15 -3
  259. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +11 -0
  260. package/dist/features/stores/components/StorePrizeDrawsPageView.js +27 -0
  261. package/dist/features/stores/components/index.d.ts +2 -0
  262. package/dist/features/stores/migrations.d.ts +2 -0
  263. package/dist/features/stores/migrations.js +10 -0
  264. package/dist/features/stores/schemas/index.d.ts +2 -2
  265. package/dist/features/stores/types/index.d.ts +1 -1
  266. package/dist/features/sublisting/migrations.d.ts +2 -0
  267. package/dist/features/sublisting/migrations.js +10 -0
  268. package/dist/features/sublisting/schemas/firestore.d.ts +2 -0
  269. package/dist/features/support/migrations.d.ts +2 -0
  270. package/dist/features/support/migrations.js +10 -0
  271. package/dist/features/wishlist/migrations.d.ts +2 -0
  272. package/dist/features/wishlist/migrations.js +10 -0
  273. package/dist/features/wishlist/types/index.d.ts +1 -1
  274. package/dist/index.d.ts +26 -18
  275. package/dist/index.js +41 -24
  276. package/dist/jobs.d.ts +1 -1
  277. package/dist/jobs.js +4 -0
  278. package/dist/next/routing/route-map.d.ts +4 -0
  279. package/dist/next/routing/route-map.js +2 -0
  280. package/dist/providers/db-firebase/admin-app-lite.js +21 -0
  281. package/dist/providers/db-firebase/filter-aliases.d.ts +2 -2
  282. package/dist/repositories/index.d.ts +0 -5
  283. package/dist/repositories/index.js +5 -4
  284. package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
  285. package/dist/seed/categories-seed-data.js +1105 -6
  286. package/dist/seed/faq-seed-data.js +160 -0
  287. package/dist/seed/grouped-listings-seed-data.js +32 -32
  288. package/dist/seed/homepage-sections-seed-data.js +52 -6
  289. package/dist/seed/index.d.ts +1 -3
  290. package/dist/seed/index.js +4 -3
  291. package/dist/seed/manifest.js +8 -13
  292. package/dist/seed/products-prize-draws-seed-data.d.ts +17 -0
  293. package/dist/seed/products-prize-draws-seed-data.js +313 -0
  294. package/dist/seo/json-ld.d.ts +1 -1
  295. package/dist/server-entry.d.ts +2 -2
  296. package/dist/server-entry.js +5 -3
  297. package/dist/server.d.ts +9 -2
  298. package/dist/server.js +11 -5
  299. package/dist/tailwind-utilities.css +1 -1
  300. package/dist/validation/schemas.d.ts +8 -8
  301. package/package.json +1 -1
  302. package/scripts/seed-cli.mjs +2 -4
@@ -21,7 +21,7 @@ export interface CartItemDocument {
21
21
  * carts) and cart-side UI badges. Replaces the legacy `isAuction`/`isPreOrder`
22
22
  * pair on the cart item.
23
23
  */
24
- listingType: "standard" | "auction" | "pre-order" | "prize-draw" | "bundle";
24
+ listingType: "standard" | "auction" | "pre-order" | "prize-draw";
25
25
  /** True when item was added from an accepted Make-an-Offer */
26
26
  isOffer?: boolean;
27
27
  offerId?: string;
@@ -89,7 +89,7 @@ export type AddToCartInput = {
89
89
  /** Store slug (= storeId = store.id) */
90
90
  storeId: string;
91
91
  storeName: string;
92
- listingType: "standard" | "auction" | "pre-order" | "prize-draw" | "bundle";
92
+ listingType: "standard" | "auction" | "pre-order" | "prize-draw";
93
93
  isOffer?: boolean;
94
94
  offerId?: string;
95
95
  lockedPrice?: number;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
- import { categoriesRepository, productRepository } from "../../../repositories";
3
+ import { categoriesRepository, productRepository, } from "../../../repositories";
4
4
  import { ROUTES } from "../../../next";
5
5
  import { Container, Main, Section } from "../../../ui";
6
6
  import { BrandDetailTabs } from "./BrandDetailTabs";
@@ -9,7 +9,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
9
9
  .getCategoryBySlug(slug)
10
10
  .catch(() => undefined)));
11
11
  const brandName = brand?.name;
12
- const [productsResult, auctionsResult, preOrdersResult] = await Promise.all([
12
+ const [productsResult, auctionsResult, preOrdersResult, prizeDrawsResult, allBundles] = await Promise.all([
13
13
  brandName
14
14
  ? productRepository
15
15
  .list({
@@ -40,7 +40,32 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
40
40
  })
41
41
  .catch(() => null)
42
42
  : Promise.resolve(null),
43
+ brandName
44
+ ? productRepository
45
+ .list({
46
+ filters: `status==published,brand==${brandName},listingType==prize-draw`,
47
+ sorts: "-createdAt",
48
+ page: 1,
49
+ pageSize: 1,
50
+ })
51
+ .catch(() => null)
52
+ : Promise.resolve(null),
53
+ // SB-UNI-D — bundles are categoryType:"bundle" rows on the categories
54
+ // collection. We pull all bundle categories and filter client-side by
55
+ // brand affinity until the bundle storage carries an explicit brandSlug.
56
+ brandName
57
+ ? categoriesRepository
58
+ .listByType("bundle", { activeOnly: true, limit: 50 })
59
+ .catch(() => [])
60
+ : Promise.resolve([]),
43
61
  ]);
62
+ const brandLower = brandName?.toLowerCase();
63
+ const brandBundles = brandLower
64
+ ? allBundles.filter((b) => {
65
+ const seo = b.seo?.keywords?.map((k) => k.toLowerCase()) ?? [];
66
+ return seo.includes(brandLower);
67
+ })
68
+ : [];
44
69
  const coverImage = brand?.display?.coverImage;
45
70
  const hasCover = Boolean(coverImage);
46
71
  const brandColor = brand?.display?.color || "#6366f1";
@@ -48,7 +73,13 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
48
73
  products: productsResult?.total ?? brand?.metrics?.productCount ?? 0,
49
74
  auctions: auctionsResult?.total ?? brand?.metrics?.auctionCount ?? 0,
50
75
  preOrders: preOrdersResult?.total ?? 0,
76
+ prizeDraws: prizeDrawsResult?.total ?? 0,
77
+ bundles: brandBundles.length,
51
78
  };
52
- const totalItems = counts.products + counts.auctions + counts.preOrders;
53
- return (_jsxs(Main, { children: [_jsxs("section", { className: `relative overflow-hidden ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-zinc-50 dark:bg-zinc-900"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 bg-center bg-cover", style: { backgroundImage: `url(${coverImage})` } }), _jsx("div", { className: "absolute inset-0 bg-black/55" })] })), !hasCover && (_jsx("div", { className: "absolute inset-0 opacity-10", style: { backgroundColor: brandColor } })), _jsxs("div", { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs("nav", { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Home" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS ?? "/brands"), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Brands" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx("span", { className: hasCover ? "text-white font-medium" : "text-zinc-900 dark:text-zinc-100 font-medium", children: brand?.name ?? slug })] }), _jsxs("div", { className: "flex items-center gap-4 mb-3", children: [brand?.display?.icon && (_jsx("span", { className: `text-5xl leading-none ${!hasCover ? "" : ""}`, children: brand.display.icon })), _jsxs("div", { children: [_jsx("h1", { className: `text-3xl md:text-4xl font-bold ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx("p", { className: `text-base max-w-2xl mt-1 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: brand.description }))] })] }), _jsxs("div", { className: "flex flex-wrap gap-2 mt-3", children: [counts.products > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx("span", { className: `text-sm ${hasCover ? "text-white/60" : "text-zinc-400"}`, children: "No items listed yet" }))] })] })] }), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, counts: counts })) : (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "Brand not found." })) }) })] }));
79
+ const totalItems = counts.products +
80
+ counts.auctions +
81
+ counts.preOrders +
82
+ counts.prizeDraws +
83
+ counts.bundles;
84
+ return (_jsxs(Main, { children: [_jsxs("section", { className: `relative overflow-hidden ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-zinc-50 dark:bg-zinc-900"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 bg-center bg-cover", style: { backgroundImage: `url(${coverImage})` } }), _jsx("div", { className: "absolute inset-0 bg-black/55" })] })), !hasCover && (_jsx("div", { className: "absolute inset-0 opacity-10", style: { backgroundColor: brandColor } })), _jsxs("div", { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs("nav", { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Home" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS ?? "/brands"), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Brands" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx("span", { className: hasCover ? "text-white font-medium" : "text-zinc-900 dark:text-zinc-100 font-medium", children: brand?.name ?? slug })] }), _jsxs("div", { className: "flex items-center gap-4 mb-3", children: [brand?.display?.icon && (_jsx("span", { className: `text-5xl leading-none ${!hasCover ? "" : ""}`, children: brand.display.icon })), _jsxs("div", { children: [_jsx("h1", { className: `text-3xl md:text-4xl font-bold ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx("p", { className: `text-base max-w-2xl mt-1 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: brand.description }))] })] }), _jsxs("div", { className: "flex flex-wrap gap-2 mt-3", children: [counts.products > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), counts.auctions > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), counts.preOrders > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [counts.preOrders.toLocaleString(), " ", counts.preOrders === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx("span", { className: `text-sm ${hasCover ? "text-white/60" : "text-zinc-400"}`, children: "No items listed yet" }))] })] })] }), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts })) : (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "Brand not found." })) }) })] }));
54
85
  }
@@ -1,10 +1,14 @@
1
+ import type { CategoryDocument } from "../schemas";
1
2
  export interface BrandDetailTabsProps {
2
3
  brandName: string;
3
4
  initialProductsData?: any;
5
+ initialBundles?: CategoryDocument[];
4
6
  counts?: {
5
7
  products?: number;
6
8
  auctions?: number;
7
9
  preOrders?: number;
10
+ prizeDraws?: number;
11
+ bundles?: number;
8
12
  };
9
13
  }
10
- export declare function BrandDetailTabs({ brandName, initialProductsData, counts }: BrandDetailTabsProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, }: BrandDetailTabsProps): import("react/jsx-runtime").JSX.Element;
@@ -4,19 +4,33 @@ import { useState } from "react";
4
4
  import { CategoryProductsListing } from "./CategoryProductsListing";
5
5
  import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
6
6
  import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing";
7
+ import { PrizeDrawsIndexListing } from "../../products/components/PrizeDrawsIndexListing";
8
+ import { CategoryBundlesListing } from "./CategoryBundlesListing";
9
+ import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
7
10
  function tabLabel(label, count) {
8
11
  if (!count)
9
12
  return label;
10
13
  return `${label} (${count.toLocaleString()})`;
11
14
  }
12
- export function BrandDetailTabs({ brandName, initialProductsData, counts }) {
15
+ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, }) {
13
16
  const [activeTab, setActiveTab] = useState("products");
14
- const TABS = [
15
- { id: "products", label: tabLabel("Products", counts?.products) },
16
- { id: "auctions", label: tabLabel("Auctions", counts?.auctions) },
17
- { id: "pre-orders", label: tabLabel("Pre-Orders", counts?.preOrders) },
18
- ];
19
- return (_jsxs("div", { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6", children: TABS.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `px-5 py-2.5 text-sm font-medium transition-colors -mb-px border-b-2 ${activeTab === t.id
17
+ const countFor = (id) => {
18
+ switch (id) {
19
+ case "products":
20
+ return counts?.products;
21
+ case "auctions":
22
+ return counts?.auctions;
23
+ case "pre-orders":
24
+ return counts?.preOrders;
25
+ case "prize-draws":
26
+ return counts?.prizeDraws;
27
+ case "bundles":
28
+ return counts?.bundles;
29
+ default:
30
+ return undefined;
31
+ }
32
+ };
33
+ return (_jsxs("div", { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6 overflow-x-auto", children: CATEGORY_PAGE_TABS.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `px-5 py-2.5 text-sm font-medium whitespace-nowrap transition-colors -mb-px border-b-2 ${activeTab === t.id
20
34
  ? "border-primary text-primary"
21
- : "border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: t.label }, t.id))) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { brandName: brandName }))] }));
35
+ : "border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: tabLabel(t.label, countFor(t.id)) }, t.id))) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === "prize-draws" && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
22
36
  }
@@ -0,0 +1,6 @@
1
+ import type { CategoryDocument } from "../schemas";
2
+ export interface CategoryBundlesListingProps {
3
+ initialBundles: CategoryDocument[];
4
+ brandName?: string;
5
+ }
6
+ export declare function CategoryBundlesListing({ initialBundles, brandName, }: CategoryBundlesListingProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * CategoryBundlesListing — SB-UNI-D + V replacement for the deleted
5
+ * BundlesByCategoryListing. Renders a sortable list of bundles that live
6
+ * as `categoryType:"bundle"` rows on the categories collection.
7
+ *
8
+ * Parent server-fetches the list; client owns only the sort state.
9
+ */
10
+ import { useMemo, useState } from "react";
11
+ import Link from "next/link";
12
+ import { Badge, Div, Row, Select, Stack, Text } from "../../../ui";
13
+ import { ROUTES } from "../../../next/routing/route-map";
14
+ import { formatCurrency } from "../../../utils/number.formatter";
15
+ const SORT_OPTIONS = [
16
+ { label: "Newest", value: "newest" },
17
+ { label: "Price ↑", value: "price-asc" },
18
+ { label: "Price ↓", value: "price-desc" },
19
+ ];
20
+ const STOCK_BADGE_TEXT = {
21
+ in_stock: "",
22
+ partial: "Partial",
23
+ out_of_stock: "Out of stock",
24
+ };
25
+ const STOCK_BADGE_VARIANT = {
26
+ in_stock: "success",
27
+ partial: "warning",
28
+ out_of_stock: "danger",
29
+ };
30
+ const PLACEHOLDER_EMOJI = "📦";
31
+ const COPY = {
32
+ empty: "No bundles available",
33
+ forBrand: (name) => ` for ${name}`,
34
+ yetSuffix: " yet.",
35
+ sortLabel: "Sort:",
36
+ items: (n) => `${n} item${n !== 1 ? "s" : ""}`,
37
+ priceFallback: "—",
38
+ };
39
+ function comparator(sort) {
40
+ return (a, b) => {
41
+ if (sort === "price-asc") {
42
+ return (a.bundlePriceInPaise ?? 0) - (b.bundlePriceInPaise ?? 0);
43
+ }
44
+ if (sort === "price-desc") {
45
+ return (b.bundlePriceInPaise ?? 0) - (a.bundlePriceInPaise ?? 0);
46
+ }
47
+ const aT = a.createdAt instanceof Date ? a.createdAt.getTime() : 0;
48
+ const bT = b.createdAt instanceof Date ? b.createdAt.getTime() : 0;
49
+ return bT - aT;
50
+ };
51
+ }
52
+ export function CategoryBundlesListing({ initialBundles, brandName, }) {
53
+ const [sort, setSort] = useState("newest");
54
+ const filtered = useMemo(() => initialBundles
55
+ .filter((c) => c.categoryType === "bundle")
56
+ .slice()
57
+ .sort(comparator(sort)), [initialBundles, sort]);
58
+ if (filtered.length === 0) {
59
+ return (_jsx(Div, { className: "rounded-2xl border border-dashed border-zinc-200 py-16 text-center dark:border-zinc-700", children: _jsxs(Text, { color: "muted", children: [COPY.empty, brandName ? COPY.forBrand(brandName) : "", COPY.yetSuffix] }) }));
60
+ }
61
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "end", children: [_jsx(Text, { size: "sm", color: "muted", children: COPY.sortLabel }), _jsx(Select, { options: SORT_OPTIONS, value: sort, onValueChange: setSort, "aria-label": COPY.sortLabel })] }), _jsx(Div, { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((bundle) => (_jsx(BundleCard, { bundle: bundle }, bundle.id))) })] }));
62
+ }
63
+ function BundleCard({ bundle }) {
64
+ const memberCount = bundle.bundleProductIds?.length ?? 0;
65
+ const stock = bundle.bundleStockStatus ?? "in_stock";
66
+ const badge = STOCK_BADGE_TEXT[stock];
67
+ const cover = bundle.display?.coverImage;
68
+ const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
69
+ return (_jsxs(Link, { href: href, className: "group rounded-xl border border-zinc-200 bg-white p-3 transition-colors hover:border-zinc-300 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-zinc-700", children: [_jsx(Div, { className: "mb-2 aspect-video overflow-hidden rounded-lg bg-zinc-100 dark:bg-zinc-800", children: cover ? (
70
+ // eslint-disable-next-line @next/next/no-img-element
71
+ _jsx("img", { src: cover, alt: bundle.name, className: "h-full w-full object-cover transition-transform group-hover:scale-105", loading: "lazy" })) : (_jsx(Div, { className: "flex h-full w-full items-center justify-center text-3xl", children: PLACEHOLDER_EMOJI })) }), _jsx(Text, { className: "line-clamp-2 text-sm font-semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePriceInPaise
72
+ ? formatCurrency(bundle.bundlePriceInPaise / 100, "INR")
73
+ : COPY.priceFallback }), _jsx(Text, { size: "xs", color: "muted", children: COPY.items(memberCount) })] }), badge && (_jsx(Badge, { variant: STOCK_BADGE_VARIANT[stock], className: "mt-1", children: badge }))] }));
74
+ }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
- import { categoriesRepository, productRepository } from "../../../repositories";
3
+ import { categoriesRepository, productRepository, } from "../../../repositories";
4
4
  import { ROUTES } from "../../../next";
5
5
  import { Container, Main, Section } from "../../../ui";
6
6
  import { CategoryDetailTabs } from "./CategoryDetailTabs";
@@ -8,7 +8,7 @@ export async function CategoryDetailPageView({ slug }) {
8
8
  const category = await categoriesRepository
9
9
  .getCategoryBySlug(slug)
10
10
  .catch(() => undefined);
11
- const [productsResult, auctionsCountResult, preOrdersCountResult, childCategories] = await Promise.all([
11
+ const [productsResult, auctionsCountResult, preOrdersCountResult, prizeDrawsCountResult, bundlesResult, childCategories] = await Promise.all([
12
12
  category?.id
13
13
  ? productRepository
14
14
  .list({
@@ -39,6 +39,23 @@ export async function CategoryDetailPageView({ slug }) {
39
39
  })
40
40
  .catch(() => null)
41
41
  : Promise.resolve(null),
42
+ category?.id
43
+ ? productRepository
44
+ .list({
45
+ filters: `status==published,category==${category.id},listingType==prize-draw`,
46
+ sorts: "-createdAt",
47
+ page: 1,
48
+ pageSize: 1,
49
+ })
50
+ .catch(() => null)
51
+ : Promise.resolve(null),
52
+ // SB-UNI-D — bundles fetched from the categories collection. We pull
53
+ // all active bundle rows; the carousel filters by category affinity.
54
+ category?.id
55
+ ? categoriesRepository
56
+ .listByType("bundle", { activeOnly: true, limit: 50 })
57
+ .catch(() => [])
58
+ : Promise.resolve([]),
42
59
  category?.id
43
60
  ? categoriesRepository.getChildren(category.id).catch(() => [])
44
61
  : Promise.resolve([]),
@@ -46,8 +63,16 @@ export async function CategoryDetailPageView({ slug }) {
46
63
  const productCount = productsResult?.total ?? category?.metrics?.productCount ?? 0;
47
64
  const auctionCount = auctionsCountResult?.total ?? category?.metrics?.auctionCount ?? 0;
48
65
  const preOrderCount = preOrdersCountResult?.total ?? 0;
49
- const totalCount = productCount + auctionCount + preOrderCount;
66
+ const prizeDrawCount = prizeDrawsCountResult?.total ?? 0;
67
+ const bundleCount = bundlesResult?.length ?? 0;
68
+ const totalCount = productCount + auctionCount + preOrderCount + prizeDrawCount + bundleCount;
50
69
  const coverImage = category?.display?.coverImage;
51
70
  const hasCover = Boolean(coverImage);
52
- return (_jsxs(Main, { children: [_jsxs("section", { className: `relative overflow-hidden ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-zinc-50 dark:bg-zinc-900"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 bg-center bg-cover", style: { backgroundImage: `url(${coverImage})` } }), _jsx("div", { className: "absolute inset-0 bg-black/55" })] })), _jsxs("div", { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs("nav", { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Home" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Categories" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx("span", { className: hasCover ? "text-white font-medium" : "text-zinc-900 dark:text-zinc-100 font-medium", children: category?.name ?? slug })] }), _jsx("h1", { className: `text-3xl md:text-4xl font-bold mb-2 ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx("p", { className: `text-base max-w-2xl mb-4 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: category.description })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [productCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] }))] })] })] }), childCategories.length > 0 && (_jsx("section", { className: "border-b border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 py-3", children: _jsx("div", { className: "flex gap-2.5 overflow-x-auto pb-1", style: { scrollbarWidth: "none" }, children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)), className: "flex-shrink-0 flex items-center gap-1.5 rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-4 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx("span", { className: "text-base leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx("span", { className: "text-xs text-zinc-400 dark:text-zinc-500", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, counts: { products: productCount, auctions: auctionCount, preOrders: preOrderCount } }) }) })] }));
71
+ return (_jsxs(Main, { children: [_jsxs("section", { className: `relative overflow-hidden ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-zinc-50 dark:bg-zinc-900"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx("div", { className: "absolute inset-0 bg-center bg-cover", style: { backgroundImage: `url(${coverImage})` } }), _jsx("div", { className: "absolute inset-0 bg-black/55" })] })), _jsxs("div", { className: `relative z-10 max-w-7xl mx-auto px-4 ${hasCover ? "py-12" : "py-8"}`, children: [_jsxs("nav", { className: "flex items-center gap-1.5 text-sm mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Home" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORIES), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-zinc-500 hover:text-primary-600 transition-colors", children: "Categories" }), _jsx("span", { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx("span", { className: hasCover ? "text-white font-medium" : "text-zinc-900 dark:text-zinc-100 font-medium", children: category?.name ?? slug })] }), _jsx("h1", { className: `text-3xl md:text-4xl font-bold mb-2 ${hasCover ? "text-white" : "text-zinc-900 dark:text-zinc-50"}`, children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx("p", { className: `text-base max-w-2xl mb-4 ${hasCover ? "text-white/80" : "text-zinc-600 dark:text-zinc-400"}`, children: category.description })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [productCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), auctionCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"}`, children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs font-medium px-2.5 py-1 rounded-full ${hasCover ? "bg-white/20 text-white backdrop-blur-sm" : "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"}`, children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] }))] })] })] }), childCategories.length > 0 && (_jsx("section", { className: "border-b border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 py-3", children: _jsx("div", { className: "flex gap-2.5 overflow-x-auto pb-1", style: { scrollbarWidth: "none" }, children: childCategories.map((child) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(child.slug)), className: "flex-shrink-0 flex items-center gap-1.5 rounded-full border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-4 py-1.5 text-sm font-medium text-zinc-700 dark:text-zinc-200 hover:border-primary hover:text-primary transition-colors whitespace-nowrap", children: [child.display?.icon && (_jsx("span", { className: "text-base leading-none", children: child.display.icon })), child.name, (child.metrics?.productCount ?? 0) > 0 && (_jsx("span", { className: "text-xs text-zinc-400 dark:text-zinc-500", children: (child.metrics?.productCount ?? 0).toLocaleString() }))] }, child.id))) }) }) })), _jsx(Section, { className: "py-6", children: _jsx(Container, { size: "xl", children: _jsx(CategoryDetailTabs, { categorySlug: slug, categoryId: category?.id, initialProductsData: productsResult ?? undefined, initialBundles: bundlesResult ?? [], counts: {
72
+ products: productCount,
73
+ auctions: auctionCount,
74
+ preOrders: preOrderCount,
75
+ prizeDraws: prizeDrawCount,
76
+ bundles: bundleCount,
77
+ } }) }) })] }));
53
78
  }
@@ -1,11 +1,15 @@
1
+ import type { CategoryDocument } from "../schemas";
1
2
  export interface CategoryDetailTabsProps {
2
3
  categorySlug: string;
3
4
  categoryId?: string;
4
5
  initialProductsData?: any;
6
+ initialBundles?: CategoryDocument[];
5
7
  counts?: {
6
8
  products?: number;
7
9
  auctions?: number;
8
10
  preOrders?: number;
11
+ prizeDraws?: number;
12
+ bundles?: number;
9
13
  };
10
14
  }
11
- export declare function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, counts }: CategoryDetailTabsProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles, counts, }: CategoryDetailTabsProps): import("react/jsx-runtime").JSX.Element;
@@ -4,19 +4,33 @@ import { useState } from "react";
4
4
  import { CategoryProductsListing } from "./CategoryProductsListing";
5
5
  import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing";
6
6
  import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing";
7
+ import { PrizeDrawsIndexListing } from "../../products/components/PrizeDrawsIndexListing";
8
+ import { CategoryBundlesListing } from "./CategoryBundlesListing";
9
+ import { CATEGORY_PAGE_TABS } from "../../products/constants/listing-tabs";
7
10
  function tabLabel(label, count) {
8
11
  if (!count)
9
12
  return label;
10
13
  return `${label} (${count.toLocaleString()})`;
11
14
  }
12
- export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, counts }) {
15
+ export function CategoryDetailTabs({ categorySlug, categoryId, initialProductsData, initialBundles = [], counts, }) {
13
16
  const [activeTab, setActiveTab] = useState("products");
14
- const TABS = [
15
- { id: "products", label: tabLabel("Products", counts?.products) },
16
- { id: "auctions", label: tabLabel("Auctions", counts?.auctions) },
17
- { id: "pre-orders", label: tabLabel("Pre-Orders", counts?.preOrders) },
18
- ];
19
- return (_jsxs("div", { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6", children: TABS.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `px-5 py-2.5 text-sm font-medium transition-colors -mb-px border-b-2 ${activeTab === t.id
17
+ const countFor = (id) => {
18
+ switch (id) {
19
+ case "products":
20
+ return counts?.products;
21
+ case "auctions":
22
+ return counts?.auctions;
23
+ case "pre-orders":
24
+ return counts?.preOrders;
25
+ case "prize-draws":
26
+ return counts?.prizeDraws;
27
+ case "bundles":
28
+ return counts?.bundles;
29
+ default:
30
+ return undefined;
31
+ }
32
+ };
33
+ return (_jsxs("div", { children: [_jsx("div", { className: "flex border-b border-zinc-200 dark:border-slate-700 mb-6 overflow-x-auto", children: CATEGORY_PAGE_TABS.map((t) => (_jsx("button", { type: "button", onClick: () => setActiveTab(t.id), className: `px-5 py-2.5 text-sm font-medium whitespace-nowrap transition-colors -mb-px border-b-2 ${activeTab === t.id
20
34
  ? "border-primary text-primary"
21
- : "border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: t.label }, t.id))) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug }))] }));
35
+ : "border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"}`, children: tabLabel(t.label, countFor(t.id)) }, t.id))) }), activeTab === "products" && (_jsx(CategoryProductsListing, { categorySlug: categorySlug, categoryId: categoryId, initialData: initialProductsData })), activeTab === "auctions" && (_jsx(AuctionsIndexListing, { categorySlug: categorySlug })), activeTab === "pre-orders" && (_jsx(PreOrdersIndexListing, { categorySlug: categorySlug })), activeTab === "prize-draws" && (_jsx(PrizeDrawsIndexListing, { categorySlug: categorySlug })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles }))] }));
22
36
  }
@@ -0,0 +1,2 @@
1
+ import type { MigrationStep } from "../../_internal/shared/schema-versions";
2
+ export declare const migrations: Record<number, MigrationStep<any>>;
@@ -0,0 +1,10 @@
1
+ // SB-UNI X3 — migrations shell for category.
2
+ // v1 schema is current; no migrations needed yet.
3
+ //
4
+ // To add a migration:
5
+ // 1. Bump the relevant SCHEMA_VERSIONS entry in
6
+ // `_internal/shared/schema-versions.ts` (e.g. `categories: 2`)
7
+ // 2. Add a row below keyed by the FROM-version, returning the upgraded doc
8
+ // 3. Repository wraps reads with `migrateDocument(doc, current, migrations)`
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export const migrations = {};
@@ -26,5 +26,34 @@ export declare class CategoriesRepository extends BaseRepository<CategoryDocumen
26
26
  * Cloud Functions: full-overwrite category metrics (used by nightly reconciliation job).
27
27
  */
28
28
  setMetrics(categoryId: string, productCount: number, auctionCount: number, productIds: string[], auctionIds: string[]): Promise<void>;
29
+ /** SB-UNI — discriminator-keyed listing (sublistings, brands, bundles). */
30
+ listByType(type: import("../types").CategoryType, opts?: {
31
+ limit?: number;
32
+ activeOnly?: boolean;
33
+ }): Promise<CategoryDocument[]>;
34
+ /** SB-UNI — locate a category by slug, optionally constrained by discriminator. */
35
+ findBySlugAndType(slug: string, type?: import("../types").CategoryType): Promise<CategoryDocument | null>;
36
+ /**
37
+ * SB-UNI-B — fetch products linked to a sublisting category (legacy
38
+ * sublistingCategoryId field on ProductDocument), ordered by price asc.
39
+ * Used by /api/sublisting-categories/[slug]/listings.
40
+ */
41
+ getSublistingListings(sublistingId: string, limit?: number): Promise<Record<string, unknown>[]>;
42
+ /**
43
+ * SB-UNI-B — delete a sublisting category and unlink all products
44
+ * that referenced it. Mirrors the cascade behavior of the old
45
+ * SublistingCategoriesRepository.delete().
46
+ */
47
+ deleteWithSublistingUnlink(categoryId: string): Promise<void>;
48
+ /**
49
+ * SB-UNI-C — list active brand categories ordered by displayOrder.
50
+ * Replaces the old `brandsRepository.findActive()` call site.
51
+ */
52
+ findActiveBrands(): Promise<CategoryDocument[]>;
53
+ /**
54
+ * SB-UNI-B — derive the canonical `sublisting-{slug}` ID from a
55
+ * human-entered category name.
56
+ */
57
+ generateSublistingId(name: string): string;
29
58
  }
30
59
  export declare const categoriesRepository: CategoriesRepository;
@@ -364,6 +364,88 @@ export class CategoriesRepository extends BaseRepository {
364
364
  updatedAt: new Date(),
365
365
  });
366
366
  }
367
+ /** SB-UNI — discriminator-keyed listing (sublistings, brands, bundles). */
368
+ async listByType(type, opts = {}) {
369
+ let q = this.db
370
+ .collection(this.collection)
371
+ .where("categoryType", "==", type);
372
+ if (opts.activeOnly)
373
+ q = q.where("isActive", "==", true);
374
+ if (opts.limit)
375
+ q = q.limit(opts.limit);
376
+ const snap = await q.get();
377
+ return snap.docs.map((d) => this.mapDoc(d));
378
+ }
379
+ /** SB-UNI — locate a category by slug, optionally constrained by discriminator. */
380
+ async findBySlugAndType(slug, type) {
381
+ let q = this.db
382
+ .collection(this.collection)
383
+ .where("slug", "==", slug)
384
+ .limit(1);
385
+ if (type)
386
+ q = q.where("categoryType", "==", type);
387
+ const snap = await q.get();
388
+ if (snap.empty)
389
+ return null;
390
+ return this.mapDoc(snap.docs[0]);
391
+ }
392
+ /**
393
+ * SB-UNI-B — fetch products linked to a sublisting category (legacy
394
+ * sublistingCategoryId field on ProductDocument), ordered by price asc.
395
+ * Used by /api/sublisting-categories/[slug]/listings.
396
+ */
397
+ async getSublistingListings(sublistingId, limit = 20) {
398
+ const snap = await this.db
399
+ .collection("products")
400
+ .where("sublistingCategoryId", "==", sublistingId)
401
+ .where("status", "==", "published")
402
+ .orderBy("price", "asc")
403
+ .limit(limit)
404
+ .get();
405
+ return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
406
+ }
407
+ /**
408
+ * SB-UNI-B — delete a sublisting category and unlink all products
409
+ * that referenced it. Mirrors the cascade behavior of the old
410
+ * SublistingCategoriesRepository.delete().
411
+ */
412
+ async deleteWithSublistingUnlink(categoryId) {
413
+ const batch = this.db.batch();
414
+ const productsSnap = await this.db
415
+ .collection("products")
416
+ .where("sublistingCategoryId", "==", categoryId)
417
+ .get();
418
+ for (const doc of productsSnap.docs) {
419
+ batch.update(doc.ref, { sublistingCategoryId: null, updatedAt: new Date() });
420
+ }
421
+ batch.delete(this.db.collection(this.collection).doc(categoryId));
422
+ await batch.commit();
423
+ }
424
+ /**
425
+ * SB-UNI-C — list active brand categories ordered by displayOrder.
426
+ * Replaces the old `brandsRepository.findActive()` call site.
427
+ */
428
+ async findActiveBrands() {
429
+ const snap = await this.db
430
+ .collection(this.collection)
431
+ .where("categoryType", "==", "brand")
432
+ .where("isActive", "==", true)
433
+ .orderBy("order", "asc")
434
+ .get();
435
+ return snap.docs.map((d) => this.mapDoc(d));
436
+ }
437
+ /**
438
+ * SB-UNI-B — derive the canonical `sublisting-{slug}` ID from a
439
+ * human-entered category name.
440
+ */
441
+ generateSublistingId(name) {
442
+ const base = name
443
+ .toLowerCase()
444
+ .trim()
445
+ .replace(/[^a-z0-9]+/g, "-")
446
+ .replace(/^-+|-+$/g, "");
447
+ return base.startsWith("sublisting-") ? base : `sublisting-${base}`;
448
+ }
367
449
  }
368
450
  CategoriesRepository.SIEVE_FIELDS = {
369
451
  name: { canFilter: true, canSort: true },
@@ -372,6 +454,7 @@ CategoriesRepository.SIEVE_FIELDS = {
372
454
  isActive: { canFilter: true, canSort: false },
373
455
  isFeatured: { canFilter: true, canSort: false },
374
456
  isBrand: { canFilter: true, canSort: false },
457
+ categoryType: { canFilter: true, canSort: false },
375
458
  isSearchable: { canFilter: true, canSort: false },
376
459
  parentId: { canFilter: true, canSort: false, path: "parentIds" },
377
460
  order: { canFilter: true, canSort: true },
@@ -6,6 +6,32 @@
6
6
  */
7
7
  import type { CategoryAncestor, CategoryMetrics, CategoryDisplay } from "../types";
8
8
  export type { CategoryAncestor, CategoryMetrics };
9
+ /**
10
+ * How a bundle resolves its member products.
11
+ *
12
+ * `static` — hand-picked product IDs. The admin editor multi-select writes
13
+ * here; bundleProductIds mirrors for index-friendly queries.
14
+ *
15
+ * `dynamic` — publisher-pack style: filter against the products collection
16
+ * (e.g. "all Pokémon TCG products in this category, ordered by price asc,
17
+ * limit N"). Resolved by onProductStockChange + a scheduled job; the
18
+ * resolved IDs are cached on `bundleProductIds` with a `bundleQueryResolvedAt`
19
+ * timestamp.
20
+ */
21
+ export type BundleQueryRule = {
22
+ type: "static";
23
+ productIds: string[];
24
+ } | {
25
+ type: "dynamic";
26
+ filter: {
27
+ categorySlug?: string;
28
+ brandSlug?: string;
29
+ tags?: string[];
30
+ listingType?: "standard" | "pre-order";
31
+ };
32
+ orderBy?: "price-asc" | "price-desc" | "createdAt-desc";
33
+ limit: number;
34
+ };
9
35
  /** Full Firestore category document (includes server-only fields) */
10
36
  export interface CategoryDocumentSEO {
11
37
  title: string;
@@ -56,8 +82,34 @@ export interface CategoryDocument {
56
82
  metrics: CategoryDocumentMetrics;
57
83
  isFeatured: boolean;
58
84
  featuredPriority?: number;
85
+ /** @deprecated Use categoryType === "brand". Will be removed once seed + admin form fully migrate. */
59
86
  isBrand?: boolean;
60
- type?: import("../types").CategoryType;
87
+ /**
88
+ * Discriminator — distinguishes plain categories from sublistings (tier-4
89
+ * leaf groups under a parent category) and brands (storefront-facing
90
+ * brand pages). SB-UNI-D will add `"bundle"` to this union.
91
+ */
92
+ categoryType?: import("../types").CategoryType;
93
+ /** Sublisting/grading code (e.g. "108/120", "PSA 10") — categoryType==="sublisting". */
94
+ itemCode?: string;
95
+ /** Brand homepage URL — categoryType==="brand". */
96
+ brandWebsite?: string;
97
+ /** Brand country of origin — categoryType==="brand". */
98
+ brandCountry?: string;
99
+ /** Brand founding year — categoryType==="brand". */
100
+ brandFounded?: number;
101
+ /** Banner image for the brand storefront page — categoryType==="brand". */
102
+ brandBannerImage?: string;
103
+ /** Discounted bundle price in paise. */
104
+ bundlePriceInPaise?: number;
105
+ /** Rule resolving the bundle's member products — static list or live query. */
106
+ bundleQueryRule?: BundleQueryRule;
107
+ /** Snapshot stock state — recomputed by onProductStockChange. */
108
+ bundleStockStatus?: "in_stock" | "partial" | "out_of_stock";
109
+ /** Timestamp of the last dynamic-rule resolution. */
110
+ bundleQueryResolvedAt?: Date;
111
+ /** Hand-picked products list (mirror of bundleQueryRule for static rules); kept for index-friendly queries. */
112
+ bundleProductIds?: string[];
61
113
  seo: CategoryDocumentSEO;
62
114
  display: CategoryDocumentDisplay;
63
115
  isActive: boolean;
@@ -75,7 +127,7 @@ export interface CategoryDocument {
75
127
  ancestors: CategoryAncestor[];
76
128
  }
77
129
  export declare const CATEGORIES_COLLECTION: "categories";
78
- export declare const CATEGORIES_INDEXED_FIELDS: readonly ["slug", "rootId", "tier", "parentIds", "isLeaf", "isFeatured", "featuredPriority", "isBrand", "isActive", "isSearchable", "showOnHomepage", "createdBy", "createdByType", "createdByStoreId", "createdAt"];
130
+ export declare const CATEGORIES_INDEXED_FIELDS: readonly ["slug", "rootId", "tier", "parentIds", "isLeaf", "isFeatured", "featuredPriority", "isBrand", "categoryType", "isActive", "isSearchable", "showOnHomepage", "createdBy", "createdByType", "createdByStoreId", "createdAt"];
79
131
  export declare const MIN_ITEMS_FOR_FEATURED: 8;
80
132
  export declare const MAX_FEATURED_CATEGORIES: 4;
81
133
  export declare const DEFAULT_CATEGORY_DATA: Partial<CategoryDocument>;
@@ -103,7 +155,12 @@ export declare const categoryQueryHelpers: {
103
155
  readonly byRootId: (rootId: string) => readonly ["rootId", "==", string];
104
156
  readonly children: (parentId: string) => readonly ["parentIds", "array-contains", string];
105
157
  readonly featured: () => readonly ["isFeatured", "==", true];
158
+ /** @deprecated Use byCategoryType("brand"). */
106
159
  readonly brands: () => readonly ["isBrand", "==", true];
160
+ /** SB-UNI B + C + D — discriminator-based listing. */
161
+ readonly byCategoryType: (type: import("../types").CategoryType) => readonly ["categoryType", "==", import("..").CategoryType];
162
+ readonly sublistings: () => readonly ["categoryType", "==", "sublisting"];
163
+ readonly brandPages: () => readonly ["categoryType", "==", "brand"];
107
164
  readonly active: () => readonly ["isActive", "==", true];
108
165
  readonly searchable: () => readonly ["isSearchable", "==", true];
109
166
  readonly byCreator: (userId: string) => readonly ["createdBy", "==", string];