@mohasinac/appkit 3.6.5 → 3.8.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 (298) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +17 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +7 -2
  3. package/dist/_internal/client/features/tour/TourProvider.d.ts +5 -2
  4. package/dist/_internal/client/features/tour/TourProvider.js +125 -12
  5. package/dist/_internal/client/theme/ThemeProvider.js +1 -1
  6. package/dist/_internal/server/features/categories/data.js +2 -2
  7. package/dist/_internal/server/features/faqs/og.js +5 -1
  8. package/dist/_internal/server/features/reviews/og.js +5 -1
  9. package/dist/_internal/server/features/seo/manifest.js +2 -1
  10. package/dist/_internal/server/features/seo/og.js +7 -1
  11. package/dist/_internal/server/features/tester/visibility.d.ts +13 -0
  12. package/dist/_internal/server/features/tester/visibility.js +28 -0
  13. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  14. package/dist/_internal/server/functions/scheduled.js +9 -1
  15. package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
  16. package/dist/_internal/server/jobs/core/resetOtpVerification.d.ts +17 -0
  17. package/dist/_internal/server/jobs/core/resetOtpVerification.js +88 -0
  18. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +6 -0
  19. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +35 -0
  20. package/dist/_internal/server/jobs/handlers/_helpers.d.ts +6 -0
  21. package/dist/_internal/server/jobs/handlers/_helpers.js +12 -0
  22. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  23. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  24. package/dist/_internal/server/jobs/handlers/messages.d.ts +2 -0
  25. package/dist/_internal/server/jobs/handlers/messages.js +2 -0
  26. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +2 -0
  27. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +2 -0
  28. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  29. package/dist/_internal/shared/actions/action-registry.js +56 -0
  30. package/dist/_internal/shared/tokens/index.d.ts +12 -12
  31. package/dist/client.d.ts +15 -3
  32. package/dist/client.js +10 -2
  33. package/dist/constants/api-endpoints.d.ts +39 -12
  34. package/dist/constants/api-endpoints.js +17 -8
  35. package/dist/constants/field-names.d.ts +9 -0
  36. package/dist/constants/field-names.js +12 -0
  37. package/dist/constants/index.d.ts +1 -1
  38. package/dist/constants/index.js +1 -1
  39. package/dist/features/account/components/LinkedAccountsSection.d.ts +11 -0
  40. package/dist/features/account/components/LinkedAccountsSection.js +11 -0
  41. package/dist/features/account/components/UserSidebar.d.ts +3 -1
  42. package/dist/features/account/components/UserSidebar.js +9 -13
  43. package/dist/features/account/hooks/useCollapsedSections.d.ts +20 -0
  44. package/dist/features/account/hooks/useCollapsedSections.js +63 -0
  45. package/dist/features/account/hooks/useProfile.d.ts +8 -0
  46. package/dist/features/account/index.d.ts +1 -0
  47. package/dist/features/account/index.js +1 -0
  48. package/dist/features/admin/components/AdminAddressEditorView.js +2 -2
  49. package/dist/features/admin/components/AdminBidsView.js +6 -6
  50. package/dist/features/admin/components/AdminBrandsView.js +2 -2
  51. package/dist/features/admin/components/AdminCarouselView.js +2 -2
  52. package/dist/features/admin/components/AdminCategoryEditorView.js +2 -2
  53. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  54. package/dist/features/admin/components/AdminProductEditorView.js +5 -2
  55. package/dist/features/admin/components/AdminReviewsView.js +5 -5
  56. package/dist/features/admin/components/AdminSectionsView.js +3 -18
  57. package/dist/features/admin/components/AdminSidebar.js +8 -6
  58. package/dist/features/admin/components/AdminSiteSettingsView.js +133 -127
  59. package/dist/features/admin/components/AdminSublistingCategoriesView.js +1 -1
  60. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.d.ts +9 -0
  61. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +129 -0
  62. package/dist/features/admin/components/AdminTesterChecklistView.d.ts +6 -0
  63. package/dist/features/admin/components/AdminTesterChecklistView.js +93 -0
  64. package/dist/features/admin/components/AdminUserEditorView.d.ts +3 -1
  65. package/dist/features/admin/components/AdminUserEditorView.js +7 -1
  66. package/dist/features/admin/components/AdminUsersView.js +1 -1
  67. package/dist/features/admin/components/DataListingView.js +2 -2
  68. package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +8 -0
  69. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +41 -0
  70. package/dist/features/admin/components/index.d.ts +6 -0
  71. package/dist/features/admin/components/index.js +3 -0
  72. package/dist/features/admin/schemas/firestore.d.ts +0 -7
  73. package/dist/features/admin/schemas/firestore.js +6 -6
  74. package/dist/features/analytics/components/PageViewTracker.d.ts +12 -0
  75. package/dist/features/analytics/components/PageViewTracker.js +22 -0
  76. package/dist/features/analytics/repository/page-views.repository.d.ts +32 -0
  77. package/dist/features/analytics/repository/page-views.repository.js +43 -0
  78. package/dist/features/analytics/types.d.ts +6 -0
  79. package/dist/features/analytics/types.js +13 -0
  80. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  81. package/dist/features/auth/hooks/useAuth.d.ts +12 -0
  82. package/dist/features/auth/hooks/useAuth.js +109 -0
  83. package/dist/features/auth/permissions/constants.d.ts +1 -1
  84. package/dist/features/auth/repository/session.repository.d.ts +2 -0
  85. package/dist/features/auth/repository/session.repository.js +4 -1
  86. package/dist/features/auth/schemas/firestore.d.ts +17 -1
  87. package/dist/features/auth/schemas/firestore.js +2 -0
  88. package/dist/features/blog/components/BlogIndexListing.js +2 -2
  89. package/dist/features/blog/schemas/firestore.d.ts +4 -0
  90. package/dist/features/cart/actions/cart-actions.js +20 -2
  91. package/dist/features/categories/api/route.js +2 -2
  92. package/dist/features/categories/components/CategoriesIndexListing.js +3 -3
  93. package/dist/features/categories/components/CategoryBundlesListing.js +2 -2
  94. package/dist/features/categories/components/CategoryDetailPageView.js +3 -1
  95. package/dist/features/categories/components/CategoryProductsListing.js +2 -2
  96. package/dist/features/categories/schemas/firestore.d.ts +4 -0
  97. package/dist/features/classified/components/ClassifiedIndexListing.js +2 -2
  98. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +2 -2
  99. package/dist/features/email/primitives.d.ts +2 -2
  100. package/dist/features/email/primitives.js +2 -2
  101. package/dist/features/events/components/EventsIndexListing.js +2 -2
  102. package/dist/features/events/repository/event-entry.repository.js +1 -0
  103. package/dist/features/events/schemas/firestore.d.ts +7 -1
  104. package/dist/features/events/schemas/firestore.js +1 -0
  105. package/dist/features/layout/BackToTop.d.ts +6 -1
  106. package/dist/features/layout/BackToTop.js +31 -6
  107. package/dist/features/layout/BottomNavbar.js +1 -1
  108. package/dist/features/layout/NavItem.js +1 -1
  109. package/dist/features/layout/NavbarLayout.js +2 -2
  110. package/dist/features/live/components/LiveItemsIndexListing.js +2 -2
  111. package/dist/features/media/MediaVideo.js +5 -1
  112. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -2
  113. package/dist/features/products/components/AuctionsIndexListing.js +2 -2
  114. package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -2
  115. package/dist/features/products/components/ProductsIndexListing.js +2 -2
  116. package/dist/features/products/constants/action-defs.d.ts +2 -0
  117. package/dist/features/products/constants/action-defs.js +2 -0
  118. package/dist/features/products/constants/sieve.d.ts +0 -6
  119. package/dist/features/products/constants/sieve.js +4 -2
  120. package/dist/features/products/repository/products.repository.d.ts +20 -0
  121. package/dist/features/products/repository/products.repository.js +5 -0
  122. package/dist/features/products/schemas/firestore.d.ts +4 -0
  123. package/dist/features/promotions/components/CouponsIndexListing.js +18 -5
  124. package/dist/features/reviews/components/ReviewsIndexListing.js +2 -2
  125. package/dist/features/search/hooks/useNavSuggestions.js +20 -1
  126. package/dist/features/seller/actions/seller-actions.js +11 -6
  127. package/dist/features/seller/components/SellerAddressesView.js +2 -2
  128. package/dist/features/seller/components/SellerBidsView.js +2 -2
  129. package/dist/features/seller/components/SellerCouponsView.js +1 -1
  130. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  131. package/dist/features/seller/components/SellerProductsView.js +2 -2
  132. package/dist/features/seller/components/SellerSidebar.d.ts +3 -1
  133. package/dist/features/seller/components/SellerSidebar.js +9 -8
  134. package/dist/features/seller/components/SellerStoreCategoriesView.js +2 -2
  135. package/dist/features/seller/components/SellerTemplatesView.js +2 -2
  136. package/dist/features/shell/StepForm.js +4 -4
  137. package/dist/features/stores/actions/store-query-actions.d.ts +5 -4
  138. package/dist/features/stores/actions/store-query-actions.js +11 -8
  139. package/dist/features/stores/components/StoreAuctionsListing.js +2 -2
  140. package/dist/features/stores/components/StoreClassifiedsListing.js +2 -2
  141. package/dist/features/stores/components/StoreDetailLayoutView.js +1 -1
  142. package/dist/features/stores/components/StoreDigitalCodesListing.js +2 -2
  143. package/dist/features/stores/components/StoreLiveItemsListing.js +2 -2
  144. package/dist/features/stores/components/StorePreOrdersListing.js +2 -2
  145. package/dist/features/stores/components/StoreProductsListing.js +2 -2
  146. package/dist/features/stores/components/StoreReviewsListing.js +2 -2
  147. package/dist/features/stores/components/StoresIndexListing.js +2 -2
  148. package/dist/features/stores/repository/store.repository.js +3 -0
  149. package/dist/features/stores/schemas/firestore.d.ts +4 -0
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +69 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +42 -0
  152. package/dist/features/tester/actions/index.d.ts +1 -0
  153. package/dist/features/tester/actions/index.js +1 -0
  154. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.d.ts +2 -0
  155. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +22 -0
  156. package/dist/features/tester/components/AdminTesterFeedbackListView.d.ts +3 -0
  157. package/dist/features/tester/components/AdminTesterFeedbackListView.js +100 -0
  158. package/dist/features/tester/components/AdminTesterFeedbackReportView.d.ts +2 -0
  159. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +30 -0
  160. package/dist/features/tester/components/AdminTesterFeedbackView.d.ts +2 -0
  161. package/dist/features/tester/components/AdminTesterFeedbackView.js +29 -0
  162. package/dist/features/tester/components/TesterChecklistStepRow.d.ts +18 -0
  163. package/dist/features/tester/components/TesterChecklistStepRow.js +29 -0
  164. package/dist/features/tester/components/TesterFeedbackChart.d.ts +14 -0
  165. package/dist/features/tester/components/TesterFeedbackChart.js +18 -0
  166. package/dist/features/tester/components/TesterHubView.d.ts +5 -0
  167. package/dist/features/tester/components/TesterHubView.js +71 -0
  168. package/dist/features/tester/components/index.d.ts +11 -0
  169. package/dist/features/tester/components/index.js +7 -0
  170. package/dist/features/tester/index.d.ts +2 -0
  171. package/dist/features/tester/index.js +2 -0
  172. package/dist/features/tester/repository/index.d.ts +2 -0
  173. package/dist/features/tester/repository/index.js +2 -0
  174. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +13 -0
  175. package/dist/features/tester/repository/tester-checklist-item.repository.js +55 -0
  176. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +50 -0
  177. package/dist/features/tester/repository/tester-checklist-response.repository.js +196 -0
  178. package/dist/features/tester/schemas/firestore.d.ts +85 -0
  179. package/dist/features/tester/schemas/firestore.js +93 -0
  180. package/dist/features/tester/schemas/index.d.ts +1 -0
  181. package/dist/features/tester/schemas/index.js +1 -0
  182. package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +2 -0
  183. package/dist/features/tester/seed-data/blog-tester-seed-data.js +40 -0
  184. package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +2 -0
  185. package/dist/features/tester/seed-data/categories-tester-seed-data.js +177 -0
  186. package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +2 -0
  187. package/dist/features/tester/seed-data/events-tester-seed-data.js +50 -0
  188. package/dist/features/tester/seed-data/index.d.ts +6 -0
  189. package/dist/features/tester/seed-data/index.js +6 -0
  190. package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +2 -0
  191. package/dist/features/tester/seed-data/products-tester-seed-data.js +151 -0
  192. package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +2 -0
  193. package/dist/features/tester/seed-data/stores-tester-seed-data.js +40 -0
  194. package/dist/features/tester/seed-data/tester-checklist-seed-data.d.ts +2 -0
  195. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +248 -0
  196. package/dist/features/tester/seed-data/tester-ttl.d.ts +4 -0
  197. package/dist/features/tester/seed-data/tester-ttl.js +6 -0
  198. package/dist/features/tester/server.d.ts +9 -0
  199. package/dist/features/tester/server.js +8 -0
  200. package/dist/index.d.ts +28 -2
  201. package/dist/index.js +51 -1
  202. package/dist/next/api/api-response.js +5 -1
  203. package/dist/next/api/routeHandler.js +10 -1
  204. package/dist/next/middleware/chain.js +1 -1
  205. package/dist/next/routing/route-map.d.ts +6 -7
  206. package/dist/next/routing/route-map.js +3 -7
  207. package/dist/providers/payment-manual/index.d.ts +2 -2
  208. package/dist/providers/payment-manual/index.js +2 -2
  209. package/dist/react/contexts/SessionContext.d.ts +4 -0
  210. package/dist/react/contexts/SessionContext.js +4 -0
  211. package/dist/repositories/index.d.ts +4 -0
  212. package/dist/repositories/index.js +3 -0
  213. package/dist/security/pii-encrypt.js +12 -2
  214. package/dist/seed/addresses-seed-data.js +1 -1
  215. package/dist/seed/bids-seed-data.js +64 -185
  216. package/dist/seed/blog-posts-seed-data.js +1 -1
  217. package/dist/seed/carousel-slides-seed-data.js +22 -16
  218. package/dist/seed/carousels-seed-data.js +1 -1
  219. package/dist/seed/cart-seed-data.js +1 -1
  220. package/dist/seed/categories-seed-data.js +1 -1
  221. package/dist/seed/claimed-coupons-seed-data.js +1 -1
  222. package/dist/seed/conversations-seed-data.js +1 -1
  223. package/dist/seed/coupon-usage-seed-data.js +1 -1
  224. package/dist/seed/coupons-seed-data.js +1 -1
  225. package/dist/seed/events-seed-data.js +1 -1
  226. package/dist/seed/faq-seed-data.js +119 -3
  227. package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
  228. package/dist/seed/grouped-listings-seed-data.js +1 -1
  229. package/dist/seed/history-seed-data.js +1 -1
  230. package/dist/seed/homepage-sections-seed-data.js +1 -1
  231. package/dist/seed/index.d.ts +2 -2
  232. package/dist/seed/index.js +7 -3
  233. package/dist/seed/lottery-entries-seed-data.js +1 -1
  234. package/dist/seed/manifest.d.ts +4 -3
  235. package/dist/seed/manifest.js +13 -6
  236. package/dist/seed/notifications-seed-data.js +1 -1
  237. package/dist/seed/offers-seed-data.js +1 -1
  238. package/dist/seed/orders-seed-data.js +1 -1
  239. package/dist/seed/payouts-seed-data.js +1 -1
  240. package/dist/seed/products-art-seed-data.js +1 -1
  241. package/dist/seed/products-auctions-seed-data.js +1 -1
  242. package/dist/seed/products-classifieds-seed-data.js +1 -1
  243. package/dist/seed/products-digital-codes-seed-data.js +1 -1
  244. package/dist/seed/products-live-items-seed-data.js +1 -1
  245. package/dist/seed/products-preorders-seed-data.d.ts +2 -0
  246. package/dist/seed/products-preorders-seed-data.js +1 -1
  247. package/dist/seed/products-prize-draws-seed-data.js +1 -1
  248. package/dist/seed/products-standard-seed-data.js +1 -1
  249. package/dist/seed/products-stickers-seed-data.js +1 -1
  250. package/dist/seed/reviews-seed-data.js +1 -1
  251. package/dist/seed/scammers-seed-data.js +1 -1
  252. package/dist/seed/sessions-seed-data.js +1 -1
  253. package/dist/seed/site-settings-seed-data.js +22 -16
  254. package/dist/seed/store-addresses-seed-data.js +1 -1
  255. package/dist/seed/store-extensions-seed-data.js +1 -1
  256. package/dist/seed/stores-seed-data.js +1 -1
  257. package/dist/seed/support-tickets-seed-data.js +1 -1
  258. package/dist/seed/tester-checklist-seed-data.d.ts +2 -0
  259. package/dist/seed/tester-checklist-seed-data.js +248 -0
  260. package/dist/seed/users-seed-data.js +1 -1
  261. package/dist/seed/wishlists-seed-data.js +1 -1
  262. package/dist/server.d.ts +7 -2
  263. package/dist/server.js +19 -12
  264. package/dist/styles.css +181 -165
  265. package/dist/tailwind-utilities.css +1 -1
  266. package/dist/tokens/color-pairs.d.ts +46 -0
  267. package/dist/tokens/color-pairs.js +93 -0
  268. package/dist/tokens/index.d.ts +60 -60
  269. package/dist/tokens/index.js +30 -30
  270. package/dist/tokens/themes/cobalt-night.d.ts +3 -2
  271. package/dist/tokens/themes/cobalt-night.js +3 -2
  272. package/dist/tokens/themes/default-dark.d.ts +4 -1
  273. package/dist/tokens/themes/default-dark.js +53 -50
  274. package/dist/tokens/themes/default-light.d.ts +5 -1
  275. package/dist/tokens/themes/default-light.js +48 -44
  276. package/dist/tokens/themes/sunset.js +4 -4
  277. package/dist/tokens/tokens.css +128 -122
  278. package/dist/ui/components/CollapsibleSection.d.ts +15 -0
  279. package/dist/ui/components/CollapsibleSection.js +13 -0
  280. package/dist/ui/components/ListingToolbar.js +3 -2
  281. package/dist/ui/components/Semantic.style.css +3 -3
  282. package/dist/ui/components/SideDrawer.style.css +19 -19
  283. package/dist/ui/components/Slider.js +1 -1
  284. package/dist/ui/components/StickyToolbar.d.ts +36 -7
  285. package/dist/ui/components/StickyToolbar.js +63 -5
  286. package/dist/ui/components/Tabs.js +17 -1
  287. package/dist/ui/components/Tabs.style.css +10 -0
  288. package/dist/ui/components/Typography.js +7 -0
  289. package/dist/ui/components/surface-tokens.d.ts +0 -7
  290. package/dist/ui/components/surface-tokens.js +7 -0
  291. package/dist/ui/index.d.ts +6 -0
  292. package/dist/ui/index.js +3 -0
  293. package/dist/ui/rich-text/RichText.style.css +20 -20
  294. package/dist/utils/id-generators.d.ts +13 -0
  295. package/dist/utils/id-generators.js +17 -0
  296. package/package.json +1 -1
  297. package/scripts/seed-cli-loader.mjs +15 -0
  298. package/scripts/seed-cli.mjs +71 -17
