@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
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import type { ListingLayoutProps } from "../../../ui";
3
+ export interface AdminTesterChecklistViewProps extends ListingLayoutProps {
4
+ onBulkDelete?: (ids: string[]) => Promise<void>;
5
+ }
6
+ export declare function AdminTesterChecklistView({ children, onBulkDelete, ...props }: AdminTesterChecklistViewProps): React.JSX.Element;
@@ -0,0 +1,93 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
+ import { sortBy } from "@mohasinac/appkit";
5
+ import React, { useMemo } from "react";
6
+ import { FilterChipGroup, Heading, ListingLayout, Span, Text } from "../../../ui";
7
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
9
+ import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
10
+ import { DataListingView } from "./DataListingView";
11
+ import { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
12
+ const COLUMNS = [
13
+ {
14
+ key: "primary",
15
+ header: "Test case",
16
+ sortable: true,
17
+ render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { weight: "semibold", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", children: row.secondary })] })),
18
+ },
19
+ {
20
+ key: "status",
21
+ header: "Status",
22
+ className: "w-32",
23
+ render: (row) => (_jsx(Span, { size: "xs", weight: "medium", className: "inline-flex bg-primary-50 text-primary-800 dark:bg-secondary-900/30 dark:text-secondary-300", rounded: "full", padding: "pill-sm-tall", children: row.status })),
24
+ },
25
+ {
26
+ key: "updatedAt",
27
+ header: "Updated",
28
+ className: "w-32",
29
+ render: (row) => (_jsx(Text, { size: "sm", color: "muted", children: row.updatedAt })),
30
+ },
31
+ ];
32
+ export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
33
+ const config = useMemo(() => ({
34
+ portal: "admin",
35
+ title: "Tester Checklist",
36
+ searchPlaceholder: "Search test cases, groups, or pages",
37
+ emptyLabel: "No checklist items found",
38
+ filterKeys: ["isActive"],
39
+ defaultSort: sortBy("order", "ASC"),
40
+ queryKey: ["admin", "tester-checklist-items", "listing"],
41
+ endpoint: ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEMS,
42
+ sortOptions: [
43
+ { value: "order", label: "Order" },
44
+ { value: sortBy("createdAt", "DESC"), label: "Newest" },
45
+ { value: "label", label: "Test case A–Z" },
46
+ ],
47
+ columns: COLUMNS,
48
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
49
+ id: toStringValue(item.id, `checklist-${index}`),
50
+ primary: toStringValue(item.label, "Untitled test case"),
51
+ secondary: `${toStringValue(item.groupLabel, "Uncategorized")} / ${toStringValue(item.pageLabel, "")}`,
52
+ status: toStringValue(typeof item.isActive === "boolean"
53
+ ? item.isActive
54
+ ? "Active"
55
+ : "Inactive"
56
+ : item.status, "Active"),
57
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
58
+ })),
59
+ getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
60
+ buildFilters: (state) => state.isActive ? sieveFilter("isActive", SIEVE_OP.EQ, state.isActive) : undefined,
61
+ primaryAction: {
62
+ label: "Add Test Case",
63
+ onClick: ({ openCreatePanel }) => openCreatePanel(),
64
+ },
65
+ // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
66
+ buildBulkActions: (selection) => {
67
+ const handlers = {
68
+ [ROW_ACTION_ID.DELETE]: onBulkDelete
69
+ ? async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); }
70
+ : undefined,
71
+ };
72
+ return ADMIN_BULK_ACTIONS.testerChecklistItems
73
+ .filter((id) => handlers[id])
74
+ .map((id) => ({
75
+ id,
76
+ label: ROW_ACTION_META[id].label,
77
+ destructive: ROW_ACTION_META[id].destructive,
78
+ onClick: handlers[id],
79
+ }));
80
+ },
81
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: [
82
+ { id: "All", label: "All" },
83
+ { id: "true", label: "Active" },
84
+ { id: "false", label: "Inactive" },
85
+ ], value: pendingFilters.isActive || "", onChange: (v) => setPendingFilters((p) => ({ ...p, isActive: v })) })),
86
+ renderEditor: ({ editId, closePanel }) => (_jsx(AdminTesterChecklistItemEditorView, { itemId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })),
87
+ resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Test Case" : "Edit Test Case"),
88
+ }), [onBulkDelete]);
89
+ if (React.Children.count(children) > 0) {
90
+ return (_jsxs(ListingLayout, { portal: "admin", ...props, children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Tester Checklist" }), children] }));
91
+ }
92
+ return (_jsxs(_Fragment, { children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Tester Checklist" }), _jsx(DataListingView, { config: config })] }));
93
+ }
@@ -14,6 +14,8 @@ export interface AdminUserEditorViewProps {
14
14
  currentRole?: string;
15
15
  currentIsDisabled?: boolean;
16
16
  currentEmailVerified?: boolean;
17
+ /** Tester program flag — orthogonal to role. Grants access to the Tester Hub and auto-approves the user's store. */
18
+ currentIsTester?: boolean;
17
19
  /** Store the user owns (for sellers/admins). storeId === storeSlug in this project. */
18
20
  ownedStoreId?: string;
19
21
  ownedStoreName?: string;
@@ -33,5 +35,5 @@ export interface AdminUserEditorViewProps {
33
35
  linkedin?: string;
34
36
  };
35
37
  }
36
- export declare function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }: AdminUserEditorViewProps): React.JSX.Element;
38
+ export declare function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, currentIsTester, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }: AdminUserEditorViewProps): React.JSX.Element;
37
39
  export {};
@@ -41,12 +41,13 @@ function SoftBanPanel({ userId, softBans, showAddSoftBan, setShowAddSoftBan, sof
41
41
  return (_jsxs(Div, { surface: "muted", padding: "sm", rounded: "lg", border: "default", children: [_jsxs(Row, { justify: "between", gap: "sm", className: "mb-2", children: [_jsxs(Span, { size: "sm", weight: "medium", color: "muted", children: ["Soft bans", softBans.length > 0 ? ` (${softBans.length})` : ""] }), !showAddSoftBan && (_jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: !userId, onClick: () => setShowAddSoftBan(true), children: "Add soft ban" }))] }), softBans.length > 0 && (_jsx(Stack, { as: "ul", gap: "xs", className: "mb-3", children: softBans.map((ban) => (_jsxs(Row, { as: "li", align: "start", justify: "between", gap: "xs", surface: "default", padding: "inlineSm", rounded: "md", border: "default", children: [_jsxs(Stack, { gap: "none", className: "min-w-0 flex-1", children: [_jsx(Span, { size: "xs", weight: "semibold", children: formatBanAction(ban.action) }), _jsx(Span, { size: "xs", color: "muted", children: ban.reason }), _jsx(Span, { size: "xs", color: "muted", children: formatExpiry(ban.expiresAt) })] }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", isLoading: liftPending, disabled: liftPending, onClick: () => onLiftSoftBan(ban.action), children: "Lift" })] }, ban.action))) })), showAddSoftBan && (_jsxs(Stack, { gap: "xs", children: [_jsx(Select, { label: "Action to restrict", options: BANNED_ACTION_OPTIONS, value: softBanAction, onValueChange: setSoftBanAction }), _jsx(Textarea, { label: "Reason (required)", value: softBanReason, onChange: (e) => setSoftBanReason(e.target.value), rows: 2, placeholder: "e.g. Suspicious bid activity\u2026" }), _jsx(Input, { label: "Expires at (optional \u2014 leave blank for permanent)", type: "datetime-local", value: softBanExpiry, onChange: (e) => setSoftBanExpiry(e.target.value) }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: softBanPending, disabled: !softBanReason.trim() || softBanPending, onClick: () => onAddSoftBan({ action: softBanAction, reason: softBanReason.trim(), ...(softBanExpiry ? { expiresAt: new Date(softBanExpiry).toISOString() } : {}) }), children: "Apply soft ban" }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => { setShowAddSoftBan(false); setSoftBanReason(""); setSoftBanExpiry(""); }, children: "Cancel" })] })] })), softBans.length === 0 && !showAddSoftBan && (_jsx(Text, { size: "xs", color: "muted", children: "No active soft bans." }))] }));
42
42
  }
43
43
  // --- Component ---------------------------------------------------------------
44
- export function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }) {
44
+ export function AdminUserEditorView({ open, onClose, userId, displayName, currentRole, currentIsDisabled: _currentIsDisabled, currentEmailVerified, currentIsTester, ownedStoreId, ownedStoreName, currentSoftBans, currentIsHardBanned, currentHardBanReason, currentPhoneNumber, currentBio, currentLocation, currentWebsite, currentSocialLinks, }) {
45
45
  const queryClient = useQueryClient();
46
46
  const { showToast } = useToast();
47
47
  // --- General fields -------------------------------------------------------
48
48
  const [role, setRole] = React.useState(currentRole ?? "user");
49
49
  const [emailVerified, setEmailVerified] = React.useState(currentEmailVerified ?? false);
50
+ const [isTester, setIsTester] = React.useState(currentIsTester ?? false);
50
51
  const [adminNotes, setAdminNotes] = React.useState("");
51
52
  const [deleteOpen, setDeleteOpen] = React.useState(false);
52
53
  // --- ST-2 profile fields --------------------------------------------------
@@ -71,6 +72,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
71
72
  if (open) {
72
73
  setRole(currentRole ?? "user");
73
74
  setEmailVerified(currentEmailVerified ?? false);
75
+ setIsTester(currentIsTester ?? false);
74
76
  setAdminNotes("");
75
77
  setEditDisplayName(displayName ?? "");
76
78
  setPhoneNumber(currentPhoneNumber ?? "");
@@ -92,6 +94,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
92
94
  open,
93
95
  currentRole,
94
96
  currentEmailVerified,
97
+ currentIsTester,
95
98
  displayName,
96
99
  currentPhoneNumber,
97
100
  currentBio,
@@ -129,6 +132,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
129
132
  await apiClient.patch(ADMIN_ENDPOINTS.USER_BY_ID(userId), {
130
133
  role,
131
134
  emailVerified,
135
+ isTester,
132
136
  adminNotes: adminNotes || undefined,
133
137
  displayName: editDisplayName.trim() || undefined,
134
138
  phoneNumber: phoneNumber.trim() || undefined,
@@ -219,6 +223,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
219
223
  const renderInfoCard = () => userId ? (_jsx(Div, { textSize: "xs", surface: "muted", rounded: "lg", border: "default", padding: "inlineSm", children: _jsxs(Stack, { color: "primary", gap: "xs", children: [_jsxs(Text, { size: "xs", children: [_jsx(Span, { weight: "semibold", children: "Owner ID (Firebase UID):" }), " ", _jsx(Code, { className: "select-all font-mono", children: userId })] }), ownedStoreId && (_jsxs(Text, { size: "xs", children: [_jsx(Span, { weight: "semibold", children: "Owns store:" }), " ", _jsx(Code, { className: "select-all font-mono", children: ownedStoreId }), ownedStoreName ? ` — ${ownedStoreName}` : ""] }))] }) })) : null;
220
224
  const renderRoleSection = () => (_jsx(Select, { label: "Role", options: ROLE_OPTIONS, value: role, onValueChange: setRole }));
221
225
  const renderEmailVerifiedSection = () => (_jsx(Toggle, { label: "Email verified", checked: emailVerified, onChange: setEmailVerified }));
226
+ const renderIsTesterSection = () => (_jsx(Toggle, { label: "Is Tester", checked: isTester, onChange: setIsTester }));
222
227
  const renderProfileSection = () => (_jsxs(Stack, { border: "default", as: "section", gap: "sm", className: "border-t", padding: "t-sm", children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Profile details" }), _jsx(Input, { label: "Display name", value: editDisplayName, onChange: (e) => setEditDisplayName(e.target.value), placeholder: "Full name shown on profile" }), _jsx(Input, { label: "Phone number", type: "tel", value: phoneNumber, onChange: (e) => setPhoneNumber(e.target.value), placeholder: "+91 90000 00000" }), _jsx(Textarea, { label: "Bio", value: bio, onChange: (e) => setBio(e.target.value), rows: 3, placeholder: "Short bio shown on the public profile\u2026" }), _jsx(Input, { label: "Location", value: location, onChange: (e) => setLocation(e.target.value), placeholder: "City, Country" }), _jsx(Input, { label: "Website", type: "url", value: website, onChange: (e) => setWebsite(e.target.value), placeholder: "https://" }), _jsx(Text, { size: "xs", color: "muted", children: "Social links" }), _jsx(Input, { label: "Twitter / X", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "@handle or full URL" }), _jsx(Input, { label: "Instagram", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "@handle or full URL" }), _jsx(Input, { label: "Facebook", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/\u2026" }), _jsx(Input, { label: "LinkedIn", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/in/\u2026" })] }));
223
228
  const renderAdminNotesSection = () => (_jsx(Textarea, { label: "Admin notes (optional)", value: adminNotes, onChange: (e) => setAdminNotes(e.target.value), rows: 3, placeholder: "Internal notes about this user\u2026" }));
224
229
  const renderActionsSection = () => (_jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "danger", onClick: () => setDeleteOpen(true), disabled: !userId, children: "Delete user" }), _jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !userId || saveMutation.isPending, children: "Save changes" })] }));
@@ -230,6 +235,7 @@ export function AdminUserEditorView({ open, onClose, userId, displayName, curren
230
235
  renderInfoCard,
231
236
  renderRoleSection,
232
237
  renderEmailVerifiedSection,
238
+ renderIsTesterSection,
233
239
  renderProfileSection,
234
240
  renderAdminNotesSection,
235
241
  renderActionsSection,
@@ -214,7 +214,7 @@ export function AdminUsersView({ children, ...props }) {
214
214
  },
215
215
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_USER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Role", tabs: ADMIN_USER_ROLE_TABS, value: pendingFilters.role ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, role: id })) })] })),
216
216
  };
217
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
217
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminUserEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), userId: selectedRow?.id, displayName: selectedRow?.primary, currentRole: toStringValue(selectedRow?._raw?.role, "user"), currentEmailVerified: Boolean(selectedRow?._raw?.emailVerified), currentIsTester: Boolean(selectedRow?._raw?.isTester), ownedStoreId: toStringValue(selectedRow?._raw?.storeId, "") || undefined, ownedStoreName: toStringValue(selectedRow?._raw?.storeName, "") || undefined, currentIsHardBanned: Boolean((selectedRow?._raw?.isDisabled ?? selectedRow?._raw?.disabled) &&
218
218
  selectedRow?._raw?.hardBanReason), currentHardBanReason: toStringValue(selectedRow?._raw?.hardBanReason, "") || undefined, currentSoftBans: Array.isArray(selectedRow?._raw?.softBans)
219
219
  ? selectedRow._raw.softBans.map((b) => ({
220
220
  action: toStringValue(b.action, ""),
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Plus } from "lucide-react";
4
- import { BulkActionBar, Button, Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, SideDrawer } from "../../../ui";
4
+ import { BulkActionBar, Button, Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, SideDrawer, StickyToolbar } from "../../../ui";
5
5
  import { useBottomActions } from "../../layout";
6
6
  import { useAdminListing } from "../hooks/useAdminListing";
7
7
  import { AdminViewCards } from "./AdminViewCards";
@@ -44,7 +44,7 @@ export function DataListingView({ config, }) {
44
44
  const isEditorOpen = panel.isCreateOpen || panel.isEditOpen;
45
45
  return (_jsxs(Div, { className: config.className ?? "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: config.searchPlaceholder, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || config.defaultSort, sortOptions: config.sortOptions, onSortChange: (v) => table.set("sort", v), showTableView: !config.hideTableView, toggles: config.toggles, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: config.primaryAction || config.toolbarExtra ? (_jsxs(Row, { align: "center", gap: "sm", children: [config.toolbarExtra, config.primaryAction && (_jsxs(Button, { gap: "sm", size: "sm", onClick: () => config.primaryAction.onClick({
46
46
  openCreatePanel: panel.openCreatePanel,
47
- }), children: [config.primaryAction.icon ?? _jsx(Plus, { className: "h-4 w-4" }), config.primaryAction.label] }))] })) : undefined }), config.renderAboveContent?.(), bulkActionItems && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActionItems })), totalPages > 1 && (_jsx(Row, { border: "bottom", className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 backdrop-blur-sm", surface: "default", padding: "toolbar", justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p), pageSize: pageSize, onPageSizeChange: setPageSize, paginationConfig: { showPageSizeSelector: true, pageSizeOptions: [10, 25, 50, 100] } }) })), _jsxs(Div, { paddingX: "x-sm-md", padding: "y-md", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), view === "table" ? (_jsx(DataTable, { columns: config.columns, rows: rows, isLoading: isLoading, emptyLabel: config.emptyLabel ?? `No ${config.title.toLowerCase()} found`, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
47
+ }), children: [config.primaryAction.icon ?? _jsx(Plus, { className: "h-4 w-4" }), config.primaryAction.label] }))] })) : undefined }), config.renderAboveContent?.(), bulkActionItems && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActionItems })), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p), pageSize: pageSize, onPageSizeChange: setPageSize, paginationConfig: { showPageSizeSelector: true, pageSizeOptions: [10, 25, 50, 100] } }) }) })), _jsxs(Div, { paddingX: "x-sm-md", padding: "y-md", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), view === "table" ? (_jsx(DataTable, { columns: config.columns, rows: rows, isLoading: isLoading, emptyLabel: config.emptyLabel ?? `No ${config.title.toLowerCase()} found`, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
48
48
  ? selection.setSelectedIds(rows.map((r) => r.id))
49
49
  : selection.clearSelection(), getRowHref: config.getRowHref, onRowClick: config.onRowClick
50
50
  ? (row) => config.onRowClick(row, {
@@ -0,0 +1,8 @@
1
+ export interface AdminPageViewsReportViewProps {
2
+ startDate: string;
3
+ endDate: string;
4
+ }
5
+ /** Admin analytics tab — page views for the requested date range, grouped by
6
+ * entity (product/store/category/homepage/auction/review/user-profile) and
7
+ * by raw URL. Backed by the day-bucketed pageViews collection. */
8
+ export declare function AdminPageViewsReportView({ startDate, endDate }: AdminPageViewsReportViewProps): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { Alert, Div, Stack, Text } from "../../../../ui";
5
+ import { DataTable } from "../DataTable";
6
+ import { apiClient } from "../../../../http";
7
+ import { ADMIN_ENDPOINTS } from "../../../../constants/api-endpoints";
8
+ /** Admin analytics tab — page views for the requested date range, grouped by
9
+ * entity (product/store/category/homepage/auction/review/user-profile) and
10
+ * by raw URL. Backed by the day-bucketed pageViews collection. */
11
+ export function AdminPageViewsReportView({ startDate, endDate }) {
12
+ const endpoint = `${ADMIN_ENDPOINTS.ANALYTICS_PAGE_VIEWS}?startDate=${startDate}&endDate=${endDate}`;
13
+ const query = useQuery({
14
+ queryKey: ["admin-page-views", endpoint],
15
+ queryFn: () => apiClient.get(endpoint),
16
+ staleTime: 60000,
17
+ });
18
+ if (query.error) {
19
+ return (_jsx(Alert, { variant: "error", title: "Could not load page views", children: query.error instanceof Error ? query.error.message : "Unknown error" }));
20
+ }
21
+ const data = query.data;
22
+ return (_jsxs(Stack, { gap: "lg", children: [_jsx(Text, { size: "sm", color: "muted", children: data ? `${data.total.toLocaleString()} total views, ${startDate} – ${endDate}` : "Loading…" }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", className: "mb-2", children: "By entity" }), _jsx(DataTable, { columns: [
23
+ { key: "entityType", header: "Type" },
24
+ { key: "entityId", header: "Entity" },
25
+ { key: "url", header: "URL" },
26
+ { key: "count", header: "Views" },
27
+ ], rows: (data?.byEntity ?? []).map((row) => ({
28
+ id: `${row.entityType}:${row.entityId}`,
29
+ entityType: row.entityType,
30
+ entityId: row.entityId,
31
+ url: row.url,
32
+ count: row.count.toLocaleString(),
33
+ })), isLoading: query.isLoading, emptyLabel: "No page views recorded for this range." })] }), _jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", className: "mb-2", children: "By URL" }), _jsx(DataTable, { columns: [
34
+ { key: "url", header: "URL" },
35
+ { key: "count", header: "Views" },
36
+ ], rows: (data?.byUrl ?? []).map((row) => ({
37
+ id: row.url,
38
+ url: row.url,
39
+ count: row.count.toLocaleString(),
40
+ })), isLoading: query.isLoading, emptyLabel: "No page views recorded for this range." })] })] }));
41
+ }
@@ -65,6 +65,10 @@ export { AdminFaqsView } from "./AdminFaqsView";
65
65
  export type { AdminFaqsViewProps } from "./AdminFaqsView";
