@mohasinac/appkit 4.30.19 → 4.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (417) hide show
  1. package/dist/_internal/client/features/layout/useSidebarSearch.d.ts +21 -5
  2. package/dist/_internal/client/features/layout/useSidebarSearch.js +27 -6
  3. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +23 -3
  4. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +106 -87
  5. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +16 -5
  6. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -40
  7. package/dist/_internal/server/features/auth/permissions.d.ts +2 -0
  8. package/dist/_internal/server/features/auth/permissions.js +3 -2
  9. package/dist/_internal/server/features/categories/data.js +15 -4
  10. package/dist/_internal/server/features/classified/index.d.ts +0 -1
  11. package/dist/_internal/server/features/classified/index.js +0 -1
  12. package/dist/_internal/server/features/digital-code/index.d.ts +0 -1
  13. package/dist/_internal/server/features/digital-code/index.js +0 -1
  14. package/dist/_internal/server/features/live/index.d.ts +0 -1
  15. package/dist/_internal/server/features/live/index.js +0 -1
  16. package/dist/_internal/server/features/products/data.js +15 -5
  17. package/dist/_internal/server/features/products/list-public.d.ts +9 -0
  18. package/dist/_internal/server/features/products/list-public.js +95 -7
  19. package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
  20. package/dist/_internal/server/features/site-settings/adapters.js +8 -5
  21. package/dist/_internal/server/features/tester/visibility.d.ts +2 -1
  22. package/dist/_internal/server/features/tester/visibility.js +2 -2
  23. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  24. package/dist/_internal/server/functions/scheduled.js +27 -1
  25. package/dist/_internal/server/jobs/core/hardBanCascade.js +4 -0
  26. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  27. package/dist/_internal/server/jobs/core/index.js +2 -0
  28. package/dist/_internal/server/jobs/core/pageViewPrune.d.ts +10 -0
  29. package/dist/_internal/server/jobs/core/pageViewPrune.js +44 -0
  30. package/dist/_internal/server/jobs/core/pageViewRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/core/pageViewRollup.js +87 -0
  32. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  33. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  34. package/dist/_internal/server/jobs/handlers/pageViewPrune.d.ts +2 -0
  35. package/dist/_internal/server/jobs/handlers/pageViewPrune.js +2 -0
  36. package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +2 -0
  37. package/dist/_internal/server/jobs/handlers/pageViewRollup.js +2 -0
  38. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  39. package/dist/_internal/shared/actions/action-registry.js +69 -22
  40. package/dist/_internal/shared/checkout/rules/classified.rule.d.ts +9 -4
  41. package/dist/_internal/shared/checkout/rules/classified.rule.js +7 -2
  42. package/dist/_internal/shared/features/layout/matchesNavQuery.d.ts +14 -0
  43. package/dist/_internal/shared/features/layout/matchesNavQuery.js +72 -0
  44. package/dist/_internal/shared/features/layout/types.d.ts +37 -1
  45. package/dist/_internal/shared/features/layout/types.js +19 -1
  46. package/dist/_internal/shared/features/notifications/action-url.d.ts +9 -1
  47. package/dist/_internal/shared/features/notifications/action-url.js +47 -0
  48. package/dist/_internal/shared/features/offers/config.d.ts +65 -0
  49. package/dist/_internal/shared/features/offers/config.js +73 -2
  50. package/dist/_internal/shared/features/orders/schema.d.ts +19 -1
  51. package/dist/_internal/shared/features/orders/schema.js +22 -2
  52. package/dist/_internal/shared/features/site-settings/types.d.ts +12 -3
  53. package/dist/_internal/shared/listing-types/capabilities.d.ts +8 -2
  54. package/dist/_internal/shared/listing-types/capabilities.js +6 -1
  55. package/dist/_internal/shared/listing-types/classified/ctas.js +6 -1
  56. package/dist/_internal/shared/listing-types/feature-flags.d.ts +10 -5
  57. package/dist/_internal/shared/listing-types/feature-flags.js +11 -6
  58. package/dist/client.d.ts +37 -20
  59. package/dist/client.js +36 -14
  60. package/dist/configs/next.js +10 -2
  61. package/dist/constants/api-endpoints.d.ts +18 -54
  62. package/dist/constants/api-endpoints.js +6 -26
  63. package/dist/constants/field-names.d.ts +6 -24
  64. package/dist/constants/field-names.js +10 -27
  65. package/dist/constants/geo/countries.d.ts +50 -0
  66. package/dist/constants/geo/countries.js +129 -0
  67. package/dist/constants/geo/subdivisions.d.ts +13 -0
  68. package/dist/constants/geo/subdivisions.js +104 -0
  69. package/dist/constants/index.d.ts +2 -2
  70. package/dist/constants/index.js +2 -2
  71. package/dist/constants/table-keys.d.ts +24 -0
  72. package/dist/constants/table-keys.js +28 -0
  73. package/dist/contracts/extend.d.ts +0 -9
  74. package/dist/contracts/index.d.ts +1 -1
  75. package/dist/contracts/realtime-session.js +7 -0
  76. package/dist/core/browser.d.ts +1 -1
  77. package/dist/core/browser.js +1 -1
  78. package/dist/core/index.d.ts +1 -1
  79. package/dist/core/index.js +1 -1
  80. package/dist/core/site-config.d.ts +0 -3
  81. package/dist/core/site-config.js +0 -3
  82. package/dist/errors/messages.d.ts +0 -8
  83. package/dist/errors/messages.js +0 -9
  84. package/dist/features/about/schemas/index.d.ts +8 -8
  85. package/dist/features/account/components/AddressFilters.js +32 -11
  86. package/dist/features/account/components/AddressForm.d.ts +26 -25
  87. package/dist/features/account/components/AddressForm.js +81 -103
  88. package/dist/features/account/components/AddressesIndexListing.js +8 -4
  89. package/dist/features/account/components/UserSidebar.d.ts +3 -4
  90. package/dist/features/account/components/UserSupportView.js +89 -58
  91. package/dist/features/account/components/index.d.ts +1 -7
  92. package/dist/features/account/components/index.js +0 -3
  93. package/dist/features/account/hooks/useAddresses.d.ts +4 -3
  94. package/dist/features/account/hooks/useAddresses.js +4 -6
  95. package/dist/features/account/hooks/useCollapsedSections.d.ts +51 -10
  96. package/dist/features/account/hooks/useCollapsedSections.js +211 -38
  97. package/dist/features/account/hooks/useProfile.d.ts +8 -0
  98. package/dist/features/account/index.d.ts +0 -3
  99. package/dist/features/account/index.js +0 -3
  100. package/dist/features/account/schemas/index.d.ts +0 -92
  101. package/dist/features/account/schemas/index.js +42 -20
  102. package/dist/features/addresses/components/AddressLocationFields.d.ts +13 -0
  103. package/dist/features/addresses/components/AddressLocationFields.js +41 -0
  104. package/dist/features/addresses/hooks/usePostalLookup.d.ts +17 -0
  105. package/dist/features/addresses/hooks/usePostalLookup.js +133 -0
  106. package/dist/features/addresses/schemas/address-form.d.ts +423 -0
  107. package/dist/features/addresses/schemas/address-form.js +212 -0
  108. package/dist/features/addresses/schemas/index.d.ts +1 -0
  109. package/dist/features/addresses/schemas/index.js +8 -0
  110. package/dist/features/admin/actions/admin-read-actions.d.ts +1 -1
  111. package/dist/features/admin/actions/index.d.ts +0 -1
  112. package/dist/features/admin/actions/index.js +0 -1
  113. package/dist/features/admin/actions/notification-actions.js +1 -41
  114. package/dist/features/admin/components/AdminActionIndexView.d.ts +32 -0
  115. package/dist/features/admin/components/AdminActionIndexView.js +128 -0
  116. package/dist/features/admin/components/AdminAdEditorView.js +159 -163
  117. package/dist/features/admin/components/AdminAddressEditorView.js +97 -55
  118. package/dist/features/admin/components/AdminBrandEditorView.d.ts +15 -0
  119. package/dist/features/admin/components/AdminBrandEditorView.js +113 -77
  120. package/dist/features/admin/components/AdminBundleEditorView.d.ts +19 -9
  121. package/dist/features/admin/components/AdminBundleEditorView.js +136 -168
  122. package/dist/features/admin/components/AdminCategoryEditorView.js +120 -43
  123. package/dist/features/admin/components/AdminCouponEditorView.js +145 -109
  124. package/dist/features/admin/components/AdminEmployeeEditorView.js +125 -56
  125. package/dist/features/admin/components/AdminFaqEditorView.js +112 -58
  126. package/dist/features/admin/components/AdminFeatureEditorView.js +108 -68
  127. package/dist/features/admin/components/AdminNavEditorView.js +69 -44
  128. package/dist/features/admin/components/AdminOrderEditorView.js +47 -10
  129. package/dist/features/admin/components/AdminPrizeDrawsView.js +16 -2
  130. package/dist/features/admin/components/AdminSidebar.d.ts +11 -7
  131. package/dist/features/admin/components/AdminSiteSettingsView.js +37 -37
  132. package/dist/features/admin/components/AdminStoreEditorView.js +75 -52
  133. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +71 -30
  134. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +95 -65
  135. package/dist/features/admin/components/BrandQuickCreateForm.d.ts +10 -0
  136. package/dist/features/admin/components/BrandQuickCreateForm.js +49 -26
  137. package/dist/features/admin/components/CategoryQuickCreateForm.d.ts +11 -0
  138. package/dist/features/admin/components/CategoryQuickCreateForm.js +48 -26
  139. package/dist/features/admin/components/index.d.ts +2 -2
  140. package/dist/features/admin/components/index.js +1 -1
  141. package/dist/features/admin/constants/site-settings-tabs.d.ts +120 -0
  142. package/dist/features/admin/constants/site-settings-tabs.js +152 -0
  143. package/dist/features/admin/index.d.ts +0 -3
  144. package/dist/features/admin/index.js +0 -2
  145. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +35 -0
  146. package/dist/features/admin/repository/analytics-rollup.repository.js +30 -0
  147. package/dist/features/admin/schemas/admin-editor-forms.d.ts +56 -57
  148. package/dist/features/admin/schemas/admin-editor-forms.js +97 -54
  149. package/dist/features/admin/schemas/admin-ops-forms.d.ts +57 -5
  150. package/dist/features/admin/schemas/admin-ops-forms.js +104 -20
  151. package/dist/features/admin/schemas/admin-user-form.js +7 -0
  152. package/dist/features/admin/schemas/firestore.d.ts +48 -95
  153. package/dist/features/admin/schemas/firestore.js +15 -151
  154. package/dist/features/admin/schemas/nav-item-form.js +9 -2
  155. package/dist/features/admin/server.d.ts +0 -4
  156. package/dist/features/admin/server.js +0 -2
  157. package/dist/features/admin/types/index.d.ts +0 -7
  158. package/dist/features/analytics/repository/page-views.repository.d.ts +14 -0
  159. package/dist/features/analytics/repository/page-views.repository.js +24 -0
  160. package/dist/features/auth/actions/index.d.ts +0 -1
  161. package/dist/features/auth/actions/index.js +0 -1
  162. package/dist/features/auth/actions/realtime-token-actions.js +1 -12
  163. package/dist/features/auth/auth-helpers.js +5 -7
  164. package/dist/features/auth/components/Guards.js +2 -1
  165. package/dist/features/auth/components/LoginForm.d.ts +0 -2
  166. package/dist/features/auth/components/LoginForm.js +1 -3
  167. package/dist/features/auth/components/RegisterForm.js +62 -19
  168. package/dist/features/auth/permissions/constants.d.ts +27 -1
  169. package/dist/features/auth/role-predicates.d.ts +75 -7
  170. package/dist/features/auth/role-predicates.js +28 -2
  171. package/dist/features/auth/schemas/firestore.d.ts +22 -0
  172. package/dist/features/auth/schemas/index.d.ts +1 -1
  173. package/dist/features/auth/schemas/index.js +44 -8
  174. package/dist/features/auth/types/index.d.ts +14 -1
  175. package/dist/features/blog/api/route.js +10 -2
  176. package/dist/features/blog/components/BlogIndexListing.js +1 -1
  177. package/dist/features/blog/hooks/useBlog.js +2 -2
  178. package/dist/features/blog/schemas/index.d.ts +4 -0
  179. package/dist/features/blog/schemas/index.js +2 -0
  180. package/dist/features/blog/types/index.d.ts +1 -1
  181. package/dist/features/catalogue/components/CatalogueItemEditorView.d.ts +22 -0
  182. package/dist/features/catalogue/components/CatalogueItemEditorView.js +112 -52
  183. package/dist/features/catalogue/schemas/validation.d.ts +4 -4
  184. package/dist/features/catalogue/schemas/validation.js +47 -10
  185. package/dist/features/categories/schemas/bundle-form.d.ts +142 -0
  186. package/dist/features/categories/schemas/bundle-form.js +149 -0
  187. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +5 -4
  188. package/dist/features/classified/components/ClassifiedDetailPageView.js +37 -16
  189. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  190. package/dist/features/consultation/schemas/index.js +34 -10
  191. package/dist/features/corporate/schemas/index.js +36 -15
  192. package/dist/features/filters/FilterPanel.js +3 -3
  193. package/dist/features/filters/RangeFilter.d.ts +10 -0
  194. package/dist/features/filters/RangeFilter.js +41 -4
  195. package/dist/features/homepage/components/MarketplaceHomepageView.js +24 -5
  196. package/dist/features/homepage/hooks/useBlogArticles.js +1 -1
  197. package/dist/features/layout/BottomActions.js +84 -11
  198. package/dist/features/layout/BottomActionsContext.d.ts +1 -6
  199. package/dist/features/layout/BottomActionsContext.js +1 -1
  200. package/dist/features/layout/hooks/useBottomActions.js +2 -2
  201. package/dist/features/layout/hooks/useFormBottomActions.d.ts +12 -1
  202. package/dist/features/layout/hooks/useFormBottomActions.js +12 -6
  203. package/dist/features/lottery/schemas/config-write.d.ts +9 -5
  204. package/dist/features/lottery/schemas/config-write.js +69 -9
  205. package/dist/features/orders/schemas/buyer-forms.d.ts +6 -3
  206. package/dist/features/orders/schemas/buyer-forms.js +31 -3
  207. package/dist/features/orders/schemas/index.d.ts +2 -2
  208. package/dist/features/products/components/GroupSettingsPanel.js +3 -1
  209. package/dist/features/products/components/MakeOfferButton.d.ts +13 -2
  210. package/dist/features/products/components/MakeOfferButton.js +33 -11
  211. package/dist/features/products/components/ProductDetailActions.js +0 -1
  212. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -3
  213. package/dist/features/products/components/ProductDetailPageView.js +8 -5
  214. package/dist/features/products/constants/action-defs.d.ts +1 -2
  215. package/dist/features/products/constants/action-defs.js +40 -43
  216. package/dist/features/products/repository/products.repository.d.ts +5 -1
  217. package/dist/features/products/repository/products.repository.js +23 -1
  218. package/dist/features/products/schemas/firestore.d.ts +7 -3
  219. package/dist/features/products/schemas/product-features.validators.js +3 -0
  220. package/dist/features/promotions/components/CouponCard.js +8 -7
  221. package/dist/features/scams/schemas/firestore.d.ts +15 -1
  222. package/dist/features/scams/schemas/report-form.d.ts +5 -5
  223. package/dist/features/scams/schemas/report-form.js +18 -3
  224. package/dist/features/search/action-index/derive.d.ts +49 -0
  225. package/dist/features/search/action-index/derive.js +121 -0
  226. package/dist/features/search/action-index/repository.d.ts +42 -0
  227. package/dist/features/search/action-index/repository.js +100 -0
  228. package/dist/features/search/action-index/settings-entries.d.ts +41 -0
  229. package/dist/features/search/action-index/settings-entries.js +268 -0
  230. package/dist/features/search/action-index/types.d.ts +81 -0
  231. package/dist/features/search/action-index/types.js +102 -0
  232. package/dist/features/search/action-index/validate-entry.d.ts +34 -0
  233. package/dist/features/search/action-index/validate-entry.js +114 -0
  234. package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
  235. package/dist/features/search/hooks/useNavSuggestions.js +23 -3
  236. package/dist/features/search/repository/search.repository.js +3 -1
  237. package/dist/features/seller/actions/offer-actions.js +43 -24
  238. package/dist/features/seller/actions/seller-actions.js +29 -5
  239. package/dist/features/seller/components/CategoryChipPicker.d.ts +9 -0
  240. package/dist/features/seller/components/CategoryChipPicker.js +12 -0
  241. package/dist/features/seller/components/SellerAddressesView.js +5 -1
  242. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +83 -43
  243. package/dist/features/seller/components/SellerArtView.d.ts +14 -7
  244. package/dist/features/seller/components/SellerArtView.js +4 -119
  245. package/dist/features/seller/components/SellerClassifiedView.d.ts +14 -7
  246. package/dist/features/seller/components/SellerClassifiedView.js +4 -124
  247. package/dist/features/seller/components/SellerCouponEditorView.d.ts +22 -1
  248. package/dist/features/seller/components/SellerCouponEditorView.js +91 -45
  249. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +14 -7
  250. package/dist/features/seller/components/SellerDigitalCodesView.js +4 -126
  251. package/dist/features/seller/components/SellerListingTypeView.d.ts +66 -0
  252. package/dist/features/seller/components/SellerListingTypeView.js +277 -0
  253. package/dist/features/seller/components/SellerLiveView.d.ts +14 -7
  254. package/dist/features/seller/components/SellerLiveView.js +4 -126
  255. package/dist/features/seller/components/SellerSidebar.d.ts +3 -6
  256. package/dist/features/seller/components/SellerStickersView.d.ts +14 -7
  257. package/dist/features/seller/components/SellerStickersView.js +4 -119
  258. package/dist/features/seller/components/SellerSupportView.d.ts +31 -0
  259. package/dist/features/seller/components/SellerSupportView.js +100 -0
  260. package/dist/features/seller/components/index.d.ts +4 -8
  261. package/dist/features/seller/components/index.js +2 -4
  262. package/dist/features/seller/index.d.ts +0 -1
  263. package/dist/features/seller/index.js +0 -1
  264. package/dist/features/seller/schemas/coupon-form.d.ts +214 -0
  265. package/dist/features/seller/schemas/coupon-form.js +155 -23
  266. package/dist/features/seller/schemas/offer-forms.d.ts +16 -7
  267. package/dist/features/seller/schemas/offer-forms.js +28 -12
  268. package/dist/features/shell/FormShell.js +2 -2
  269. package/dist/features/shell/QuickFormDrawer.js +2 -2
  270. package/dist/features/shell/SectionForm.d.ts +19 -1
  271. package/dist/features/shell/SectionForm.js +104 -17
  272. package/dist/features/shell/build-sections.d.ts +23 -0
  273. package/dist/features/shell/build-sections.js +106 -8
  274. package/dist/features/shell/field-ui-meta.d.ts +44 -0
  275. package/dist/features/shell/field-ui-meta.js +14 -7
  276. package/dist/features/shell/form-sections.d.ts +46 -0
  277. package/dist/features/shell/form-sections.js +114 -0
  278. package/dist/features/shipments/components/AdminShipmentEditorView.js +87 -64
  279. package/dist/features/shipments/schemas/validation.d.ts +2 -2
  280. package/dist/features/shipments/schemas/validation.js +44 -13
  281. package/dist/features/store-extensions/schemas/custom-role-form.js +6 -2
  282. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +7 -7
  283. package/dist/features/store-extensions/schemas/payout-method-form.js +14 -0
  284. package/dist/features/stores/actions/store-query-actions.js +3 -2
  285. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -1
  286. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -1
  287. package/dist/features/support/repository/support.repository.d.ts +21 -0
  288. package/dist/features/support/repository/support.repository.js +63 -0
  289. package/dist/features/support/schemas/firestore.d.ts +27 -2
  290. package/dist/features/support/schemas/firestore.js +2 -0
  291. package/dist/features/support/schemas/ticket-create-form.js +7 -0
  292. package/dist/features/tester/schemas/checklist-item-form.d.ts +43 -0
  293. package/dist/features/tester/schemas/checklist-item-form.js +109 -0
  294. package/dist/features/tester/seed-data/blog-tester-seed-data.js +2 -2
  295. package/dist/features/tester/seed-data/categories-tester-seed-data.js +2 -2
  296. package/dist/features/tester/seed-data/events-tester-seed-data.js +5 -5
  297. package/dist/features/tester/seed-data/orders-tester-seed-data.js +2 -2
  298. package/dist/features/tester/seed-data/products-tester-seed-data.js +48 -49
  299. package/dist/features/tester/seed-data/stores-tester-seed-data.js +3 -3
  300. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +275 -29
  301. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +48 -12
  302. package/dist/http/api-handler.js +2 -1
  303. package/dist/index.d.ts +24 -103
  304. package/dist/index.js +33 -141
  305. package/dist/jobs.d.ts +1 -1
  306. package/dist/jobs.js +1 -1
  307. package/dist/monitoring/browser.d.ts +0 -2
  308. package/dist/monitoring/browser.js +12 -1
  309. package/dist/monitoring/index.d.ts +0 -2
  310. package/dist/monitoring/index.js +0 -1
  311. package/dist/next/api/routeHandler.js +6 -3
  312. package/dist/next/index.d.ts +1 -2
  313. package/dist/next/index.js +0 -1
  314. package/dist/next/middleware/index.d.ts +0 -2
  315. package/dist/next/middleware/index.js +0 -1
  316. package/dist/next/routing/route-map.d.ts +8 -7
  317. package/dist/next/routing/route-map.js +10 -3
  318. package/dist/providers/db-firebase/admin.js +15 -1
  319. package/dist/providers/db-firebase/sieve.d.ts +22 -0
  320. package/dist/react/hooks/useListingTypeFlags.d.ts +1 -1
  321. package/dist/react/hooks/useListingTypeFlags.js +1 -1
  322. package/dist/react/hooks/usePendingFilters.js +5 -3
  323. package/dist/react/hooks/usePendingTable.js +15 -3
  324. package/dist/repositories/index.d.ts +0 -1
  325. package/dist/repositories/index.js +0 -1
  326. package/dist/schemas/registry.d.ts +197 -232
  327. package/dist/schemas/registry.js +5 -6
  328. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  329. package/dist/security/authorization.d.ts +9 -0
  330. package/dist/security/authorization.js +16 -2
  331. package/dist/security/browser.d.ts +1 -3
  332. package/dist/security/browser.js +5 -2
  333. package/dist/security/index.d.ts +1 -3
  334. package/dist/security/index.js +19 -2
  335. package/dist/security/pii-encrypt.js +16 -0
  336. package/dist/security/pii-schemas.d.ts +0 -14
  337. package/dist/security/pii-schemas.js +7 -15
  338. package/dist/seed/_helpers/category-forest.js +2 -2
  339. package/dist/seed/_helpers/media.d.ts +19 -0
  340. package/dist/seed/_helpers/media.js +75 -0
  341. package/dist/seed/blog-posts-seed-data.js +17 -17
  342. package/dist/seed/carousel-slides-seed-data.js +7 -7
  343. package/dist/seed/cart-seed-data.js +13 -13
  344. package/dist/seed/categories-seed-data.js +10 -10
  345. package/dist/seed/events-seed-data.js +24 -24
  346. package/dist/seed/factories/product.factory.js +4 -4
  347. package/dist/seed/factories/store.factory.js +3 -3
  348. package/dist/seed/factories/user.factory.js +2 -2
  349. package/dist/seed/grouped-listings-seed-data.js +11 -11
  350. package/dist/seed/homepage-sections-seed-data.js +2 -2
  351. package/dist/seed/index.d.ts +0 -1
  352. package/dist/seed/index.js +0 -1
  353. package/dist/seed/manifest.d.ts +1 -1
  354. package/dist/seed/manifest.js +0 -5
  355. package/dist/seed/notifications-seed-data.js +20 -1
  356. package/dist/seed/orders-seed-data.js +2 -2
  357. package/dist/seed/products-art-seed-data.js +16 -16
  358. package/dist/seed/products-auctions-seed-data.js +34 -34
  359. package/dist/seed/products-classifieds-seed-data.js +35 -37
  360. package/dist/seed/products-digital-codes-seed-data.js +20 -20
  361. package/dist/seed/products-live-items-seed-data.js +11 -11
  362. package/dist/seed/products-preorders-seed-data.js +13 -13
  363. package/dist/seed/products-prize-draws-seed-data.js +43 -43
  364. package/dist/seed/products-standard-seed-data.js +54 -54
  365. package/dist/seed/products-stickers-seed-data.js +15 -15
  366. package/dist/seed/reviews-seed-data.js +4 -4
  367. package/dist/seed/scammers-seed-data.js +3 -3
  368. package/dist/seed/site-settings-seed-data.js +11 -24
  369. package/dist/seed/store-extensions-seed-data.js +10 -10
  370. package/dist/seed/stores-seed-data.js +11 -11
  371. package/dist/seed/support-tickets-seed-data.d.ts +14 -0
  372. package/dist/seed/support-tickets-seed-data.js +20 -1
  373. package/dist/seed/users-seed-data.js +20 -20
  374. package/dist/server-entry.d.ts +0 -1
  375. package/dist/server-entry.js +0 -1
  376. package/dist/server.d.ts +9 -6
  377. package/dist/server.js +7 -13
  378. package/dist/styles.css +248 -68
  379. package/dist/tailwind-utilities.css +1 -1
  380. package/dist/tokens/themes/default-dark.js +2 -2
  381. package/dist/tokens/themes/default-light.js +2 -2
  382. package/dist/tokens/tokens.css +24 -4
  383. package/dist/ui/components/ActionRow.d.ts +50 -0
  384. package/dist/ui/components/ActionRow.js +53 -0
  385. package/dist/ui/components/ActionRow.style.css +145 -0
  386. package/dist/ui/components/Drawer.d.ts +10 -0
  387. package/dist/ui/components/Drawer.js +2 -1
  388. package/dist/ui/components/FilterDrawer.js +4 -1
  389. package/dist/ui/components/FilterDrawer.style.css +6 -12
  390. package/dist/ui/components/FormActionBar.js +14 -1
  391. package/dist/ui/components/FormActionBar.style.css +12 -11
  392. package/dist/ui/components/ListingLayout.js +8 -1
  393. package/dist/ui/components/ListingLayout.style.css +6 -13
  394. package/dist/ui/components/Modal.d.ts +15 -1
  395. package/dist/ui/components/Modal.js +17 -3
  396. package/dist/ui/components/QuickCreateModal.js +2 -2
  397. package/dist/ui/components/QuickCreateModal.style.css +78 -84
  398. package/dist/ui/components/RecordDetailModal.js +2 -2
  399. package/dist/ui/components/SideDrawer.d.ts +8 -0
  400. package/dist/ui/components/SideDrawer.js +2 -1
  401. package/dist/ui/components/SideDrawer.style.css +6 -3
  402. package/dist/ui/components/SideModal.d.ts +11 -1
  403. package/dist/ui/components/SideModal.js +3 -2
  404. package/dist/ui/components/SideModal.style.css +14 -0
  405. package/dist/ui/components/Typography.style.css +33 -24
  406. package/dist/ui/components/index.style.css +1 -0
  407. package/dist/ui/forms/FormShell.style.css +260 -311
  408. package/dist/ui/index.d.ts +2 -0
  409. package/dist/ui/index.js +1 -0
  410. package/dist/utils/index.d.ts +0 -1
  411. package/dist/utils/index.js +0 -1
  412. package/dist/validation/index.d.ts +1 -1
  413. package/dist/validation/index.js +1 -1
  414. package/dist/validation/schemas.d.ts +0 -19
  415. package/dist/validation/schemas.js +8 -19
  416. package/package.json +1 -1
  417. package/scripts/seed-cli.mjs +3 -6