@@ -37,8 +37,22 @@ export interface DashboardLayoutClientProps {
37
37
  activeHref?: string;
38
38
  /** Responsive controls — currently only hideAt is honoured. */
39
39
  responsive?: SectionResponsive;
40
- /** Optional render-prop slot for additional sidebar footer content. */
41
- renderSidebarFooter?: () => ReactNode;
40
+ /**
41
+ * Cross-dashboard nav links shown in the sidebar footer. Plain hrefs
42
+ * rather than a render-prop — a Server Component caller (every real
43
+ * consumer: admin/store/user layout.tsx) cannot legally pass a function
44
+ * across the RSC boundary to this "use client" component, so the
45
+ * previous `renderSidebarFooter?: () => ReactNode` shape was never
46
+ * actually usable from where it needed to be used.
47
+ */
48
+ crossNav?: {
49
+ /** Always-available "My Profile" shortcut — every dashboard user is also a user. */
50
+ profileHref?: string;
51
+ /** Admin only: shown when the signed-in admin also owns a store. */
52
+ storeHref?: string;
53
+ /** Seller only: shown when the signed-in seller account is also an admin/employee. */
54
+ adminHref?: string;
55
+ };
42
56
  /** Optional className passed through to the sidebar component. */
43
57
  className?: string;
44
58
  /** Override the content area padding classes. */
@@ -53,4 +67,4 @@ export interface DashboardLayoutClientProps {
53
67
  darkBackground?: BackgroundConfig;
54
68
  children: ReactNode;
55
69
  }
56
- export declare function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }: DashboardLayoutClientProps): import("react").JSX.Element;
70
+ export declare function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, crossNav, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }: DashboardLayoutClientProps): import("react").JSX.Element;
@@ -147,7 +147,7 @@ function DashboardBottomNav({ items, activeHref }) {
147
147
  }
