@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
@@ -16,6 +16,7 @@ export const CATEGORIES_INDEXED_FIELDS = [
16
16
  "isFeatured",
17
17
  "featuredPriority",
18
18
  "isBrand",
19
+ "categoryType",
19
20
  "isActive",
20
21
  "isSearchable",
21
22
  "showOnHomepage",
@@ -97,7 +98,12 @@ export const categoryQueryHelpers = {
97
98
  byRootId: (rootId) => ["rootId", "==", rootId],
98
99
  children: (parentId) => ["parentIds", "array-contains", parentId],
99
100
  featured: () => ["isFeatured", "==", true],
101
+ /** @deprecated Use byCategoryType("brand"). */
100
102
  brands: () => ["isBrand", "==", true],
103
+ /** SB-UNI B + C + D — discriminator-based listing. */
104
+ byCategoryType: (type) => ["categoryType", "==", type],
105
+ sublistings: () => ["categoryType", "==", "sublisting"],
106
+ brandPages: () => ["categoryType", "==", "brand"],
101
107
  active: () => ["isActive", "==", true],
102
108
  searchable: () => ["isSearchable", "==", true],
103
109
  byCreator: (userId) => ["createdBy", "==", userId],
@@ -1,4 +1,4 @@
1
- export type CategoryType = "category" | "concern" | "collection" | "brand";
1
+ export type CategoryType = "category" | "sublisting" | "brand" | "bundle";
2
2
  export interface CategorySeo {
3
3
  title?: string;
4
4
  description?: string;
@@ -24,7 +24,15 @@ export interface CategoryAncestor {
24
24
  }
25
25
  export interface CategoryItem {
26
26
  id: string;
27
- type?: CategoryType;
27
+ categoryType?: CategoryType;
28
+ /** Sublisting/grading code, e.g. "108/120" or "PSA 10" — only set when categoryType==="sublisting". */
29
+ itemCode?: string;
30
+ /** Brand website — only set when categoryType==="brand". */
31
+ brandWebsite?: string;
32
+ /** Brand country — only set when categoryType==="brand". */
33
+ brandCountry?: string;
34
+ /** Brand founded year — only set when categoryType==="brand". */
35
+ brandFounded?: number;
28
36
  name: string;
29
37
  slug: string;
30
38
  description?: string;
@@ -38,7 +46,7 @@ export interface CategoryItem {
38
46
  metrics?: CategoryMetrics;
39
47
  isFeatured?: boolean;
40
48
  featuredPriority?: number;
41
- /** @deprecated Use type === "brand" */ isBrand?: boolean;
49
+ /** @deprecated Use categoryType === "brand" */ isBrand?: boolean;
42
50
  seo?: CategorySeo;
43
51
  display?: CategoryDisplay;
44
52
  ancestors?: CategoryAncestor[];
@@ -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 event/eventEntry.
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. `events: 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 = {};
@@ -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 faq.
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. `faq: 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 = {};
@@ -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 groupedListing.
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. `grouped: 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 = {};
@@ -1,13 +1,28 @@
1
1
  /**
2
2
  * Grouped Listings Firestore Document Types & Constants
3
3
  *
4
- * A grouped listing bundles multiple products together, optionally at a
5
- * discounted bundle price. Standard products and pre-orders only — auctions
6
- * are excluded per GP1 spec. Displayed as a horizontal scroller of circular
7
- * thumbnail cards between the buy-box and the TABS row on the detail page.
4
+ * SB-UNI-V re-scoped this from a "bundle-like" pricing construct (with
5
+ * `bundlePrice` / `originalPrice` / `discountPercent`) to a **theme group**
6
+ * a horizontal scroller that links related listings on the detail page
7
+ * without any pricing semantics. Pricing-aware bundles now live on the
8
+ * categories collection with `categoryType:"bundle"` (SB-UNI-D).
8
9
  *
9
- * Used in Session 86 (GP1–GP2).
10
+ * A grouped listing survives partial sellout: when the count of currently-
11
+ * active members drops below `minActiveMembers`, `visibilityStatus` flips
12
+ * to "hidden" (recomputed by `onProductStockChange`).
10
13
  */
14
+ export type GroupTheme =
15
+ /** "Other anime figures you might like" */
16
+ "related"
17
+ /** "Same character across multiple lines" */
18
+ | "character"
19
+ /** "All HG kits in this lineage" */
20
+ | "lineage"
21
+ /** "From the same set / wave / drop" */
22
+ | "set"
23
+ /** Default / unspecified theme */
24
+ | "generic";
25
+ export type GroupVisibility = "visible" | "hidden";
11
26
  export interface GroupedListingDocument {
12
27
  id: string;
13
28
  slug: string;
@@ -15,10 +30,14 @@ export interface GroupedListingDocument {
15
30
  description?: string;
16
31
  productIds: string[];
17
32
  coverImage?: string;
18
- bundlePrice?: number;
19
- originalPrice?: number;
20
- discountPercent?: number;
21
- currency: "INR";
33
+ /** SB-UNI-V — semantic label for the group (drives section heading copy). */
34
+ groupTheme: GroupTheme;
35
+ /** Hide the group if fewer than this many members are active. Default 2. */
36
+ minActiveMembers: number;
37
+ /** Count of currently-active members. Maintained by onProductStockChange. */
38
+ activeMemberCount: number;
39
+ /** Computed from activeMemberCount < minActiveMembers. */
40
+ visibilityStatus: GroupVisibility;
22
41
  isActive: boolean;
23
42
  isFeatured: boolean;
24
43
  storeId?: string;
@@ -29,4 +48,4 @@ export interface GroupedListingDocument {
29
48
  updatedAt: Date;
30
49
  }
31
50
  export declare const GROUPED_LISTINGS_COLLECTION: "groupedListings";
32
- export declare const GROUPED_LISTINGS_INDEXED_FIELDS: readonly ["storeId", "brandSlug", "categorySlug", "isActive", "isFeatured", "createdAt"];
51
+ export declare const GROUPED_LISTINGS_INDEXED_FIELDS: readonly ["storeId", "brandSlug", "categorySlug", "isActive", "isFeatured", "groupTheme", "visibilityStatus", "createdAt"];
@@ -1,12 +1,15 @@
1
1
  /**
2
2
  * Grouped Listings Firestore Document Types & Constants
3
3
  *
4
- * A grouped listing bundles multiple products together, optionally at a
5
- * discounted bundle price. Standard products and pre-orders only — auctions
6
- * are excluded per GP1 spec. Displayed as a horizontal scroller of circular
7
- * thumbnail cards between the buy-box and the TABS row on the detail page.
4
+ * SB-UNI-V re-scoped this from a "bundle-like" pricing construct (with
5
+ * `bundlePrice` / `originalPrice` / `discountPercent`) to a **theme group**
6
+ * a horizontal scroller that links related listings on the detail page
7
+ * without any pricing semantics. Pricing-aware bundles now live on the
8
+ * categories collection with `categoryType:"bundle"` (SB-UNI-D).
8
9
  *
9
- * Used in Session 86 (GP1–GP2).
10
+ * A grouped listing survives partial sellout: when the count of currently-
11
+ * active members drops below `minActiveMembers`, `visibilityStatus` flips
12
+ * to "hidden" (recomputed by `onProductStockChange`).
10
13
  */
11
14
  export const GROUPED_LISTINGS_COLLECTION = "groupedListings";
12
15
  export const GROUPED_LISTINGS_INDEXED_FIELDS = [
@@ -15,5 +18,7 @@ export const GROUPED_LISTINGS_INDEXED_FIELDS = [
15
18
  "categorySlug",
16
19
  "isActive",
17
20
  "isFeatured",
21
+ "groupTheme",
22
+ "visibilityStatus",
18
23
  "createdAt",
19
24
  ];
@@ -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 history.
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. `history: 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 = {};
@@ -10,12 +10,12 @@ export function useFeaturedAuctions(options) {
10
10
  queryKey: ["auctions", "featured", options?.filterByBrand ?? "all"],
11
11
  initialData: options?.initialData,
12
12
  queryFn: async () => {
13
- const promotedRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=isAuction%3D%3Dtrue%2Cstatus%3D%3Dpublished%2CisPromoted%3D%3Dtrue${brandFilter}&pageSize=18`);
13
+ const promotedRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=listingType%3D%3Dauction%2Cstatus%3D%3Dpublished%2CisPromoted%3D%3Dtrue${brandFilter}&pageSize=18`);
14
14
  const promoted = promotedRes?.items ?? [];
15
15
  if (promoted.length >= MIN_COUNT)
16
16
  return promoted;
17
17
  const remaining = MIN_COUNT - promoted.length;
18
- const latestRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=isAuction%3D%3Dtrue%2Cstatus%3D%3Dpublished${brandFilter}&sorts=-createdAt&pageSize=${remaining + promoted.length}`);
18
+ const latestRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=listingType%3D%3Dauction%2Cstatus%3D%3Dpublished${brandFilter}&sorts=-createdAt&pageSize=${remaining + promoted.length}`);
19
19
  const latest = latestRes?.items ?? [];
20
20
  const existingIds = new Set(promoted.map((a) => a.id));
21
21
  const filler = latest
@@ -10,12 +10,12 @@ export function useFeaturedPreOrders(options) {
10
10
  queryKey: ["pre-orders", "featured", options?.filterByBrand ?? "all"],
11
11
  initialData: options?.initialData,
12
12
  queryFn: async () => {
13
- const featuredRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=isPreOrder%3D%3Dtrue%2Cstatus%3D%3Dpublished${brandFilter}&sorts=preOrderDeliveryDate&pageSize=6`);
13
+ const featuredRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=listingType%3D%3Dpre-order%2Cstatus%3D%3Dpublished${brandFilter}&sorts=preOrderDeliveryDate&pageSize=6`);
14
14
  const featured = featuredRes?.items ?? [];
15
15
  if (featured.length >= MIN_COUNT)
16
16
  return featured;
17
17
  const remaining = MIN_COUNT - featured.length;
18
- const latestRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=isPreOrder%3D%3Dtrue%2Cstatus%3D%3Dpublished${brandFilter}&sorts=-createdAt&pageSize=${remaining + featured.length}`);
18
+ const latestRes = await apiClient.get(`${PRODUCT_ENDPOINTS.LIST}?filters=listingType%3D%3Dpre-order%2Cstatus%3D%3Dpublished${brandFilter}&sorts=-createdAt&pageSize=${remaining + featured.length}`);
19
19
  const latest = latestRes?.items ?? [];
20
20
  const existingIds = new Set(featured.map((p) => p.id));
21
21
  const filler = latest
@@ -25,7 +25,8 @@ import { BrandsSection } from "../components/BrandsSection";
25
25
  import { SocialFeedSection } from "../components/SocialFeedSection";
26
26
  import { CustomCardsSection } from "../components/CustomCardsSection";
27
27
  import { GoogleReviewsSection } from "../components/GoogleReviewsSection";
28
- import { FeaturedBundlesSection } from "../../bundles/components/FeaturedBundlesSection";
28
+ // SB-UNI-V FeaturedBundlesSection deleted; bundle homepage section to be
29
+ // rebuilt against categoryType:"bundle" queries in a follow-up cohort.
29
30
  import { PrizeDrawsSection } from "../../products/components/PrizeDrawsSection";
30
31
  import { EventRafflesSection } from "../../events/components/EventRafflesSection";
31
32
  import { CollectionCardsSection } from "../components/CollectionCardsSection";
@@ -174,8 +175,9 @@ function renderSectionElement(section, newsletterFormSlot, faqItems, slides, liv
174
175
  return (_jsx(GoogleReviewsSection, { placeId: cfg?.placeId ?? "", maxReviews: cfg?.maxReviews ?? 6, minRating: cfg?.minRating ?? 0, layout: cfg?.layout ?? "grid", showRating: cfg?.showRating ?? true, showDate: cfg?.showDate ?? true, linkToGoogleMaps: cfg?.linkToGoogleMaps ?? true, googleMapsUrl: cfg?.googleMapsUrl }));
175
176
  }
176
177
  case "featured-bundles": {
177
- const cfg = config;
178
- return _jsx(FeaturedBundlesSection, { config: cfg ?? {} });
178
+ // SB-UNI-V FeaturedBundlesSection deleted with features/bundles/. The
179
+ // category-based replacement lands in a follow-up.
180
+ return null;
179
181
  }
180
182
  case "prize-draws": {
181
183
  const cfg = config;
@@ -24,7 +24,7 @@ export function AvatarUpload({ currentPhotoURL, currentCropData, userId, display
24
24
  const [pendingUploadFile, setPendingUploadFile] = useState(null);
25
25
  const fileInputRef = useRef(null);
26
26
  const { showToast } = useToast();
27
- const { mutateAsync: uploadMedia, isPending: isUploading, error: uploadApiError, } = useMediaUpload();
27
+ const { upload: uploadMedia, isPending: isUploading, error: uploadApiError, } = useMediaUpload();
28
28
  useEffect(() => {
29
29
  const hasPending = pendingCropData !== null && pendingUploadFile !== null;
30
30
  onPendingStateChange?.(hasPending);
@@ -53,37 +53,15 @@ export function AvatarUpload({ currentPhotoURL, currentCropData, userId, display
53
53
  const handleConfirmSave = async () => {
54
54
  if (!pendingUploadFile || !pendingCropData)
55
55
  return;
56
- const formData = new FormData();
57
- formData.append("file", pendingUploadFile);
58
56
  const nameParts = (displayName ?? "").trim().split(/\s+/).filter(Boolean);
59
57
  const firstName = nameParts[0] || userId;
60
58
  const lastName = nameParts.slice(1).join("-") || firstName;
61
- formData.append("context", JSON.stringify({
62
- type: "user-avatar",
63
- firstName,
64
- lastName,
65
- }));
66
- formData.append("folder", "users");
67
- formData.append("public", "true");
68
- formData.append("metadata", JSON.stringify({
69
- cropX: 0,
70
- cropY: 0,
71
- cropWidth: 400,
72
- cropHeight: 400,
73
- zoom: pendingCropData.zoom ?? 1,
74
- focalX: (pendingCropData.position?.x ?? 50) / 100,
75
- focalY: (pendingCropData.position?.y ?? 50) / 100,
76
- aspectRatio: "1:1",
77
- objectFit: "cover",
78
- displayMode: "avatar",
79
- originalWidth: 400,
80
- originalHeight: 400,
81
- mimeType: pendingUploadFile.type,
82
- fileSize: pendingUploadFile.size,
83
- }));
84
59
  try {
85
- const result = await uploadMedia(formData);
86
- const downloadURL = result.url;
60
+ const downloadURL = await uploadMedia(pendingUploadFile, "users", true, {
61
+ type: "user-avatar",
62
+ firstName,
63
+ lastName,
64
+ });
87
65
  await onUploadSuccess?.(downloadURL, pendingCropData);
88
66
  setPreviewUrl(downloadURL);
89
67
  setCropData(pendingCropData);
@@ -1,8 +1,14 @@
1
1
  /**
2
2
  * useMedia — upload/crop/trim hooks for @mohasinac/feat-media.
3
3
  *
4
- * Wraps @mohasinac/http apiClient via TanStack Query mutations.
5
- * Endpoint paths can be overridden per call for non-standard deployments.
4
+ * useMediaUpload uses the signed-URL flow:
5
+ * 1. POST /api/media/sign server issues v4 signed PUT URL
6
+ * 2. PUT directly to GCS — bytes bypass the Vercel function (Rule #6)
7
+ * 3. POST /api/media/finalize — server runs magic-byte check + returns URL
8
+ *
9
+ * The hook surface (`upload(file, folder, isPublic, context) => Promise<string>`)
10
+ * is unchanged so existing field components (MediaUploadField, MediaUploadList,
11
+ * ImageUpload, MediaPickerModal) keep working without modification.
6
12
  */
7
13
  import type { MediaFilenameContext } from "../../../utils/id-generators";
8
14
  export interface MediaUploadResult {
@@ -12,6 +18,12 @@ export interface MediaUploadResult {
12
18
  size?: number;
13
19
  type?: string;
14
20
  }
21
+ interface UploadVariables {
22
+ file: File;
23
+ folder: string;
24
+ isPublic: boolean;
25
+ context?: MediaFilenameContext | Record<string, unknown>;
26
+ }
15
27
  export interface MediaCropInput {
16
28
  sourceUrl: string;
17
29
  x: number;
@@ -31,13 +43,16 @@ export interface MediaTrimInput {
31
43
  quality?: "low" | "medium" | "high";
32
44
  }
33
45
  /**
34
- * useMediaUpload — uploads a file via FormData to /api/media/upload.
46
+ * useMediaUpload — uploads a file via the signed-URL flow.
35
47
  *
36
48
  * @example
37
49
  * const { upload, isPending } = useMediaUpload();
38
50
  * const url = await upload(file, "products", true);
39
51
  */
40
- export declare function useMediaUpload(endpoint?: string): {
52
+ export declare function useMediaUpload(endpoints?: {
53
+ sign?: string;
54
+ finalize?: string;
55
+ }): {
41
56
  upload: (file: File, folder?: string, isPublic?: boolean, context?: MediaFilenameContext | Record<string, unknown>) => Promise<string>;
42
57
  data: undefined;
43
58
  variables: undefined;
@@ -47,68 +62,68 @@ export declare function useMediaUpload(endpoint?: string): {
47
62
  isPending: false;
48
63
  isSuccess: false;
49
64
  status: "idle";
50
- mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, FormData, unknown>;
65
+ mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, UploadVariables, unknown>;
51
66
  reset: () => void;
52
67
  context: unknown;
53
68
  failureCount: number;
54
69
  failureReason: Error | null;
55
70
  isPaused: boolean;
56
71
  submittedAt: number;
57
- mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, FormData, unknown>;
72
+ mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, UploadVariables, unknown>;
58
73
  } | {
59
74
  upload: (file: File, folder?: string, isPublic?: boolean, context?: MediaFilenameContext | Record<string, unknown>) => Promise<string>;
60
75
  data: undefined;
61
- variables: FormData;
76
+ variables: UploadVariables;
62
77
  error: null;
63
78
  isError: false;
64
79
  isIdle: false;
65
80
  isPending: true;
66
81
  isSuccess: false;
67
82
  status: "pending";
68
- mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, FormData, unknown>;
83
+ mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, UploadVariables, unknown>;
69
84
  reset: () => void;
70
85
  context: unknown;
71
86
  failureCount: number;
72
87
  failureReason: Error | null;
73
88
  isPaused: boolean;
74
89
  submittedAt: number;
75
- mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, FormData, unknown>;
90
+ mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, UploadVariables, unknown>;
76
91
  } | {
77
92
  upload: (file: File, folder?: string, isPublic?: boolean, context?: MediaFilenameContext | Record<string, unknown>) => Promise<string>;
78
93
  data: undefined;
79
94
  error: Error;
80
- variables: FormData;
95
+ variables: UploadVariables;
81
96
  isError: true;
82
97
  isIdle: false;
83
98
  isPending: false;
84
99
  isSuccess: false;
85
100
  status: "error";
86
- mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, FormData, unknown>;
101
+ mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, UploadVariables, unknown>;
87
102
  reset: () => void;
88
103
  context: unknown;
89
104
  failureCount: number;
90
105
  failureReason: Error | null;
91
106
  isPaused: boolean;
92
107
  submittedAt: number;
93
- mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, FormData, unknown>;
108
+ mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, UploadVariables, unknown>;
94
109
  } | {
95
110
  upload: (file: File, folder?: string, isPublic?: boolean, context?: MediaFilenameContext | Record<string, unknown>) => Promise<string>;
96
111
  data: MediaUploadResult;
97
112
  error: null;
98
- variables: FormData;
113
+ variables: UploadVariables;
99
114
  isError: false;
100
115
  isIdle: false;
101
116
  isPending: false;
102
117
  isSuccess: true;
103
118
  status: "success";
104
- mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, FormData, unknown>;
119
+ mutate: import("@tanstack/react-query").UseMutateFunction<MediaUploadResult, Error, UploadVariables, unknown>;
105
120
  reset: () => void;
106
121
  context: unknown;
107
122
  failureCount: number;
108
123
  failureReason: Error | null;
109
124
  isPaused: boolean;
110
125
  submittedAt: number;
111
- mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, FormData, unknown>;
126
+ mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<MediaUploadResult, Error, UploadVariables, unknown>;
112
127
  };
113
128
  /**
114
129
  * useMediaCrop — sends pixel-crop params to /api/media/crop.
@@ -202,3 +217,4 @@ export declare function useMediaCleanup(endpoint?: string): {
202
217
  submittedAt: number;
203
218
  mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<void, Error, string[], unknown>;
204
219
  };
220
+ export {};
@@ -1,33 +1,68 @@
1
1
  /**
2
2
  * useMedia — upload/crop/trim hooks for @mohasinac/feat-media.
3
3
  *
4
- * Wraps @mohasinac/http apiClient via TanStack Query mutations.
5
- * Endpoint paths can be overridden per call for non-standard deployments.
4
+ * useMediaUpload uses the signed-URL flow:
5
+ * 1. POST /api/media/sign server issues v4 signed PUT URL
6
+ * 2. PUT directly to GCS — bytes bypass the Vercel function (Rule #6)
7
+ * 3. POST /api/media/finalize — server runs magic-byte check + returns URL
8
+ *
9
+ * The hook surface (`upload(file, folder, isPublic, context) => Promise<string>`)
10
+ * is unchanged so existing field components (MediaUploadField, MediaUploadList,
11
+ * ImageUpload, MediaPickerModal) keep working without modification.
6
12
  */
7
13
  import { useMutation } from "@tanstack/react-query";
8
14
  import { apiClient } from "../../../http";
15
+ import { classifyMime, MAX_BYTES, MAX_LABEL, isAllowedMime, } from "../../../_internal/shared/media/limits";
9
16
  import { MEDIA_ENDPOINTS } from "../../../constants/api-endpoints";
10
17
  // --- Hooks --------------------------------------------------------------------
11
18
  /**
12
- * useMediaUpload — uploads a file via FormData to /api/media/upload.
19
+ * useMediaUpload — uploads a file via the signed-URL flow.
13
20
  *
14
21
  * @example
15
22
  * const { upload, isPending } = useMediaUpload();
16
23
  * const url = await upload(file, "products", true);
17
24
  */
18
- export function useMediaUpload(endpoint = MEDIA_ENDPOINTS.UPLOAD) {
25
+ export function useMediaUpload(endpoints = {}) {
26
+ const signEndpoint = endpoints.sign ?? MEDIA_ENDPOINTS.SIGN;
27
+ const finalizeEndpoint = endpoints.finalize ?? MEDIA_ENDPOINTS.FINALIZE;
19
28
  const mutation = useMutation({
20
- mutationFn: (formData) => apiClient.upload(endpoint, formData),
29
+ mutationFn: async ({ file, folder, isPublic, context }) => {
30
+ const contentType = file.type;
31
+ // Client-side precheck — fail fast before any network call. Catches
32
+ // image-vs-video kind mismatch and oversize files with the same
33
+ // user-facing message the server would have returned.
34
+ if (!isAllowedMime(contentType)) {
35
+ throw new Error(`Unsupported file type: ${contentType || "unknown"}`);
36
+ }
37
+ const kind = classifyMime(contentType);
38
+ if (file.size > MAX_BYTES[kind]) {
39
+ throw new Error(`File too large — ${kind} uploads must be ≤ ${MAX_LABEL[kind]}`);
40
+ }
41
+ const signResponse = await apiClient.post(signEndpoint, {
42
+ contentType,
43
+ size: file.size,
44
+ folder,
45
+ isPublic,
46
+ context,
47
+ });
48
+ // PUT bytes directly to Cloud Storage. The browser does NOT send
49
+ // credentials and the bucket must allow the request origin via CORS.
50
+ const putResponse = await fetch(signResponse.uploadUrl, {
51
+ method: "PUT",
52
+ body: file,
53
+ headers: { "Content-Type": signResponse.contentType },
54
+ });
55
+ if (!putResponse.ok) {
56
+ throw new Error(`Storage PUT failed with status ${putResponse.status}`);
57
+ }
58
+ return apiClient.post(finalizeEndpoint, {
59
+ storagePath: signResponse.storagePath,
60
+ isPublic,
61
+ });
62
+ },
21
63
  });
22
64
  const upload = async (file, folder = "uploads", isPublic = true, context) => {
23
- const formData = new FormData();
24
- formData.append("file", file);
25
- formData.append("folder", folder);
26
- formData.append("public", isPublic.toString());
27
- if (context) {
28
- formData.append("context", JSON.stringify(context));
29
- }
30
- const data = await mutation.mutateAsync(formData);
65
+ const data = await mutation.mutateAsync({ file, folder, isPublic, context });
31
66
  return data.url;
32
67
  };
33
68
  return { ...mutation, upload };
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  /**
4
4
  * ImageUpload — canonical image upload component for @mohasinac/feat-media.
5
5
  *
6
- * Stage locally → caller-provided onUpload() → /api/media/upload.
6
+ * Stage locally → caller-provided onUpload() → sign+PUT+finalize signed-URL flow.
7
7
  * Optional focal-point crop (enableCrop, default true).
8
8
  * Optional camera capture (captureSource="both").
9
9
  */
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * MediaUploadField — single-file upload field for @mohasinac/feat-media.
5
5
  *
6
6
  * For video: optionally opens VideoTrimModal then VideoThumbnailSelector after upload.
7
- * Stage locally → caller-provided onUpload() → /api/media/upload.
7
+ * Stage locally → caller-provided onUpload() → sign+PUT+finalize signed-URL flow.
8
8
  */
9
9
  import { useState, useRef, useEffect } from "react";
10
10
  import { useTranslations } from "next-intl";
@@ -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 conversation.
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. `messages: 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 = {};
@@ -23,9 +23,18 @@ export function OrderCard({ order, onClick, labels = {} }) {
23
23
  })
24
24
  : "";
25
25
  const statusColor = STATUS_COLORS[order.orderStatus] ?? "bg-neutral-100 text-neutral-700";
26
+ // SB8-F — count unrevealed prize-draw entries to surface a "reveals pending" badge.
27
+ const unrevealedPrizeDraws = order.items.filter((it) => it.listingType === "prize-draw" &&
28
+ it.prizeRevealStatus !== undefined &&
29
+ it.prizeRevealStatus !== "revealed");
30
+ const revealsRemaining = unrevealedPrizeDraws.length;
31
+ const earliestDeadline = unrevealedPrizeDraws
32
+ .map((it) => it.prizeRevealDeadline)
33
+ .filter((d) => !!d)
34
+ .sort()[0];
26
35
  return (_jsxs(Div, { role: onClick ? "button" : undefined, tabIndex: onClick ? 0 : undefined, onKeyDown: onClick
27
36
  ? (e) => (e.key === "Enter" || e.key === " ") && onClick(order)
28
- : undefined, onClick: onClick ? () => onClick(order) : undefined, className: `rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-5 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5 text-xs text-neutral-400 dark:text-zinc-500", children: date }))] }), _jsx(Span, { className: `rounded-full px-3 py-1 text-xs font-semibold capitalize ${statusColor}`, children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { className: "gap-2", children: [item.image && (_jsx(Div, { role: "img", "aria-label": item.title, className: "h-10 w-10 rounded-lg bg-center bg-cover", style: { backgroundImage: `url(${item.image})` } })), _jsxs(Div, { children: [_jsx(Text, { className: `text-sm font-medium text-neutral-900 dark:text-zinc-100 ${THEME_CONSTANTS.utilities.textClamp1}`, children: item.title }), _jsxs(Text, { className: "text-xs text-neutral-400 dark:text-zinc-500", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { className: "self-center text-xs text-neutral-400 dark:text-zinc-500", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100 dark:border-slate-700 pt-3", children: [_jsxs(Span, { className: "text-sm text-neutral-500 dark:text-zinc-400", children: [order.currency ?? "", " Total"] }), _jsx(Span, { className: "font-semibold text-neutral-900 dark:text-zinc-100", children: formatCurrency(order.total, order.currency) })] })] }));
37
+ : undefined, onClick: onClick ? () => onClick(order) : undefined, className: `rounded-xl border border-neutral-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-5 ${onClick ? "cursor-pointer transition hover:shadow-md" : ""}`, children: [_jsxs(Row, { wrap: true, align: "start", justify: "between", gap: "3", children: [_jsxs(Div, { children: [_jsxs(Text, { className: "text-xs text-neutral-500 dark:text-zinc-400", children: ["Order #", order.id.slice(-8).toUpperCase()] }), date && (_jsx(Text, { className: "mt-0.5 text-xs text-neutral-400 dark:text-zinc-500", children: date }))] }), _jsx(Span, { className: `rounded-full px-3 py-1 text-xs font-semibold capitalize ${statusColor}`, children: labels[order.orderStatus] ?? order.orderStatus.replace(/_/g, " ") })] }), revealsRemaining > 0 && (_jsxs(Row, { gap: "sm", className: "mt-2", children: [_jsxs(Span, { className: "inline-flex items-center rounded-full bg-fuchsia-100 dark:bg-fuchsia-900/30 px-2.5 py-0.5 text-xs font-semibold text-fuchsia-700 dark:text-fuchsia-300", children: [revealsRemaining, " ", revealsRemaining === 1 ? "reveal" : "reveals", " pending"] }), earliestDeadline && (_jsxs(Span, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: ["before ", new Date(earliestDeadline).toLocaleDateString(getDefaultLocale(), { month: "short", day: "numeric" })] }))] })), _jsxs(Row, { wrap: true, gap: "3", className: "mt-4", children: [order.items.slice(0, 3).map((item, i) => (_jsxs(Row, { className: "gap-2", children: [item.image && (_jsx(Div, { role: "img", "aria-label": item.title, className: "h-10 w-10 rounded-lg bg-center bg-cover", style: { backgroundImage: `url(${item.image})` } })), _jsxs(Div, { children: [_jsx(Text, { className: `text-sm font-medium text-neutral-900 dark:text-zinc-100 ${THEME_CONSTANTS.utilities.textClamp1}`, children: item.title }), _jsxs(Text, { className: "text-xs text-neutral-400 dark:text-zinc-500", children: ["\u00D7", item.quantity] })] })] }, i))), order.items.length > 3 && (_jsxs(Span, { className: "self-center text-xs text-neutral-400 dark:text-zinc-500", children: ["+", order.items.length - 3, " more"] }))] }), _jsxs(Row, { justify: "between", className: "mt-4 border-t border-neutral-100 dark:border-slate-700 pt-3", children: [_jsxs(Span, { className: "text-sm text-neutral-500 dark:text-zinc-400", children: [order.currency ?? "", " Total"] }), _jsx(Span, { className: "font-semibold text-neutral-900 dark:text-zinc-100", children: formatCurrency(order.total, order.currency) })] })] }));
29
38
  }
30
39
  export function OrdersList({ orders, isLoading, onOrderClick, totalPages = 1, currentPage = 1, onPageChange, emptyLabel = "No orders found", }) {
31
40
  if (isLoading) {
@@ -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 order.
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. `orders: 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 = {};