@@ -6,7 +6,5 @@ export type { UserRole } from "./authorization";
6
6
  export { redactPii, safeDisplayName, safeDisplayEmail, maskIp } from "./pii-redact";
7
7
  export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPiiFields, encryptPii, decryptPii, piiBlindIndex, piiIndicesFor, getPiiConfigError, encryptPayoutDetails, decryptPayoutDetails, encryptShippingConfig, decryptShippingConfig, encryptPayoutBankAccount, decryptPayoutBankAccount, } from "./pii-encrypt";
8
8
  export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-mask";
9
- export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, CHAT_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
9
+ export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
10
10
  export { encryptSecret, decryptSecret, isSecretEncrypted, maskSecret, } from "./settings-encryption";
11
- export type { Permission, RoleDefinition, RbacConfig, ResolvedUser, RbacHookReturn, } from "./rbac";
12
- export { DEFAULT_ROLES, resolvePermissions, hasPermission, hasAllPermissions, hasAnyPermission, createRbacHook, Can, createRequirePermission, createRequirePermissionSync, createRbacMiddleware, } from "./rbac";
@@ -7,6 +7,23 @@ export { encryptValue, decryptValue, hmacBlindIndex, encryptPiiFields, decryptPi
7
7
  // pii-encrypt.ts) so this barrel stays safe to re-export from the
8
8
  // universal client+server index.ts. See pii-mask.ts's header comment.
9
9
  export { isPiiEncrypted, maskName, maskEmail, maskPublicReview, maskPublicBid, maskPublicEventEntry, maskOfferForSeller, ENC_PREFIX, HMAC_PREFIX, } from "./pii-mask";
10
- export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, CHAT_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
10
+ export { USER_PII_FIELDS, USER_PII_INDEX_MAP, ADDRESS_PII_FIELDS, ORDER_PII_FIELDS, PAYOUT_PII_FIELDS, PAYOUT_PII_INDEX_MAP, BID_PII_FIELDS, TOKEN_PII_FIELDS, TOKEN_PII_INDEX_MAP, REVIEW_PII_FIELDS, REVIEW_PII_INDEX_MAP, OFFER_PII_FIELDS, EVENT_ENTRY_PII_FIELDS, LOTTERY_ENTRY_PII_FIELDS, PAYMENT_METHOD_PII_FIELDS, } from "./pii-schemas";
11
11
  export { encryptSecret, decryptSecret, isSecretEncrypted, maskSecret, } from "./settings-encryption";
12
- export { DEFAULT_ROLES, resolvePermissions, hasPermission, hasAllPermissions, hasAnyPermission, createRbacHook, Can, createRequirePermission, createRequirePermissionSync, createRbacMiddleware, } from "./rbac";
12
+ // RBAC system — DELETED 2026-08-29.
13
+ //
14
+ // `security/rbac/**` was a complete second permission system: DEFAULT_ROLES,
15
+ // resolvePermissions, createRbacHook, createRequirePermission(Sync),
16
+ // createRbacMiddleware, <Can>, and its own `Permission` type. It had ZERO
17
+ // consumers outside its own directory and these barrels, and it looked alive on
18
+ // a grep only because its permission strings coincidentally overlapped the real
19
+ // union (a hazard action-defs.ts:129 already warned about).
20
+ //
21
+ // It was worse than inert. `appkit/src/index.ts` re-exported ITS `Permission`
22
+ // and ITS `hasPermission` — so a consumer importing either from the package
23
+ // root silently got the dead 6-role model instead of the live 130-member union
24
+ // and its helpers. Root Cause #53's exact shape, one module over.
25
+ //
26
+ // The live system is `features/auth/permissions/constants.ts` (the union,
27
+ // PERMISSION_GROUPS, hasPermission/hasAny/hasAll) plus
28
+ // `_internal/server/features/auth/permissions.ts` (getServerPermissions,
29
+ // checkPermission). Import from there.
@@ -222,6 +222,22 @@ export function encryptPiiFields(doc, piiFields) {
222
222
  }
223
223
  else {
224
224
  result[field] = encryptValue(value);
225
+ /*
226
+ * 🛑 The derived name is not always the name readers query.
227
+ *
228
+ * `phoneNumber` yields `phoneNumberIndex`, and NOTHING in this codebase
229
+ * reads that — every lookup uses `phoneIndex` (`USER_PII_INDEX_MAP`,
230
+ * `byPhoneIndex`, `USER_FIELDS.PHONE_INDEX`). `BaseRepository` patches
231
+ * around it by *additionally* spreading `piiIndicesFor(data,
232
+ * piiIndexMap)`, so a user document has carried BOTH the correct
233
+ * `phoneIndex` and a dead `phoneNumberIndex` on every write.
234
+ *
235
+ * The derivation stays, because `email` -> `emailIndex` is right and
236
+ * several callers depend on it, and because removing it would silently
237
+ * drop indices for every field whose repository has no `piiIndexMap`
238
+ * entry. Where a field's index name differs, the MAP is the authority —
239
+ * which is why `scripts/lib/pii-seed.mjs` indexes only from the map.
240
+ */
225
241
  result[`${field}Index`] = hmacBlindIndex(value);
226
242
  }
227
243
  }