66
66
  export { AdminFaqEditorView } from "./AdminFaqEditorView";
67
67
  export type { AdminFaqEditorViewProps } from "./AdminFaqEditorView";
68
+ export { AdminTesterChecklistView } from "./AdminTesterChecklistView";
69
+ export type { AdminTesterChecklistViewProps } from "./AdminTesterChecklistView";
70
+ export { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
71
+ export type { AdminTesterChecklistItemEditorViewProps } from "./AdminTesterChecklistItemEditorView";
68
72
  export { AdminNavEditorView } from "./AdminNavEditorView";
69
73
  export type { AdminNavEditorViewProps, NavItemData } from "./AdminNavEditorView";
70
74
  export { AdminMediaView } from "./AdminMediaView";
@@ -127,6 +131,8 @@ export { AdminRevenueChart, AdminOrdersChart, } from "./analytics/AdminAnalytics
127
131
  export type { AdminRevenueChartProps, AdminOrdersChartProps, } from "./analytics/AdminAnalyticsCharts";
128
132
  export { AdminTopProductsTable } from "./analytics/AdminTopProductsTable";
129
133
  export type { AdminTopProductsTableProps, AdminTopProductsTableLabels, } from "./analytics/AdminTopProductsTable";
134
+ export { AdminPageViewsReportView } from "./analytics/AdminPageViewsReportView";
135
+ export type { AdminPageViewsReportViewProps } from "./analytics/AdminPageViewsReportView";
130
136
  export { AdminFeatureFlagsView } from "./AdminFeatureFlagsView";
131
137
  export type { AdminFeatureFlagsViewProps } from "./AdminFeatureFlagsView";
132
138
  export { AdminNewsletterView } from "./AdminNewsletterView";
@@ -37,6 +37,8 @@ export { AdminBlogView } from "./AdminBlogView";
37
37
  export { AdminBlogEditorView } from "./AdminBlogEditorView";
38
38
  export { AdminFaqsView } from "./AdminFaqsView";
39
39
  export { AdminFaqEditorView } from "./AdminFaqEditorView";
40
+ export { AdminTesterChecklistView } from "./AdminTesterChecklistView";
41
+ export { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
40
42
  export { AdminNavEditorView } from "./AdminNavEditorView";
41
43
  export { AdminMediaView } from "./AdminMediaView";
42
44
  export { AdminStoresView } from "./AdminStoresView";
@@ -68,6 +70,7 @@ export { AdminPageHeader } from "./AdminPageHeader";
68
70
  export { DrawerFormFooter } from "./DrawerFormFooter";
69
71
  export { AdminRevenueChart, AdminOrdersChart, } from "./analytics/AdminAnalyticsCharts";
70
72
  export { AdminTopProductsTable } from "./analytics/AdminTopProductsTable";
73
+ export { AdminPageViewsReportView } from "./analytics/AdminPageViewsReportView";
71
74
  export { AdminFeatureFlagsView } from "./AdminFeatureFlagsView";
72
75
  export { AdminNewsletterView } from "./AdminNewsletterView";
73
76
  export { AdminContactView } from "./AdminContactView";
@@ -382,15 +382,8 @@ export interface SiteSettingsDocument extends BaseDocument {
382
382
  notifications: boolean;
383
383
  sellerRegistration: boolean;
384
384
  preOrders: boolean;
385
- seedPanel: boolean;
386
385
  /** When true, admin users see a bypass button in checkout that skips OTP and payment. Server-enforced. */
387
386
  adminCheckoutBypass?: boolean;
388
- /**
389
- * Track H — when true and NODE_ENV !== "production", the payment provider
390
- * resolver returns MockRazorpayProvider. In production the resolver throws
391
- * if this is ever true. Default false.
392
- */
393
- useMockPayment?: boolean;
394
387
  listingTypes?: {
395
388
  standard?: boolean;
396
389
  auction?: boolean;
@@ -211,8 +211,12 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
211
211
  },
212
212
  featureFlags: {
213
213
  chats: true,
214
- smsVerification: true,
215
- translations: false,
214
+ // Real per-SMS cost via the Firebase phone-auth quota — kept off by
215
+ // default even though every other flag defaults on. Admin opts in
216
+ // explicitly; PATCH /api/site-settings resets all users' phoneVerified +
217
+ // rate-limit state on the false->true transition (resetOtpVerification job).
218
+ smsVerification: false,
219
+ translations: true,
216
220
  wishlists: true,
217
221
  auctions: true,
218
222
  reviews: true,
@@ -222,10 +226,6 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
222
226
  notifications: true,
223
227
  sellerRegistration: true,
224
228
  preOrders: true,
225
- seedPanel: true,
226
- // Track H — both mock-provider flags default to false. Production
227
- // deployments must never flip these on (the resolver throws if they are).
228
- useMockPayment: false,
229
229
  // W1-37 2026-05-23 — Phase 2 listing types enabled by default; all per-type
230
230
  // surfaces (seller + admin via W1-29 + public) are now shipped.
231
231
  listingTypes: {
@@ -0,0 +1,12 @@
1
+ import type { PageViewEntityType } from "../types";
2
+ export interface PageViewTrackerProps {
3
+ entityType: PageViewEntityType;
4
+ entityId: string;
5
+ /** Raw pathname to record alongside the entity — pass `usePathname()`'s value. */
6
+ url: string;
7
+ }
8
+ /** Drop into any public detail page (client or server component tree) to
9
+ * record one view on mount. Fire-and-forget — a failed ping never surfaces
10
+ * to the visitor. Fires once per entityId/url pair per mount, not on every
11
+ * re-render. */
12
+ export declare function PageViewTracker({ entityType, entityId, url }: PageViewTrackerProps): null;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ import { apiClient } from "../../../http";
4
+ import { ANALYTICS_ENDPOINTS } from "../../../constants/api-endpoints";
5
+ import { normalizeError } from "../../../errors/normalize";
6
+ /** Drop into any public detail page (client or server component tree) to
7
+ * record one view on mount. Fire-and-forget — a failed ping never surfaces
8
+ * to the visitor. Fires once per entityId/url pair per mount, not on every
9
+ * re-render. */
10
+ export function PageViewTracker({ entityType, entityId, url }) {
11
+ const firedRef = useRef(null);
12
+ useEffect(() => {
13
+ const key = `${entityType}:${entityId}:${url}`;
14
+ if (firedRef.current === key)
15
+ return;
16
+ firedRef.current = key;
17
+ apiClient.post(ANALYTICS_ENDPOINTS.PAGE_VIEW, { entityType, entityId, url }).catch((err) => {
18
+ void normalizeError(err); // instrumentation-only — never surfaced to the visitor
19
+ });
20
+ }, [entityType, entityId, url]);
21
+ return null;
22
+ }
@@ -0,0 +1,32 @@
1
+ import { BaseRepository } from "../../../providers/db-firebase";
2
+ import { PAGE_VIEW_ENTITY_TYPES, type PageViewEntityType } from "../types";
3
+ export { PAGE_VIEW_ENTITY_TYPES };
4
+ export type { PageViewEntityType };
5
+ export interface PageViewDocument {
6
+ /** `{date}_{entityType}_{entityId}` — one doc per entity per day. */
7
+ id: string;
8
+ /** YYYY-MM-DD, server date at record time. */
9
+ date: string;
10
+ entityType: PageViewEntityType;
11
+ entityId: string;
12
+ /** Raw pathname (e.g. "/products/product-charizard-psa9") — always recorded
13
+ * alongside the entity breakdown so the admin report can also group by URL. */
14
+ url: string;
15
+ count: number;
16
+ updatedAt: Date;
17
+ }
18
+ export interface RecordPageViewInput {
19
+ entityType: PageViewEntityType;
20
+ entityId: string;
21
+ url: string;
22
+ }
23
+ export declare class PageViewsRepository extends BaseRepository<PageViewDocument> {
24
+ constructor();
25
+ /** Fire-and-forget increment — one write per view, day-bucketed so the
26
+ * admin report can filter by date range without storing per-event rows. */
27
+ recordView({ entityType, entityId, url }: RecordPageViewInput): Promise<void>;
28
+ /** All day-buckets in [startDate, endDate] (inclusive, YYYY-MM-DD), optionally
29
+ * scoped to one entityType. Used by the admin page-views report tab. */
30
+ listInRange(startDate: string, endDate: string, entityType?: PageViewEntityType): Promise<PageViewDocument[]>;
31
+ }
32
+ export declare const pageViewsRepository: PageViewsRepository;
@@ -0,0 +1,43 @@
1
+ import { FieldValue } from "firebase-admin/firestore";
2
+ import { BaseRepository } from "../../../providers/db-firebase";
3
+ import { PAGE_VIEW_ENTITY_TYPES } from "../types";
4
+ import { PAGE_VIEW_FIELDS } from "../../../constants/field-names";
5
+ export { PAGE_VIEW_ENTITY_TYPES };
6
+ function todayDateKey() {
7
+ return new Date().toISOString().slice(0, 10);
8
+ }
9
+ function docId(date, entityType, entityId) {
10
+ return `${date}_${entityType}_${entityId}`;
11
+ }
12
+ export class PageViewsRepository extends BaseRepository {
13
+ constructor() {
14
+ super("pageViews");
15
+ }
16
+ /** Fire-and-forget increment — one write per view, day-bucketed so the
17
+ * admin report can filter by date range without storing per-event rows. */
18
+ async recordView({ entityType, entityId, url }) {
19
+ const date = todayDateKey();
20
+ const id = docId(date, entityType, entityId);
21
+ await this.getCollection().doc(id).set({
22
+ date,
23
+ entityType,
24
+ entityId,
25
+ url,
26
+ count: FieldValue.increment(1),
27
+ updatedAt: new Date(),
28
+ }, { merge: true });
29
+ }
30
+ /** All day-buckets in [startDate, endDate] (inclusive, YYYY-MM-DD), optionally
31
+ * scoped to one entityType. Used by the admin page-views report tab. */
32
+ async listInRange(startDate, endDate, entityType) {
33
+ let query = this.getCollection()
34
+ .where(PAGE_VIEW_FIELDS.DATE, ">=", startDate)
35
+ .where(PAGE_VIEW_FIELDS.DATE, "<=", endDate);
36
+ if (entityType) {
37
+ query = query.where(PAGE_VIEW_FIELDS.ENTITY_TYPE, "==", entityType);
38
+ }
39
+ const snap = await query.get();
40
+ return snap.docs.map((d) => this.mapDoc(d));
41
+ }
42
+ }
43
+ export const pageViewsRepository = new PageViewsRepository();
@@ -0,0 +1,6 @@
1
+ /** Shared client+server type for page-view tracking — kept out of
2
+ * page-views.repository.ts (server-only, imports firebase-admin) so the
3
+ * client-side tracker component can import just this without pulling
4
+ * firebase-admin into the client bundle. */
5
+ export declare const PAGE_VIEW_ENTITY_TYPES: readonly ["product", "store", "category", "homepage", "auction", "review", "user-profile"];
6
+ export type PageViewEntityType = (typeof PAGE_VIEW_ENTITY_TYPES)[number];
@@ -0,0 +1,13 @@
1
+ /** Shared client+server type for page-view tracking — kept out of
2
+ * page-views.repository.ts (server-only, imports firebase-admin) so the
3
+ * client-side tracker component can import just this without pulling
4
+ * firebase-admin into the client bundle. */
5
+ export const PAGE_VIEW_ENTITY_TYPES = [
6
+ "product",
7
+ "store",
8
+ "category",
9
+ "homepage",
10
+ "auction",
11
+ "review",
12
+ "user-profile",
13
+ ];
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Button, Div, Row, Span } from "../../../ui";
3
3
  export function SocialAuthButtons({ onGoogle, disabled, dividerLabel, googleLabel, }) {
4
- return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "relative", children: [_jsx(Row, { className: "absolute inset-0", align: "center", children: _jsx(Div, { className: "w-full border-t border-neutral-200" }) }), _jsx(Row, { textSize: "sm", className: "relative", justify: "center", children: _jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", padding: "x-xs", surface: "default", children: dividerLabel }) })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1", children: _jsxs(Button, { rounded: "md", type: "button", onClick: onGoogle, disabled: disabled, variant: "outline", paddingX: "md", paddingY: "sm", textSize: "sm", weight: "medium", className: "inline- justify-[center] border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)] dark:text-[var(--appkit-color-text)] hover:bg-neutral-50 hover:bg-[var(--appkit-color-surface-elevated)] disabled:cursor-not-allowed disabled:opacity-50", children: [_jsxs("svg", { className: "mr-2 h-5 w-5", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { fill: "currentColor", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "currentColor", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "currentColor", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "currentColor", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] }), googleLabel] }) })] }));
4
+ return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "relative", children: [_jsx(Row, { className: "absolute inset-0", align: "center", children: _jsx(Div, { className: "w-full border-t border-neutral-200" }) }), _jsx(Row, { textSize: "sm", className: "relative", justify: "center", children: _jsx(Span, { padding: "x-xs", surface: "default", color: "muted", children: dividerLabel }) })] }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1", children: _jsxs(Button, { rounded: "md", type: "button", onClick: onGoogle, disabled: disabled, variant: "outline", paddingX: "md", paddingY: "sm", textSize: "sm", weight: "medium", className: "inline- justify-[center] border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)] dark:text-[var(--appkit-color-text)] hover:bg-neutral-50 hover:bg-[var(--appkit-color-surface-elevated)] disabled:cursor-not-allowed disabled:opacity-50", children: [_jsxs("svg", { className: "mr-2 h-5 w-5", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { fill: "currentColor", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), _jsx("path", { fill: "currentColor", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), _jsx("path", { fill: "currentColor", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" }), _jsx("path", { fill: "currentColor", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" })] }), googleLabel] }) })] }));
5
5
  }
@@ -58,6 +58,18 @@ export declare function useGoogleLogin(options?: {
58
58
  mutate: () => Promise<void>;
59
59
  isLoading: boolean;
60
60
  };
61
+ /** Connects the current (already-logged-in) user's account to a Google account,
62
+ * without switching sessions. Reuses the same popup + RTDB/postMessage signal
63
+ * mechanism as useGoogleLogin, but POSTs { mode: "link" } to EVENT_INIT so the
64
+ * server callback records the link on the caller's own profile instead of
65
+ * resolving/creating a separate Firebase Auth user. */
66
+ export declare function useLinkGoogleAccount(options?: {
67
+ onSuccess?: () => void;
68
+ onError?: (error: Error) => void;
69
+ }): {
70
+ mutate: () => Promise<void>;
71
+ isLoading: boolean;
72
+ };
61
73
  export declare function useRegister(options?: {
62
74
  onSuccess?: (data: JsonValue) => void;
63
75
  onError?: (error: Error) => void;