148
148
  const DEFAULT_CONTENT_PADDING = "px-[var(--appkit-space-5)] py-[var(--appkit-space-8)] lg:pl-14 lg:pr-6 xl:pl-16 xl:pr-10";
149
149
  const DEFAULT_CONTENT_MAX_WIDTH = "max-w-screen-2xl";
150
- export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
150
+ export function DashboardLayoutClient({ variant, groups, permissions, activeHref: explicitActiveHref, responsive: _responsive, crossNav, className, contentPadding, contentSurface, contentMaxWidth, lightBackground, darkBackground, children, }) {
151
151
  const pathname = usePathname();
152
152
  const activeHref = explicitActiveHref ?? pathname ?? "";
153
153
  const storageKey = `appkit:sidebar-open:${variant}`;
@@ -165,7 +165,12 @@ export function DashboardLayoutClient({ variant, groups, permissions, activeHref
165
165
  const adminGroups = variant === "admin"
166
166
  ? filteredGroups
167
167
  : groups;
168
- return (_jsxs(_Fragment, { children: [hasBackground && (_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: resolvedLightBackground ?? { type: "color", value: "" }, darkMode: resolvedDarkBackground ?? { type: "color", value: "" } })), variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: adminGroups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: filteredGroups.flatMap((g) => g.items), groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className })), _jsx(Div, { className: [
168
+ const crossNavLinkClass = "flex items-center gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-[var(--appkit-color-text)] transition-colors";
169
+ const hasCrossNav = Boolean(crossNav?.profileHref || crossNav?.storeHref || crossNav?.adminHref);
170
+ const renderCrossNavFooter = hasCrossNav
171
+ ? () => (_jsxs(Nav, { "aria-label": "Cross-dashboard links", padding: "y-xs", children: [crossNav?.storeHref && (_jsx(Link, { href: crossNav.storeHref, onClick: closeMobile, className: crossNavLinkClass, children: "Go to my Store" })), crossNav?.adminHref && (_jsx(Link, { href: crossNav.adminHref, onClick: closeMobile, className: crossNavLinkClass, children: "Back to Admin" })), crossNav?.profileHref && (_jsx(Link, { href: crossNav.profileHref, onClick: closeMobile, className: crossNavLinkClass, children: "My Profile" }))] }))
172
+ : undefined;
173
+ return (_jsxs(_Fragment, { children: [hasBackground && (_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: resolvedLightBackground ?? { type: "color", value: "" }, darkMode: resolvedDarkBackground ?? { type: "color", value: "" } })), variant === "admin" && (_jsx(AdminSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activePath: activeHref, groups: adminGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), variant === "store" && (_jsx(StoreSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, activeHref: activeHref, items: [], groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), variant === "user" && (_jsx(UserSidebar, { variant: "sidebar", desktopOpen: desktopOpen, mobileOpen: mobileOpen, items: filteredGroups.flatMap((g) => g.items), groups: filteredGroups, onCloseMobile: close, onToggle: toggle, className: className, renderFooter: renderCrossNavFooter })), _jsx(Div, { className: [
169
174
  "w-full flex-1 flex flex-col min-h-[calc(100dvh-var(--header-height,3.5rem))]",
170
175
  "pb-[var(--appkit-space-16)] lg:pb-[0]", // clears the fixed mobile bottom-nav
171
176
  contentPadding ?? DEFAULT_CONTENT_PADDING,
@@ -2,8 +2,11 @@ import React from "react";
2
2
  import type { DriveStep } from "driver.js";
3
3
  export type TourRole = "buyer" | "seller" | "admin";
4
4
  export interface TourContextValue {
5
- /** Starts the tour for the given role (defaults to "buyer"). */
6
- startTour: (role?: TourRole) => void;
5
+ /** Starts the tour for the given role (defaults to "buyer") at the given
6
+ * pathname (defaults to "/" — the full role-based tour). Passing the
7
+ * current route lets the tour show only the steps relevant to what's
8
+ * actually on screen, when that route has a registered contextual set. */
9
+ startTour: (role?: TourRole, pathname?: string) => void;
7
10
  }
8
11
  export declare function useTour(): TourContextValue;
9
12
  export declare const CUSTOMER_TOUR_STEPS: DriveStep[];
@@ -12,16 +12,32 @@ const step = (element, title, description) => ({
12
12
  popover: { title, description },
13
13
  skipMissingElement: true,
14
14
  });
15
- // P-16 — role-specific step sets. Anchors are `data-tour="…"` attributes on
16
- // TitleBarLayout's icon buttons (present on every page) plus a couple of
17
- // dashboard-specific anchors. `skipMissingElement: true` on every step means
18
- // a step whose target isn't on the current page is silently skipped rather
19
- // than breaking the tour — the same tour can be started from anywhere.
15
+ // P-16 — Anchors are `data-tour="…"` attributes on TitleBarLayout's icon
16
+ // buttons (present on every page) plus a couple of dashboard-specific
17
+ // anchors. `skipMissingElement: true` on every step means a step whose
18
+ // target isn't on the current page is silently skipped rather than
19
+ // breaking the tour.
20
+ //
21
+ // Redesigned (Phase 16) from one fixed step list per role into: a short
22
+ // universal nav orientation (same on every page, since the header anchors
23
+ // are always present) + a per-route contextual slice keyed off the current
24
+ // pathname. A route with no registered slice falls back to that role's full
25
+ // legacy step list, so nothing regresses for pages not yet given contextual
26
+ // content.
27
+ const UNIVERSAL_NAV_STEPS = {
28
+ buyer: [
29
+ step('[data-tour="nav-search"]', "Search", "Search by name, brand, or price to find what you're after."),
30
+ step('[data-tour="nav-wishlist"]', "Wishlist", "Save items here to come back to them later."),
31
+ step('[data-tour="nav-cart"]', "Cart", "Review your items and check out when you're ready."),
32
+ step('[data-tour="nav-profile"]', "Your account", "Manage your profile, orders, and messages here."),
33
+ ],
34
+ seller: [
35
+ step('[data-tour="nav-profile"]', "Store settings", "Set up your store profile, shipping, and pickup address."),
36
+ ],
37
+ admin: [],
38
+ };
20
39
  export const CUSTOMER_TOUR_STEPS = [
21
- step('[data-tour="nav-search"]', "Search", "Search by name, brand, or price to find what you're after."),
22
- step('[data-tour="nav-wishlist"]', "Wishlist", "Save items here to come back to them later."),
23
- step('[data-tour="nav-cart"]', "Cart", "Review your items and check out when you're ready."),
24
- step('[data-tour="nav-profile"]', "Your account", "Manage your profile, orders, and messages here."),
40
+ ...UNIVERSAL_NAV_STEPS.buyer,
25
41
  {
26
42
  popover: {
27
43
  title: "Checkout",
@@ -54,7 +70,7 @@ export const SELLER_TOUR_STEPS = [
54
70
  },
55
71
  skipMissingElement: true,
56
72
  },
57
- step('[data-tour="nav-profile"]', "Store settings", "Set up your store profile, shipping, and pickup address."),
73
+ ...UNIVERSAL_NAV_STEPS.seller,
58
74
  step('[data-tour="store-analytics"]', "Analytics", "Track your sales, revenue, and top products."),
59
75
  step('[data-tour="store-payouts"]', "Payouts", "See what you've earned and your payout history."),
60
76
  ];
@@ -79,6 +95,103 @@ const STEP_SETS = {
79
95
  seller: SELLER_TOUR_STEPS,
80
96
  admin: ADMIN_TOUR_STEPS,
81
97
  };
98
+ // One entry per page that has dedicated data-tour anchors — the contextual
99
+ // slice for that exact page, not the whole role's tour.
100
+ const ROUTE_TOUR_STEPS = [
101
+ {
102
+ pathPrefix: "/store/products",
103
+ role: "seller",
104
+ steps: [
105
+ step('[data-tour="store-products"]', "Your listings", "Create and manage your product listings here."),
106
+ {
107
+ popover: {
108
+ title: "Product form",
109
+ description: "Fill in details, images, and pricing — you can save as a draft anytime.",
110
+ },
111
+ skipMissingElement: true,
112
+ },
113
+ ],
114
+ },
115
+ {
116
+ pathPrefix: "/store/orders",
117
+ role: "seller",
118
+ steps: [
119
+ step('[data-tour="store-orders"]', "Incoming orders", "See and manage orders as buyers place them."),
120
+ {
121
+ popover: {
122
+ title: "Mark as shipped",
123
+ description: "Open an order to enter the carrier name and tracking number once it ships.",
124
+ },
125
+ skipMissingElement: true,
126
+ },
127
+ ],
128
+ },
129
+ {
130
+ pathPrefix: "/store/analytics",
131
+ role: "seller",
132
+ steps: [step('[data-tour="store-analytics"]', "Analytics", "Track your sales, revenue, and top products.")],
133
+ },
134
+ {
135
+ pathPrefix: "/store/payouts",
136
+ role: "seller",
137
+ steps: [step('[data-tour="store-payouts"]', "Payouts", "See what you've earned and your payout history.")],
138
+ },
139
+ {
140
+ pathPrefix: "/admin/orders",
141
+ role: "admin",
142
+ steps: [
143
+ step('[data-tour="admin-orders"]', "Marketplace orders", "All orders across every store, in one place."),
144
+ {
145
+ popover: {
146
+ title: "Verify payments",
147
+ description: "Open an order to confirm manual payments and update its status.",
148
+ },
149
+ skipMissingElement: true,
150
+ },
151
+ ],
152
+ },
153
+ {
154
+ pathPrefix: "/admin/products",
155
+ role: "admin",
156
+ steps: [step('[data-tour="admin-products"]', "Moderation", "Approve or reject listings before they go live.")],
157
+ },
158
+ {
159
+ pathPrefix: "/admin/users",
160
+ role: "admin",
161
+ steps: [step('[data-tour="admin-users"]', "Users", "Manage buyer and seller accounts.")],
162
+ },
163
+ {
164
+ pathPrefix: "/admin/stores",
165
+ role: "admin",
166
+ steps: [step('[data-tour="admin-stores"]', "Stores", "Approve new store applications.")],
167
+ },
168
+ {
169
+ pathPrefix: "/admin/analytics",
170
+ role: "admin",
171
+ steps: [step('[data-tour="admin-analytics"]', "Analytics", "Platform-wide GMV, orders, and top products.")],
172
+ },
173
+ {
174
+ pathPrefix: "/admin/site",
175
+ role: "admin",
176
+ steps: [step('[data-tour="admin-settings"]', "Site settings", "Configure how the platform behaves.")],
177
+ },
178
+ {
179
+ pathPrefix: "/admin/feature-flags",
180
+ role: "admin",
181
+ steps: [step('[data-tour="admin-feature-flags"]', "Feature flags", "Turn platform features on or off.")],
182
+ },
183
+ ];
184
+ /** Strips a leading two-letter locale segment ("/en/store/products" → "/store/products"). */
185
+ function stripLocale(pathname) {
186
+ return pathname.replace(/^\/[a-z]{2}(?=\/|$)/, "") || "/";
187
+ }
188
+ function resolveSteps(role, pathname) {
189
+ const stripped = stripLocale(pathname);
190
+ const match = ROUTE_TOUR_STEPS.find((entry) => entry.role === role && stripped.startsWith(entry.pathPrefix));
191
+ if (!match)
192
+ return STEP_SETS[role];
193
+ return [...UNIVERSAL_NAV_STEPS[role], ...match.steps];
194
+ }
82
195
  /**
83
196
  * TourProvider — lazily imports driver.js on first use so it never lands in
84
197
  * the initial bundle. Respects prefers-reduced-motion by disabling driver.js's
@@ -86,7 +199,7 @@ const STEP_SETS = {
86
199
  * works; only the animated transition is skipped).
87
200
  */
88
201
  export function TourProvider({ children }) {
89
- const startTour = React.useCallback((role = "buyer") => {
202
+ const startTour = React.useCallback((role = "buyer", pathname = "/") => {
90
203
  if (typeof window === "undefined")
91
204
  return;
92
205
  void (async () => {
@@ -99,7 +212,7 @@ export function TourProvider({ children }) {
99
212
  showProgress: true,
100
213
  animate: !reducedMotion,
101
214
  allowClose: true,
102
- steps: STEP_SETS[role],
215
+ steps: resolveSteps(role, pathname),
103
216
  };
104
217
  driver(config).drive();
105
218
  })();
@@ -51,7 +51,7 @@ function applyTheme(theme) {
51
51
  else
52
52
  root.classList.remove("dark");
53
53
  // Persist active theme ID so the anti-FOUC script can restore it on return visits,
54
- // preventing a flash to the built-in cobalt/hot-pink colors on page load.
54
+ // preventing a flash to the built-in teal/crimson colors on page load.
55
55
  try {
56
56
  window.localStorage.setItem(THEME_ID_STORAGE_KEY, theme.id);
57
57
  }
@@ -16,14 +16,14 @@ export const listRootCategories = cache(async () => {
16
16
  /** Featured categories for homepage display (showOnHomepage: true). */
17
17
  export const listFeaturedCategories = cache(async () => {
18
18
  return categoriesRepository
19
- .list({ filters: "isActive==true,isFeatured==true", sorts: "displayOrder", page: 1, pageSize: CATEGORIES_FEATURED_LIMIT })
19
+ .list({ filters: "isActive==true,showOnHomepage==true", sorts: "order", page: 1, pageSize: CATEGORIES_FEATURED_LIMIT })
20
20
  .then((r) => r.data ?? [])
21
21
  .catch(() => []);
22
22
  });
23
23
  /** Nav-menu categories (showInMenu: true, tier 1 + 2). */
24
24
  export const listMenuCategories = cache(async () => {
25
25
  return categoriesRepository
26
- .list({ filters: "isActive==true,display.showInMenu==true", sorts: "displayOrder", page: 1, pageSize: CATEGORIES_MENU_LIMIT })
26
+ .list({ filters: "isActive==true,display.showInMenu==true", sorts: "order", page: 1, pageSize: CATEGORIES_MENU_LIMIT })
27
27
  .then((r) => r.data ?? [])
28
28
  .catch(() => []);
29
29
  });
@@ -1,4 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
3
+ // Satori (next/og) needs literal color values, not CSS vars — derived from
4
+ // the theme token record so a rebrand doesn't require re-hardcoding here.
5
+ const OG_FAQ_GRADIENT = `linear-gradient(135deg, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"]} 0%, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary-700"]} 100%)`;
2
6
  export function renderFaqOg(category, opts) {
3
7
  const label = category
4
8
  .replace(/-/g, " ")
@@ -11,7 +15,7 @@ export function renderFaqOgImage(data) {
11
15
  display: "flex",
12
16
  width: "100%",
13
17
  height: "100%",
14
- background: "linear-gradient(135deg, #3570fc 0%, #1343de 100%)",
18
+ background: OG_FAQ_GRADIENT,
15
19
  fontFamily: "sans-serif",
16
20
  alignItems: "center",
17
21
  justifyContent: "center",
@@ -1,4 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
3
+ // Satori (next/og) needs literal color values, not CSS vars — derived from
4
+ // the theme token record so a rebrand doesn't require re-hardcoding here.
5
+ const OG_AVATAR_GRADIENT = `linear-gradient(135deg, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"]}, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-secondary"]})`;
2
6
  function stars(n) {
3
7
  return "★".repeat(Math.min(5, Math.max(1, Math.round(n))));
4
8
  }
@@ -41,7 +45,7 @@ export function renderReviewOgImage(data, siteName) {
41
45
  width: 80,
42
46
  height: 80,
43
47
  borderRadius: 40,
44
- background: "linear-gradient(135deg, #3570fc, #e91e8c)",
48
+ background: OG_AVATAR_GRADIENT,
45
49
  display: "flex",
46
50
  alignItems: "center",
47
51
  justifyContent: "center",
@@ -1,4 +1,5 @@
1
1
  import { ROUTES } from "../../../../next/routing/route-map";
2
+ import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
2
3
  export function buildManifest({ name, shortName, description }) {
3
4
  return {
4
5
  name,
@@ -8,7 +9,7 @@ export function buildManifest({ name, shortName, description }) {
8
9
  display: "standalone",
9
10
  orientation: "portrait",
10
11
  background_color: "#ffffff",
11
- theme_color: "#3570fc",
12
+ theme_color: DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"],
12
13
  categories: ["shopping", "marketplace", "business"],
13
14
  lang: "en",
14
15
  dir: "ltr",
@@ -1,6 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ImageResponse } from "next/og";
3
+ import { DEFAULT_LIGHT_THEME } from "../../../../tokens/themes/default-light";
3
4
  export const DEFAULT_OG_SIZE = { width: 1200, height: 630 };
5
+ // Satori (next/og) cannot resolve CSS custom properties — it needs literal
6
+ // color values baked into the style object. Derived from the theme's own
7
+ // token record (not re-hardcoded) so a future rebrand only requires editing
8
+ // default-light.ts, not hunting down every Satori-rendered image separately.
9
+ const OG_BRAND_GRADIENT = `linear-gradient(135deg, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary"]} 0%, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-secondary"]} 50%, ${DEFAULT_LIGHT_THEME.tokens["appkit-color-primary-800"]} 100%)`;
4
10
  /**
5
11
  * Resolves a potentially relative image URL to an absolute URL for use in
6
12
  * next/og (Satori). Satori cannot resolve relative paths; all <img src> values
@@ -27,7 +33,7 @@ export function buildDefaultOgImage({ siteName, tagline, domain, logoUrl }) {
27
33
  flexDirection: "column",
28
34
  alignItems: "center",
29
35
  justifyContent: "center",
30
- background: "linear-gradient(135deg, #3570fc 0%, #65c408 50%, #e91e8c 100%)",
36
+ background: OG_BRAND_GRADIENT,
31
37
  fontFamily: "sans-serif",
32
38
  position: "relative",
33
39
  overflow: "hidden",
@@ -0,0 +1,13 @@
1
+ export interface TestDataFlagged {
2
+ isTestData?: boolean;
3
+ }
4
+ export interface ViewerLike {
5
+ isTester?: boolean;
6
+ role?: string | null;
7
+ }
8
+ /** True when the viewer is allowed to see isTestData:true documents. */
9
+ export declare function canViewTestData(viewer: ViewerLike | null | undefined): boolean;
10
+ /** Strips isTestData:true items from a list unless the viewer is a tester or admin. */
11
+ export declare function filterTestDataForViewer<T extends TestDataFlagged>(items: T[], viewer: ViewerLike | null | undefined): T[];
12
+ /** Returns null (as if not found) for an isTestData:true single doc when the viewer can't see it. */
13
+ export declare function filterSingleTestData<T extends TestDataFlagged>(item: T | null, viewer: ViewerLike | null | undefined): T | null;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Tester sandbox visibility — the shared test store/products/categories/blog/event are
3
+ * created as fully public/active documents so testers exercise the real browse/search/
4
+ * checkout code paths, but must stay invisible to non-testers. Filtering happens at the
5
+ * application layer (post-fetch, in-memory) rather than via a Firestore `where` clause,
6
+ * because `isTestData` is a new optional field — a Firestore inequality filter
7
+ * (`where("isTestData", "!=", true)`) would silently exclude every pre-existing document
8
+ * that doesn't have the field set at all, which is worse than the problem it solves.
9
+ */
10
+ import { isAdminUser } from "../../../../features/auth/role-predicates";
11
+ /** True when the viewer is allowed to see isTestData:true documents. */
12
+ export function canViewTestData(viewer) {
13
+ return Boolean(viewer?.isTester) || isAdminUser(viewer);
14
+ }
15
+ /** Strips isTestData:true items from a list unless the viewer is a tester or admin. */
16
+ export function filterTestDataForViewer(items, viewer) {
17
+ if (canViewTestData(viewer))
18
+ return items;
19
+ return items.filter((item) => item.isTestData !== true);
20
+ }
21
+ /** Returns null (as if not found) for an isTestData:true single doc when the viewer can't see it. */
22
+ export function filterSingleTestData(item, viewer) {
23
+ if (!item)
24
+ return null;
25
+ if (item.isTestData === true && !canViewTestData(viewer))
26
+ return null;
27
+ return item;
28
+ }
@@ -14,6 +14,7 @@ export declare const autoPayoutEligibility: import("./types").ScheduledFunctionD
14
14
  export declare const cleanupRtdbEvents: import("./types").ScheduledFunctionDefinition;
15
15
  export declare const mediaTmpCleanup: import("./types").ScheduledFunctionDefinition;
16
16
  export declare const draftPrune: import("./types").ScheduledFunctionDefinition;
17
+ export declare const testerSandboxCleanup: import("./types").ScheduledFunctionDefinition;
17
18
  export declare const prizeRevealOpen: import("./types").ScheduledFunctionDefinition;
18
19
  export declare const prizeRevealClose: import("./types").ScheduledFunctionDefinition;
19
20
  export declare const prizeRevealExpiry: import("./types").ScheduledFunctionDefinition;
@@ -21,4 +22,4 @@ export declare const prizeRevealReminder: import("./types").ScheduledFunctionDef
21
22
  export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
22
23
  export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
23
24
  export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
24
- export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
25
+ export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
@@ -1,6 +1,6 @@
1
1
  // Scheduled function definitions (Cloud Scheduler triggers).
2
2
  // Cron syntax accepts both Firebase shorthand and standard cron expressions.
3
- import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealOpenHandler, prizeRevealReminderHandler, productStatsSyncHandler, weeklyPayoutEligibilityHandler, } from "../jobs/handlers";
3
+ import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeRevealCloseHandler, prizeRevealExpiryHandler, prizeRevealOpenHandler, prizeRevealReminderHandler, productStatsSyncHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
4
4
  import { defineFunction } from "./define";
5
5
  const REGION = "asia-south1";
6
6
  const EVERY_5_MIN = "every 5 minutes";
@@ -116,6 +116,13 @@ export const draftPrune = defineFunction({
116
116
  handler: draftPruneHandler,
117
117
  options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
118
118
  });
119
+ export const testerSandboxCleanup = defineFunction({
120
+ name: "testerSandboxCleanup",
121
+ description: "Delete expired tester QA sandbox test data (daily 05:00 UTC).",
122
+ trigger: { kind: "schedule", cron: "0 5 * * *", timeZone: "UTC" },
123
+ handler: testerSandboxCleanupHandler,
124
+ options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
125
+ });
119
126
  export const prizeRevealOpen = defineFunction({
120
127
  name: "prizeRevealOpen",
121
128
  description: "Flip prize-draw reveals pending→open and send opening notifications.",
@@ -182,6 +189,7 @@ export const SCHEDULED_FUNCTIONS = [
182
189
  cleanupRtdbEvents,
183
190
  mediaTmpCleanup,
184
191
  draftPrune,
192
+ testerSandboxCleanup,
185
193
  prizeRevealOpen,
186
194
  prizeRevealClose,
187
195
  prizeRevealExpiry,
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { runWeeklyPayoutEligibility } from "./weeklyPayoutEligibility";
12
12
  import { runHardBanCascade } from "./hardBanCascade";
13
+ import { runResetOtpVerification } from "./resetOtpVerification";
13
14
  async function runPayoutsWeeklyJob(_payload, ctx) {
14
15
  const { payoutsCreated, ordersProcessed, payoutIds } = await runWeeklyPayoutEligibility(ctx);
15
16
  return {
@@ -31,7 +32,11 @@ async function runHardBanCascadeJob(payload, ctx) {
31
32
  const bannedBy = String(payload.bannedBy ?? "");
32
33
  return runHardBanCascade({ uid, reason, bannedBy }, ctx);
33
34
  }
35
+ async function runResetOtpVerificationJob(_payload, ctx) {
36
+ return runResetOtpVerification(ctx);
37
+ }
34
38
  export const JOB_RUNNERS = {
35
39
  payoutsWeekly: runPayoutsWeeklyJob,
36
40
  hardBanCascade: runHardBanCascadeJob,
41
+ resetOtpVerification: runResetOtpVerificationJob,
37
42
  };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Core: reset OTP/phone verification state — runs when an admin flips
3
+ * `siteSettings.featureFlags.smsVerification` from off to on (see
4
+ * `PATCH /api/site-settings` in the consumer, which enqueues this job on
5
+ * that specific transition).
6
+ *
7
+ * Re-enabling SMS verification after a period of being off means every
8
+ * previously-verified user's `phoneVerified` flag reflects a verification
9
+ * that happened under different rules (or none at all, if verification was
10
+ * off when they signed up) — reset it so everyone re-verifies. The daily
11
+ * global counter and any live per-user cooldowns are also cleared so the
12
+ * freshly-re-enabled feature doesn't inherit rate-limit state from before
13
+ * it was off.
14
+ */
15
+ import type { JobContext } from "../runtime/types";
16
+ import type { JobRunResult } from "./jobRunners";
17
+ export declare function runResetOtpVerification(ctx: JobContext): Promise<JobRunResult>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Core: reset OTP/phone verification state — runs when an admin flips
3
+ * `siteSettings.featureFlags.smsVerification` from off to on (see
4
+ * `PATCH /api/site-settings` in the consumer, which enqueues this job on
5
+ * that specific transition).
6
+ *
7
+ * Re-enabling SMS verification after a period of being off means every
8
+ * previously-verified user's `phoneVerified` flag reflects a verification
9
+ * that happened under different rules (or none at all, if verification was
10
+ * off when they signed up) — reset it so everyone re-verifies. The daily
11
+ * global counter and any live per-user cooldowns are also cleared so the
12
+ * freshly-re-enabled feature doesn't inherit rate-limit state from before
13
+ * it was off.
14
+ */
15
+ import { USER_COLLECTION } from "../../../../features/auth/schemas/firestore";
16
+ import { SMS_COUNTERS_COLLECTION } from "../../../../features/auth/schemas/firestore";
17
+ import { normalizeError } from "../../../../errors/normalize";
18
+ const BATCH_SIZE = 400;
19
+ async function resetPhoneVerifiedFlags(ctx) {
20
+ const failed = [];
21
+ let updated = 0;
22
+ let lastDocId = null;
23
+ for (;;) {
24
+ let query = ctx.db
25
+ .collection(USER_COLLECTION)
26
+ .where("phoneVerified", "==", true)
27
+ .orderBy("__name__")
28
+ .limit(BATCH_SIZE);
29
+ if (lastDocId) {
30
+ query = query.startAfter(lastDocId);
31
+ }
32
+ const snap = await query.get();
33
+ if (snap.empty)
34
+ break;
35
+ const batch = ctx.db.batch();
36
+ for (const doc of snap.docs) {
37
+ batch.update(doc.ref, { phoneVerified: false });
38
+ }
39
+ try {
40
+ await batch.commit();
41
+ updated += snap.docs.length;
42
+ }
43
+ catch (err) {
44
+ void normalizeError(err);
45
+ ctx.logger.error("resetOtpVerification: batch commit failed", err);
46
+ for (const doc of snap.docs)
47
+ failed.push(doc.id);
48
+ }
49
+ lastDocId = snap.docs[snap.docs.length - 1].id;
50
+ if (snap.docs.length < BATCH_SIZE)
51
+ break;
52
+ }
53
+ return { updated, failed };
54
+ }
55
+ async function clearSmsCounters(ctx) {
56
+ let removed = 0;
57
+ for (;;) {
58
+ const snap = await ctx.db.collection(SMS_COUNTERS_COLLECTION).limit(BATCH_SIZE).get();
59
+ if (snap.empty)
60
+ break;
61
+ const batch = ctx.db.batch();
62
+ for (const doc of snap.docs)
63
+ batch.delete(doc.ref);
64
+ await batch.commit();
65
+ removed += snap.docs.length;
66
+ if (snap.docs.length < BATCH_SIZE)
67
+ break;
68
+ }
69
+ return removed;
70
+ }
71
+ export async function runResetOtpVerification(ctx) {
72
+ ctx.logger.info("resetOtpVerification: starting (smsVerification re-enabled)");
73
+ const { updated, failed } = await resetPhoneVerifiedFlags(ctx);
74
+ const countersCleared = await clearSmsCounters(ctx);
75
+ ctx.logger.info("resetOtpVerification: done", { updated, failed: failed.length, countersCleared });
76
+ return {
77
+ summary: {
78
+ total: updated + failed.length,
79
+ succeeded: updated,
80
+ skipped: 0,
81
+ failed: failed.length,
82
+ },
83
+ succeeded: [],
84
+ skipped: [],
85
+ failed: failed.map((id) => ({ id, reason: "batch commit failed" })),
86
+ data: { usersReset: updated, smsCountersCleared: countersCleared },
87
+ };
88
+ }
@@ -0,0 +1,6 @@
1
+ import type { JobContext } from "../runtime/types";
2
+ export interface TesterSandboxCleanupOptions {
3
+ /** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
4
+ force?: boolean;
5
+ }
6
+ export declare function runTesterSandboxCleanup(ctx: JobContext, opts?: TesterSandboxCleanupOptions): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { batchDelete, getTestDataRefs } from "../handlers/_helpers";
2
+ import { BID_FIELDS } from "../../../../constants/field-names";
3
+ const SANDBOX_COLLECTIONS = ["categories", "stores", "products", "blogPosts", "events"];
4
+ const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
5
+ export async function runTesterSandboxCleanup(ctx, opts = {}) {
6
+ const cutoff = opts.force ? null : new Date();
7
+ ctx.logger.info(opts.force
8
+ ? "Force-purging all tester sandbox test data"
9
+ : "Sweeping expired tester sandbox test data");
10
+ const allRefs = [];
11
+ let deletedProductIds = [];
12
+ for (const collection of SANDBOX_COLLECTIONS) {
13
+ const refs = await getTestDataRefs(ctx.db, collection, cutoff);
14
+ allRefs.push(...refs);
15
+ if (collection === "products") {
16
+ deletedProductIds = refs.map((ref) => ref.id);
17
+ }
18
+ }
19
+ // Cascade into bids referencing deleted test products — bids hold only a live
20
+ // productId FK with no denormalized snapshot, so leaving them behind after the
21
+ // product is gone would create meaningless orphaned auction history.
22
+ for (let i = 0; i < deletedProductIds.length; i += BID_CHUNK_SIZE) {
23
+ const chunk = deletedProductIds.slice(i, i + BID_CHUNK_SIZE);
24
+ if (chunk.length === 0)
25
+ continue;
26
+ const snap = await ctx.db.collection("bids").where(BID_FIELDS.PRODUCT_ID, "in", chunk).get();
27
+ allRefs.push(...snap.docs.map((d) => d.ref));
28
+ }
29
+ if (allRefs.length === 0) {
30
+ ctx.logger.info("No tester sandbox test data to clean up");
31
+ return;
32
+ }
33
+ const deleted = await batchDelete(ctx, allRefs);
34
+ ctx.logger.info("Tester sandbox cleanup complete", { deleted });
35
+ }
@@ -6,3 +6,9 @@ import type { JobContext } from "../runtime/types";
6
6
  /** Batch-delete document refs, respecting Firestore's 500-write batch limit. */
7
7
  export declare function batchDelete(ctx: JobContext, refs: FirebaseFirestore.DocumentReference[]): Promise<number>;
8
8
  export { getAdminRealtimeDb };
9
+ /**
10
+ * Query docs tagged isTestData:true in a collection. `cutoff: null` returns every
11
+ * tester-sandbox doc regardless of expiry (manual force-purge); a real Date returns
12
+ * only the ones past testDataExpiresAt (the scheduled 7-day sweep).
13
+ */
14
+ export declare function getTestDataRefs(db: FirebaseFirestore.Firestore, collection: string, cutoff: Date | null): Promise<FirebaseFirestore.DocumentReference[]>;