@@ -56,26 +56,12 @@ export declare const BID_PII_FIELDS: readonly ["userEmail"];
56
56
  export declare const TOKEN_PII_FIELDS: readonly ["email"];
57
57
  /** Blind-index mapping for token email queries */
58
58
  export declare const TOKEN_PII_INDEX_MAP: Record<string, string>;
59
- /**
60
- * PII fields in conversations — buyer↔seller message bodies.
61
- *
62
- * `messages[].body` uses the array-path form, and `lastMessage` is the
63
- * denormalised copy of the newest body: encrypting one without the other would
64
- * leave every conversation's latest message readable at rest, which is most of
65
- * the exposure.
66
- *
67
- * Participant display names are deliberately NOT here — decision D1 keeps names
68
- * searchable, and both sides already know who they are talking to.
69
- */
70
- export declare const CONVERSATION_PII_FIELDS: readonly ["lastMessage", "messages[].body"];
71
59
  /** PII fields in reviews */
72
60
  export declare const REVIEW_PII_FIELDS: readonly ["userName"];
73
61
  /** Blind-index mapping for queryable review PII */
74
62
  export declare const REVIEW_PII_INDEX_MAP: Record<string, string>;
75
63
  /** PII fields in offers (buyer + seller data) */
76
64
  export declare const OFFER_PII_FIELDS: readonly ["buyerName", "buyerEmail", "sellerName"];
77
- /** PII fields in chat rooms */
78
- export declare const CHAT_PII_FIELDS: readonly ["buyerName", "sellerName"];
79
65
  /** PII fields in event entries */
80
66
  export declare const EVENT_ENTRY_PII_FIELDS: readonly ["userDisplayName", "userEmail", "ipAddress"];
81
67
  /** PII fields in lottery entries */
@@ -85,21 +85,15 @@ export const TOKEN_PII_FIELDS = ["email"];
85
85
  export const TOKEN_PII_INDEX_MAP = {
86
86
  email: "emailIndex",
87
87
  };
88
- /**
89
- * PII fields in conversations — buyer↔seller message bodies.
90
- *
91
- * `messages[].body` uses the array-path form, and `lastMessage` is the
92
- * denormalised copy of the newest body: encrypting one without the other would
93
- * leave every conversation's latest message readable at rest, which is most of
94
- * the exposure.
88
+ /*
89
+ * `CONVERSATION_PII_FIELDS` (`lastMessage`, `messages[].body`) was removed with
90
+ * the conversations feature on 2026-08-31.
95
91
  *
96
- * Participant display names are deliberately NOT here — decision D1 keeps names
97
- * searchable, and both sides already know who they are talking to.
92
+ * 🛑 Its array-path form was the ONLY user of `encryptPiiFields`' `[]` syntax
93
+ * outside status history. If a future schema needs to encrypt a field inside an
94
+ * array, check that machinery still works before relying on it — a path that no
95
+ * caller exercises is a path nothing proves.
98
96
  */
99
- export const CONVERSATION_PII_FIELDS = [
100
- "lastMessage",
101
- "messages[].body",
102
- ];
103
97
  /** PII fields in reviews */
104
98
  export const REVIEW_PII_FIELDS = ["userName"];
105
99
  /** Blind-index mapping for queryable review PII */
@@ -112,8 +106,6 @@ export const OFFER_PII_FIELDS = [
112
106
  "buyerEmail",
113
107
  "sellerName",
114
108
  ];
115
- /** PII fields in chat rooms */
116
- export const CHAT_PII_FIELDS = ["buyerName", "sellerName"];
117
109
  /** PII fields in event entries */
118
110
  export const EVENT_ENTRY_PII_FIELDS = [
119
111
  "userDisplayName",
@@ -18,8 +18,8 @@
18
18
  * @tag consumers:seed/categories-seed-data.ts
19
19
  * @tag sideEffects:none
20
20
  */
21
- import { seedExtMedia } from "./media";
22
- const cover = (slug) => seedExtMedia(`https://picsum.photos/seed/category-image-${slug}-20260101/1200/600`);
21
+ import { seedPhoto } from "../_helpers/media";
22
+ const cover = (slug) => seedPhoto(`category-image-${slug}-20260101`, 1200, 600);
23
23
  // ─────────────────────────────────────────────────────────────────────────────
24
24
  // Root 1 — Spinning Tops. The Beyblade catalogue, four generations deep:
25
25
  // generation -> part-family -> specific line
@@ -1 +1,20 @@
1
1
  export declare function seedExtMedia(url: string): string;
2
+ /**
3
+ * Turn a seed key into something a person would read.
4
+ *
5
+ * `art-image-dranzer-phoenix-2-20260817` → `Dranzer Phoenix`
6
+ *
7
+ * Strips the trailing `-YYYYMMDD` stamp, a trailing image index, and the
8
+ * leading `<kind>-image-` prefix that every seed key carries — all of which are
9
+ * bookkeeping, not the name of the thing.
10
+ */
11
+ export declare function seedPhotoLabel(seed: string): string;
12
+ /**
13
+ * A deterministic, labelled placeholder image for seed data, already wrapped in
14
+ * the media proxy.
15
+ *
16
+ * Deterministic on purpose — Root Cause #25: a seed file is re-executed on every
17
+ * `appkit-seed` invocation, so anything non-stable here would give the same
18
+ * fixture a different URL on every run.
19
+ */
20
+ export declare function seedPhoto(seed: string, width: number, height: number): string;
@@ -25,3 +25,78 @@ export function seedExtMedia(url) {
25
25
  return url;
26
26
  return MEDIA_ENDPOINTS.EXT_URL(url);
27
27
  }
28
+ /* ── Seed photography ────────────────────────────────────────────────────── */
29
+ /*
30
+ * WHY THIS EXISTS
31
+ *
32
+ * The seed catalogue used to name its image host **409 times**, in 30 files, as
33
+ * `https://picsum.photos/seed/<seed>/<w>/<h>` literals. On 2026-08-31 picsum
34
+ * went down — 503 from its origin AND its Fastly CDN — and because the host was
35
+ * written into every URL rather than referenced from one place, virtually every
36
+ * image on the production site broke at once and there was no single edit that
37
+ * could move them.
38
+ *
39
+ * That is the actual defect. A third party going down is not preventable; being
40
+ * unable to react to it in one line is.
41
+ *
42
+ * Measured the same day, direct: picsum 503 · fastly.picsum 503 (10.5s) ·
43
+ * placekitten 521 · loremflickr 200 but 5.4s, which exceeds the proxy's own 4s
44
+ * fetch timeout · placehold.co 200 in 0.58s. Hence placehold.co.
45
+ *
46
+ * It renders the item's own NAME on the tile, which is a real gain over random
47
+ * photography for a demo catalogue: every card now says what it is, and nobody
48
+ * can mistake seeded data for real inventory.
49
+ */
50
+ /** The one place a seed image host is named. Changing it is a one-line edit. */
51
+ const SEED_PHOTO_HOST = "https://placehold.co";
52
+ /**
53
+ * Muted backgrounds, all dark enough for the same light foreground.
54
+ *
55
+ * Raw hex rather than theme tokens because these are baked into a URL that a
56
+ * third party renders — no stylesheet and no CSS custom property is in scope.
57
+ */
58
+ const SEED_PHOTO_COLOURS = ["1e293b", "334155", "3f3f46", "44403c", "312e81", "164e63"];
59
+ const SEED_PHOTO_FG = "f8fafc";
60
+ /** Deterministic 32-bit hash. Same seed, same colour, every reseed. */
61
+ function seedHash(input) {
62
+ let h = 2166136261;
63
+ for (let i = 0; i < input.length; i += 1) {
64
+ h ^= input.charCodeAt(i);
65
+ h = Math.imul(h, 16777619);
66
+ }
67
+ return h >>> 0;
68
+ }
69
+ /**
70
+ * Turn a seed key into something a person would read.
71
+ *
72
+ * `art-image-dranzer-phoenix-2-20260817` → `Dranzer Phoenix`
73
+ *
74
+ * Strips the trailing `-YYYYMMDD` stamp, a trailing image index, and the
75
+ * leading `<kind>-image-` prefix that every seed key carries — all of which are
76
+ * bookkeeping, not the name of the thing.
77
+ */
78
+ export function seedPhotoLabel(seed) {
79
+ const cleaned = seed
80
+ .replace(/-\d{8}$/, "")
81
+ .replace(/-\d+$/, "")
82
+ .replace(/^(?:product|art|sticker|stickers|auction|preorder|prizedraw|classified|digitalcode|live|blog|event|store|user|category|brand|bundle|group|review|ad|carousel|slide)-(?:image|cover|banner|logo|avatar|photo)-/, "")
83
+ .replace(/^(?:image|cover|banner|logo|avatar|photo)-/, "")
84
+ .replace(/-/g, " ")
85
+ .trim();
86
+ if (!cleaned)
87
+ return "LetItRip";
88
+ return cleaned.replace(/\b[a-z]/g, (c) => c.toUpperCase());
89
+ }
90
+ /**
91
+ * A deterministic, labelled placeholder image for seed data, already wrapped in
92
+ * the media proxy.
93
+ *
94
+ * Deterministic on purpose — Root Cause #25: a seed file is re-executed on every
95
+ * `appkit-seed` invocation, so anything non-stable here would give the same
96
+ * fixture a different URL on every run.
97
+ */
98
+ export function seedPhoto(seed, width, height) {
99
+ const bg = SEED_PHOTO_COLOURS[seedHash(seed) % SEED_PHOTO_COLOURS.length];
100
+ const text = encodeURIComponent(seedPhotoLabel(seed));
101
+ return seedExtMedia(`${SEED_PHOTO_HOST}/${width}x${height}/${bg}/${SEED_PHOTO_FG}/png?text=${text}`);
102
+ }
@@ -14,7 +14,7 @@
14
14
  * @tag sideEffects:none
15
15
  */
16
16
  import { withBlogSearchTxt } from "./_helpers/search-txt-wrappers";
17
- import { seedExtMedia } from "./_helpers/media";
17
+ import { seedPhoto } from "./_helpers/media";
18
18
  const NOW = new Date();
19
19
  const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
20
20
  const _rawblogPostsSeedData = [
@@ -24,8 +24,8 @@ const _rawblogPostsSeedData = [
24
24
  title: "How to Spot a Genuine Takara-Tomy Beyblade (vs. Counterfeit Clones)",
25
25
  slug: "spot-genuine-takara-tomy-beyblade",
26
26
  excerpt: "Learn the telltale signs that distinguish a genuine Takara-Tomy Beyblade from a cheap clone — from the laser-etched logo to the weight of the metal driver.",
27
- content: `<h2>The Laser-Etched Logo</h2><p>Genuine Takara-Tomy Beyblades have a crisp, laser-etched "TAKARA TOMY" logo on the underside of the layer or base. Clones use a shallow, blurry stamp that smudges under a fingernail scratch test.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-genuine-beyblade-logo-1-20260101/1000/600")}" alt="Close-up of the genuine Takara-Tomy laser-etched logo" /><h2>Weight and Balance</h2><p>A genuine metal-fusion driver has a consistent, dense weight — clones often use lighter zinc alloy that feels noticeably hollow. Spin the beyblade on a flat glass surface: a genuine driver spins true with almost no wobble.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-genuine-beyblade-weight-2-20260101/1000/600")}" alt="Weighing a genuine metal-fusion driver on a digital scale" /><h2>Sticker Print Quality</h2><p>Compare the face-bolt sticker under bright light. Genuine stickers have sharp gradient printing and a slightly raised clear-coat finish. Clone stickers are flat-printed and peel at the edges within weeks.</p>`,
28
- coverImage: { type: "image", url: seedExtMedia("https://picsum.photos/seed/blog-cover-genuine-beyblade-20260101/1200/630"), alt: "Genuine Takara-Tomy Beyblade close-up" },
27
+ content: `<h2>The Laser-Etched Logo</h2><p>Genuine Takara-Tomy Beyblades have a crisp, laser-etched "TAKARA TOMY" logo on the underside of the layer or base. Clones use a shallow, blurry stamp that smudges under a fingernail scratch test.</p><img src="${seedPhoto("blog-content-image-genuine-beyblade-logo-1-20260101", 1000, 600)}" alt="Close-up of the genuine Takara-Tomy laser-etched logo" /><h2>Weight and Balance</h2><p>A genuine metal-fusion driver has a consistent, dense weight — clones often use lighter zinc alloy that feels noticeably hollow. Spin the beyblade on a flat glass surface: a genuine driver spins true with almost no wobble.</p><img src="${seedPhoto("blog-content-image-genuine-beyblade-weight-2-20260101", 1000, 600)}" alt="Weighing a genuine metal-fusion driver on a digital scale" /><h2>Sticker Print Quality</h2><p>Compare the face-bolt sticker under bright light. Genuine stickers have sharp gradient printing and a slightly raised clear-coat finish. Clone stickers are flat-printed and peel at the edges within weeks.</p>`,
28
+ coverImage: { type: "image", url: seedPhoto("blog-cover-genuine-beyblade-20260101", 1200, 630), alt: "Genuine Takara-Tomy Beyblade close-up" },
29
29
  category: "guides",
30
30
  tags: ["authentication", "takara-tomy", "beyblade", "counterfeit"],
31
31
  isFeatured: true,
@@ -33,7 +33,7 @@ const _rawblogPostsSeedData = [
33
33
  publishedAt: daysAgo(30),
34
34
  authorId: "user-admin-letitrip",
35
35
  authorName: "Mock User 1",
36
- authorAvatar: seedExtMedia("https://picsum.photos/seed/user-avatar-letitrip-admin-20260101/400/400"),
36
+ authorAvatar: seedPhoto("user-avatar-letitrip-admin-20260101", 400, 400),
37
37
  readTimeMinutes: 5,
38
38
  views: 4200,
39
39
  metaTitle: "How to Spot a Genuine Takara-Tomy Beyblade | LetItRip",
@@ -46,8 +46,8 @@ const _rawblogPostsSeedData = [
46
46
  title: "Sealed vs. Opened — How Condition Affects Beyblade Resale Value",
47
47
  slug: "sealed-vs-opened-beyblade-value",
48
48
  excerpt: "A head-to-head look at what actually moves the needle on Beyblade resale price — sealed box premiums, tip wear, and launcher grip condition.",
49
- content: `<h2>Sealed Box — The Investment Grade</h2><p>A factory-sealed box with an unbroken shrink-wrap seal commands the highest resale premiums, especially for discontinued original-series and Metal Fight releases. Collectors treat sealed stock the way trading-card collectors treat unopened booster boxes.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-sealed-beyblade-box-1-20260101/1000/600")}" alt="Factory-sealed Beyblade box with intact shrink wrap" /><h2>Tip Wear — The Sub-Grade That Matters</h2><p>For opened, battled beyblades, tip wear is the single biggest value factor. A sharp, unrounded tip with visible factory grooves grades far higher than a rounded, "sanded" tip from heavy stadium use.</p><h2>Launcher Grip Condition</h2><p>Ripcord launchers with clean, unfrayed grip tape and a tight ratchet mechanism add real value to a bundle. A stripped ratchet or fraying cord is an instant discount flag.</p><h2>Our Recommendation</h2><p>For investment: chase sealed, unopened stock. For a battle-ready collection: prioritize tip sharpness and launcher condition over cosmetic sticker wear.</p>`,
50
- coverImage: { type: "image", url: seedExtMedia("https://picsum.photos/seed/blog-cover-sealed-vs-opened-beyblade-20260101/1200/630"), alt: "Sealed and opened Beyblade boxes side by side" },
49
+ content: `<h2>Sealed Box — The Investment Grade</h2><p>A factory-sealed box with an unbroken shrink-wrap seal commands the highest resale premiums, especially for discontinued original-series and Metal Fight releases. Collectors treat sealed stock the way trading-card collectors treat unopened booster boxes.</p><img src="${seedPhoto("blog-content-image-sealed-beyblade-box-1-20260101", 1000, 600)}" alt="Factory-sealed Beyblade box with intact shrink wrap" /><h2>Tip Wear — The Sub-Grade That Matters</h2><p>For opened, battled beyblades, tip wear is the single biggest value factor. A sharp, unrounded tip with visible factory grooves grades far higher than a rounded, "sanded" tip from heavy stadium use.</p><h2>Launcher Grip Condition</h2><p>Ripcord launchers with clean, unfrayed grip tape and a tight ratchet mechanism add real value to a bundle. A stripped ratchet or fraying cord is an instant discount flag.</p><h2>Our Recommendation</h2><p>For investment: chase sealed, unopened stock. For a battle-ready collection: prioritize tip sharpness and launcher condition over cosmetic sticker wear.</p>`,
50
+ coverImage: { type: "image", url: seedPhoto("blog-cover-sealed-vs-opened-beyblade-20260101", 1200, 630), alt: "Sealed and opened Beyblade boxes side by side" },
51
51
  category: "guides",
52
52
  tags: ["condition", "sealed", "tip-wear", "beyblade", "investment"],
53
53
  isFeatured: true,
@@ -55,7 +55,7 @@ const _rawblogPostsSeedData = [
55
55
  publishedAt: daysAgo(25),
56
56
  authorId: "user-admin-letitrip",
57
57
  authorName: "Mock User 1",
58
- authorAvatar: seedExtMedia("https://picsum.photos/seed/user-avatar-letitrip-admin-20260101/400/400"),
58
+ authorAvatar: seedPhoto("user-avatar-letitrip-admin-20260101", 400, 400),
59
59
  readTimeMinutes: 7,
60
60
  views: 3800,
61
61
  metaTitle: "Sealed vs. Opened — Beyblade Condition & Resale Value | LetItRip",
@@ -68,15 +68,15 @@ const _rawblogPostsSeedData = [
68
68
  title: "Complete Beyblade X Series Guide — Every Wave and Value",
69
69
  slug: "complete-beyblade-x-series-guide",
70
70
  excerpt: "Every Beyblade X wave released so far — from the BX-01 starter line to the latest BX-08 wave — with current market values.",
71
- content: `<h2>The Starter Waves</h2><p>BX-01 through BX-04 introduced the core X-series parts — Blade, Ratchet, and Bit — replacing the old layer/disk/driver system entirely. Sealed starters from these early waves are already appreciating.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-beyblade-x-waves-1-20260101/1000/600")}" alt="Beyblade X starter wave lineup" /><h2>BX-05 through BX-08 (2025-2026)</h2><p>The current generation. Each piece in sealed condition ranges from ₹500-₹1,200. A complete wave set? Over ₹8,000.</p><h2>Later Waves</h2><p>Limited convention exclusives, gold-finish promos, and various starter-set reprints. Values range from ₹300 for common reprints to ₹5,000+ for rare promotional versions.</p>`,
72
- coverImage: { type: "image", url: seedExtMedia("https://picsum.photos/seed/blog-cover-beyblade-x-guide-20260101/1200/630"), alt: "Beyblade X series wave lineup" },
71
+ content: `<h2>The Starter Waves</h2><p>BX-01 through BX-04 introduced the core X-series parts — Blade, Ratchet, and Bit — replacing the old layer/disk/driver system entirely. Sealed starters from these early waves are already appreciating.</p><img src="${seedPhoto("blog-content-image-beyblade-x-waves-1-20260101", 1000, 600)}" alt="Beyblade X starter wave lineup" /><h2>BX-05 through BX-08 (2025-2026)</h2><p>The current generation. Each piece in sealed condition ranges from ₹500-₹1,200. A complete wave set? Over ₹8,000.</p><h2>Later Waves</h2><p>Limited convention exclusives, gold-finish promos, and various starter-set reprints. Values range from ₹300 for common reprints to ₹5,000+ for rare promotional versions.</p>`,
72
+ coverImage: { type: "image", url: seedPhoto("blog-cover-beyblade-x-guide-20260101", 1200, 630), alt: "Beyblade X series wave lineup" },
73
73
  category: "guides",
74
74
  tags: ["beyblade-x", "guide", "waves", "values"],
75
75
  isFeatured: false,
76
76
  status: "draft",
77
77
  authorId: "user-admin-letitrip",
78
78
  authorName: "Mock User 1",
79
- authorAvatar: seedExtMedia("https://picsum.photos/seed/user-avatar-letitrip-admin-20260101/400/400"),
79
+ authorAvatar: seedPhoto("user-avatar-letitrip-admin-20260101", 400, 400),
80
80
  readTimeMinutes: 10,
81
81
  views: 0,
82
82
  metaTitle: "Complete Beyblade X Series Guide — Every Wave & Value | LetItRip",
@@ -151,8 +151,8 @@ const _rawblogPostsSeedData = [
151
151
  title: "Top 10 Most Valuable Vintage Beyblades",
152
152
  slug: "top-10-most-valuable-vintage-beyblades",
153
153
  excerpt: "From the legendary original-series Dragoon Storm to the elusive tournament-only L-Drago variants — these are the beyblades every collector dreams of.",
154
- content: `<h2>1. Tournament-Exclusive Gold Dragoon (2000)</h2><p>The rarest original-series beyblade ever distributed. Only handed out at the Japan national finals. Estimated value: over ₹4 lakh.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-top10-tournament-gold-1-20260101/1000/600")}" alt="Tournament-exclusive gold Dragoon beyblade" /><h2>2. Beyblade Original Dragoon Storm, Sealed</h2><p>Tyson's signature spinner in sealed, factory condition. Sealed copies sell for ₹1.5-2.5 lakh.</p><h2>3. Beyblade Metal Lightning L-Drago, Sealed</h2><p>Kai's iconic rival spinner. A sealed Metal Lightning L-Drago commands ₹80,000-1.2 lakh.</p><h2>4. Complete Original-Series Starter Set (all 5 pieces, sealed)</h2><p>All five original-series starter beyblades still sealed — virtually impossible to assemble. Set value: over ₹6 lakh.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-top10-starter-set-2-20260101/1000/600")}" alt="Complete original-series Beyblade starter set" /><h2>5-10</h2><p>Metal Fusion Storm Pegasus (sealed), Metal Fight Flame Sagittario (sealed), Burst Valkyrie tournament promo, Burst Regalia Genesis limited run, original-series Dranzer S first-print, and Driger V arena-exclusive gold edition.</p>`,
155
- coverImage: { type: "image", url: seedExtMedia("https://picsum.photos/seed/blog-cover-top10-vintage-beyblade-20260101/1200/630"), alt: "Vintage Beyblade collection" },
154
+ content: `<h2>1. Tournament-Exclusive Gold Dragoon (2000)</h2><p>The rarest original-series beyblade ever distributed. Only handed out at the Japan national finals. Estimated value: over ₹4 lakh.</p><img src="${seedPhoto("blog-content-image-top10-tournament-gold-1-20260101", 1000, 600)}" alt="Tournament-exclusive gold Dragoon beyblade" /><h2>2. Beyblade Original Dragoon Storm, Sealed</h2><p>Tyson's signature spinner in sealed, factory condition. Sealed copies sell for ₹1.5-2.5 lakh.</p><h2>3. Beyblade Metal Lightning L-Drago, Sealed</h2><p>Kai's iconic rival spinner. A sealed Metal Lightning L-Drago commands ₹80,000-1.2 lakh.</p><h2>4. Complete Original-Series Starter Set (all 5 pieces, sealed)</h2><p>All five original-series starter beyblades still sealed — virtually impossible to assemble. Set value: over ₹6 lakh.</p><img src="${seedPhoto("blog-content-image-top10-starter-set-2-20260101", 1000, 600)}" alt="Complete original-series Beyblade starter set" /><h2>5-10</h2><p>Metal Fusion Storm Pegasus (sealed), Metal Fight Flame Sagittario (sealed), Burst Valkyrie tournament promo, Burst Regalia Genesis limited run, original-series Dranzer S first-print, and Driger V arena-exclusive gold edition.</p>`,
155
+ coverImage: { type: "image", url: seedPhoto("blog-cover-top10-vintage-beyblade-20260101", 1200, 630), alt: "Vintage Beyblade collection" },
156
156
  category: "news",
157
157
  tags: ["top-10", "vintage", "investment", "rare-beyblades"],
158
158
  isFeatured: false,
@@ -160,7 +160,7 @@ const _rawblogPostsSeedData = [
160
160
  publishedAt: daysAgo(20),
161
161
  authorId: "user-admin-letitrip",
162
162
  authorName: "Mock User 1",
163
- authorAvatar: seedExtMedia("https://picsum.photos/seed/user-avatar-letitrip-admin-20260101/400/400"),
163
+ authorAvatar: seedPhoto("user-avatar-letitrip-admin-20260101", 400, 400),
164
164
  readTimeMinutes: 8,
165
165
  views: 6100,
166
166
  metaTitle: "Top 10 Most Valuable Vintage Beyblades | LetItRip",
@@ -234,8 +234,8 @@ const _rawblogPostsSeedData = [
234
234
  title: "Metal Fight Hidden Gems: Undervalued Beyblades from the 2009-2012 Era",
235
235
  slug: "metal-fight-hidden-gems",
236
236
  excerpt: "The Metal Fight era produced some of the most undervalued beyblades in collecting history. Here are the picks smart collectors are buying now.",
237
- content: `<h2>Why Metal Fight Beyblades Are Undervalued</h2><p>The Metal Fight era (2009-2012) coincided with peak Beyblade anime popularity but many collectors focus exclusively on the original 1999-2003 series. This creates a pricing gap.</p><img src="${seedExtMedia("https://picsum.photos/seed/blog-content-image-metal-fight-gems-1-20260101/1000/600")}" alt="Metal Fight era Beyblade lineup" /><h2>Metal Fight Picks</h2><p><strong>Metal Storm Pegasus</strong> — the fan-favorite protagonist spinner. Sealed copies are still affordable at ₹1,500-₹2,500.</p><p><strong>Metal Flame Sagittario</strong> — one of the most competitively impactful attack types ever. Sealed condition commands ₹3,500-₹5,000.</p><p><strong>Rare 4D System variants</strong> — low print run makes these a sleeper investment.</p>`,
238
- coverImage: { type: "image", url: seedExtMedia("https://picsum.photos/seed/blog-cover-metal-fight-gems-20260101/1200/630"), alt: "Metal Fight Beyblade collection" },
237
+ content: `<h2>Why Metal Fight Beyblades Are Undervalued</h2><p>The Metal Fight era (2009-2012) coincided with peak Beyblade anime popularity but many collectors focus exclusively on the original 1999-2003 series. This creates a pricing gap.</p><img src="${seedPhoto("blog-content-image-metal-fight-gems-1-20260101", 1000, 600)}" alt="Metal Fight era Beyblade lineup" /><h2>Metal Fight Picks</h2><p><strong>Metal Storm Pegasus</strong> — the fan-favorite protagonist spinner. Sealed copies are still affordable at ₹1,500-₹2,500.</p><p><strong>Metal Flame Sagittario</strong> — one of the most competitively impactful attack types ever. Sealed condition commands ₹3,500-₹5,000.</p><p><strong>Rare 4D System variants</strong> — low print run makes these a sleeper investment.</p>`,
238
+ coverImage: { type: "image", url: seedPhoto("blog-cover-metal-fight-gems-20260101", 1200, 630), alt: "Metal Fight Beyblade collection" },
239
239
  category: "tips",
240
240
  tags: ["metal-fight", "hidden-gems", "investment", "beyblade"],
241
241
  isFeatured: false,
@@ -243,7 +243,7 @@ const _rawblogPostsSeedData = [
243
243
  publishedAt: daysAgo(15),
244
244
  authorId: "user-admin-letitrip",
245
245
  authorName: "Mock User 1",
246
- authorAvatar: seedExtMedia("https://picsum.photos/seed/user-avatar-letitrip-admin-20260101/400/400"),
246
+ authorAvatar: seedPhoto("user-avatar-letitrip-admin-20260101", 400, 400),
247
247
  readTimeMinutes: 6,
248
248
  views: 2900,
249
249
  metaTitle: "Metal Fight Hidden Gems — Undervalued Beyblades | LetItRip",
@@ -318,7 +318,7 @@ const _rawblogPostsSeedData = [
318
318
  slug: "authenticating-beyblades",
319
319
  excerpt: "A behind-the-scenes look at how verified sellers on LetItRip check every beyblade before listing — logo tests, weight checks, and spin-balance inspection.",
320
320
  content: `<h2>The 5-Point Authentication Process</h2><p>Every beyblade listed by top verified stores goes through a rigorous 5-point check:</p><ol><li><strong>Logo Test</strong> — Genuine Takara-Tomy pieces have a specific laser-etched depth and clarity when checked under raking light.</li><li><strong>Weight Check</strong> — A genuine metal-fusion driver weighs within a tight factory tolerance verified on a digital scale.</li><li><strong>Spin-Balance Test</strong> — Each layer/driver combo is spin-tested on a flat glass surface; genuine parts spin true with minimal wobble.</li><li><strong>Sticker Print Inspection</strong> — Under 10x magnification, genuine face-bolt stickers show sharp gradient printing and a raised clear-coat.</li><li><strong>Ratchet & Grip Inspection</strong> — Check the launcher for a tight ratchet mechanism and unfrayed grip tape.</li></ol>`,
321
- coverImage: { type: "image", url: seedExtMedia("https://picsum.photos/seed/blog-cover-authenticating-beyblades-20260101/1200/630"), alt: "Beyblade authentication process" },
321
+ coverImage: { type: "image", url: seedPhoto("blog-cover-authenticating-beyblades-20260101", 1200, 630), alt: "Beyblade authentication process" },
322
322
  category: "community",
323
323
  tags: ["authentication", "verification", "counterfeit", "beyblade"],
324
324
  isFeatured: false,
@@ -17,7 +17,7 @@
17
17
  * @tag consumers:seed/index.ts,seed/runner.ts
18
18
  * @tag sideEffects:none
19
19
  */
20
- import { seedExtMedia } from "./_helpers/media";
20
+ import { seedPhoto } from "./_helpers/media";
21
21
  import { ROUTES } from "../next/routing/route-map";
22
22
  const NOW = new Date();
23
23
  const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
@@ -29,7 +29,7 @@ const _rawSlides = [
29
29
  active: true,
30
30
  background: {
31
31
  type: "image",
32
- url: seedExtMedia("https://picsum.photos/seed/carousel-hero-homepage-20260101/1600/700"),
32
+ url: seedPhoto("carousel-hero-homepage-20260101", 1600, 700),
33
33
  dimOverlay: { enabled: true, opacity: 0.5 },
34
34
  },
35
35
  settings: { autoplayDelayMs: 5000, height: "tall" },
@@ -49,7 +49,7 @@ const _rawSlides = [
49
49
  // (rejects non-image content-type with 400). MediaVideo watermarks
50
50
  // video client-side instead, so the raw external URL is correct here.
51
51
  url: "https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", // audit-seed-external-url-ok: raw <video> src, /api/media/ext is image-only (Root Cause #27)
52
- thumbnail: seedExtMedia("https://picsum.photos/seed/carousel-auctions-live-20260101/1600/700"),
52
+ thumbnail: seedPhoto("carousel-auctions-live-20260101", 1600, 700),
53
53
  dimOverlay: { enabled: true, opacity: 0.4 },
54
54
  },
55
55
  link: { url: String(ROUTES.PUBLIC.AUCTIONS), openInNewTab: false },
@@ -65,7 +65,7 @@ const _rawSlides = [
65
65
  active: true,
66
66
  background: {
67
67
  type: "image",
68
- url: seedExtMedia("https://picsum.photos/seed/carousel-complete-collection-20260101/1600/700"),
68
+ url: seedPhoto("carousel-complete-collection-20260101", 1600, 700),
69
69
  dimOverlay: { enabled: true, opacity: 0.45 },
70
70
  },
71
71
  link: { url: String(ROUTES.PUBLIC.PRODUCTS), openInNewTab: false },
@@ -81,7 +81,7 @@ const _rawSlides = [
81
81
  active: true,
82
82
  background: {
83
83
  type: "image",
84
- url: seedExtMedia("https://picsum.photos/seed/carousel-preorders-open-20260101/1600/700"),
84
+ url: seedPhoto("carousel-preorders-open-20260101", 1600, 700),
85
85
  dimOverlay: { enabled: true, opacity: 0.4 },
86
86
  },
87
87
  link: { url: String(ROUTES.PUBLIC.PRE_ORDERS), openInNewTab: false },
@@ -97,7 +97,7 @@ const _rawSlides = [
97
97
  active: true,
98
98
  background: {
99
99
  type: "image",
100
- url: seedExtMedia("https://picsum.photos/seed/carousel-authenticated-originals-20260101/1600/700"),
100
+ url: seedPhoto("carousel-authenticated-originals-20260101", 1600, 700),
101
101
  dimOverlay: { enabled: true, opacity: 0.5 },
102
102
  },
103
103
  link: { url: String(ROUTES.PUBLIC.CATEGORY_DETAIL("category-spinning-tops")), openInNewTab: false },
@@ -113,7 +113,7 @@ const _rawSlides = [
113
113
  active: false,
114
114
  background: {
115
115
  type: "image",
116
- url: seedExtMedia("https://picsum.photos/seed/carousel-tournament-promo-20260101/1600/700"),
116
+ url: seedPhoto("carousel-tournament-promo-20260101", 1600, 700),
117
117
  dimOverlay: { enabled: true, opacity: 0.5 },
118
118
  },
119
119
  settings: { autoplayDelayMs: 5000, height: "tall" },
@@ -13,7 +13,7 @@
13
13
  * @tag sideEffects:none
14
14
  */
15
15
  import { getDefaultCurrency } from "./seed-market-config";
16
- import { seedExtMedia } from "./_helpers/media";
16
+ import { seedPhoto } from "./_helpers/media";
17
17
  const _CURRENCY = getDefaultCurrency();
18
18
  const NOW = new Date();
19
19
  const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
@@ -27,7 +27,7 @@ export const cartsSeedData = [
27
27
  itemId: "cartitem-yugi-original-dranzer-s",
28
28
  productId: "product-beyblade-original-dranzer-s",
29
29
  productTitle: "Beyblade Original Dranzer S",
30
- productImage: seedExtMedia("https://picsum.photos/seed/product-image-original-dranzer-s-1-20260101/600/600"),
30
+ productImage: seedPhoto("product-image-original-dranzer-s-1-20260101", 600, 600),
31
31
  price: 1799,
32
32
  currency: _CURRENCY,
33
33
  quantity: 1,
@@ -41,7 +41,7 @@ export const cartsSeedData = [
41
41
  itemId: "cartitem-yugi-x-bx08-preorder-deposit",
42
42
  productId: "preorder-beyblade-x-bx-08-wave",
43
43
  productTitle: "Beyblade X BX-08 Wave — Pre-Order",
44
- productImage: seedExtMedia("https://picsum.photos/seed/preorder-image-x-bx08-wave-1-20260101/600/600"),
44
+ productImage: seedPhoto("preorder-image-x-bx08-wave-1-20260101", 600, 600),
45
45
  price: 899,
46
46
  currency: _CURRENCY,
47
47
  quantity: 2,
@@ -55,7 +55,7 @@ export const cartsSeedData = [
55
55
  itemId: "cartitem-yugi-stadium-set",
56
56
  productId: "classified-beyblade-stadium-set",
57
57
  productTitle: "Beyblade Stadium Set",
58
- productImage: seedExtMedia("https://picsum.photos/seed/classified-image-stadium-set-1-20260101/600/600"),
58
+ productImage: seedPhoto("classified-image-stadium-set-1-20260101", 600, 600),
59
59
  price: 1299,
60
60
  currency: _CURRENCY,
61
61
  quantity: 1,
@@ -69,7 +69,7 @@ export const cartsSeedData = [
69
69
  itemId: "cartitem-yugi-dragoon-storm-auction-won",
70
70
  productId: "auction-beyblade-original-dragoon-storm",
71
71
  productTitle: "Beyblade Original Dragoon Storm — Won Auction",
72
- productImage: seedExtMedia("https://picsum.photos/seed/auction-image-dragoon-storm-1-20260101/600/600"),
72
+ productImage: seedPhoto("auction-image-dragoon-storm-1-20260101", 600, 600),
73
73
  price: 3499,
74
74
  currency: _CURRENCY,
75
75
  quantity: 1,
@@ -84,7 +84,7 @@ export const cartsSeedData = [
84
84
  itemId: "cartitem-yugi-dranzer-offer-accepted",
85
85
  productId: "product-beyblade-original-dranzer-s",
86
86
  productTitle: "Beyblade Original Dranzer S — Offer Accepted",
87
- productImage: seedExtMedia("https://picsum.photos/seed/product-image-original-dranzer-s-2-20260101/600/600"),
87
+ productImage: seedPhoto("product-image-original-dranzer-s-2-20260101", 600, 600),
88
88
  price: 1799,
89
89
  currency: _CURRENCY,
90
90
  quantity: 1,
@@ -111,7 +111,7 @@ export const cartsSeedData = [
111
111
  itemId: "cartitem-kaiba-metal-flame-sagittario",
112
112
  productId: "product-beyblade-metal-flame-sagittario",
113
113
  productTitle: "Beyblade Metal Flame Sagittario",
114
- productImage: seedExtMedia("https://picsum.photos/seed/product-image-metal-flame-sagittario-1-20260101/600/600"),
114
+ productImage: seedPhoto("product-image-metal-flame-sagittario-1-20260101", 600, 600),
115
115
  price: 2299,
116
116
  currency: _CURRENCY,
117
117
  quantity: 1,
@@ -125,7 +125,7 @@ export const cartsSeedData = [
125
125
  itemId: "cartitem-kaiba-x-app-unlock",
126
126
  productId: "digitalcode-beyblade-x-app-unlock",
127
127
  productTitle: "Beyblade X App Unlock Code",
128
- productImage: seedExtMedia("https://picsum.photos/seed/digitalcode-image-x-app-unlock-1-20260101/600/600"),
128
+ productImage: seedPhoto("digitalcode-image-x-app-unlock-1-20260101", 600, 600),
129
129
  price: 199,
130
130
  currency: _CURRENCY,
131
131
  quantity: 1,
@@ -139,7 +139,7 @@ export const cartsSeedData = [
139
139
  itemId: "cartitem-kaiba-x-bx08-preorder",
140
140
  productId: "preorder-beyblade-x-bx-08-wave",
141
141
  productTitle: "Beyblade X BX-08 Wave — Pre-Order",
142
- productImage: seedExtMedia("https://picsum.photos/seed/preorder-image-x-bx08-wave-2-20260101/600/600"),
142
+ productImage: seedPhoto("preorder-image-x-bx08-wave-2-20260101", 600, 600),
143
143
  price: 899,
144
144
  currency: _CURRENCY,
145
145
  quantity: 1,
@@ -162,7 +162,7 @@ export const cartsSeedData = [
162
162
  itemId: "cartitem-admin-x-knife-shinobi",
163
163
  productId: "product-beyblade-x-knife-shinobi",
164
164
  productTitle: "Beyblade X Knife Shinobi",
165
- productImage: seedExtMedia("https://picsum.photos/seed/product-image-x-knife-shinobi-1-20260101/600/600"),
165
+ productImage: seedPhoto("product-image-x-knife-shinobi-1-20260101", 600, 600),
166
166
  price: 2999,
167
167
  currency: _CURRENCY,
168
168
  quantity: 1,
@@ -176,7 +176,7 @@ export const cartsSeedData = [
176
176
  itemId: "cartitem-admin-mystery-box",
177
177
  productId: "prizedraw-beyblade-mystery-box",
178
178
  productTitle: "Beyblade Mystery Box",
179
- productImage: seedExtMedia("https://picsum.photos/seed/prizedraw-image-mystery-box-1-20260101/600/600"),
179
+ productImage: seedPhoto("prizedraw-image-mystery-box-1-20260101", 600, 600),
180
180
  price: 499,
181
181
  currency: _CURRENCY,
182
182
  quantity: 1,
@@ -199,7 +199,7 @@ export const cartsSeedData = [
199
199
  itemId: "cartitem-guest-metal-storm-pegasus",
200
200
  productId: "product-beyblade-metal-storm-pegasus",
201
201
  productTitle: "Beyblade Metal Storm Pegasus",
202
- productImage: seedExtMedia("https://picsum.photos/seed/product-image-metal-storm-pegasus-1-20260101/600/600"),
202
+ productImage: seedPhoto("product-image-metal-storm-pegasus-1-20260101", 600, 600),
203
203
  price: 2499,
204
204
  currency: _CURRENCY,
205
205
  quantity: 1,
@@ -213,7 +213,7 @@ export const cartsSeedData = [
213
213
  itemId: "cartitem-guest-burst-valkyrie",
214
214
  productId: "product-beyblade-burst-valkyrie",
215
215
  productTitle: "Beyblade Burst Valkyrie",
216
- productImage: seedExtMedia("https://picsum.photos/seed/product-image-burst-valkyrie-1-20260101/600/600"),
216
+ productImage: seedPhoto("product-image-burst-valkyrie-1-20260101", 600, 600),
217
217
  price: 1899,
218
218
  currency: _CURRENCY,
219
219
  quantity: 1,