@mohasinac/appkit 3.6.5 → 3.7.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 (276) 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/faqs/og.js +5 -1
  7. package/dist/_internal/server/features/reviews/og.js +5 -1
  8. package/dist/_internal/server/features/seo/manifest.js +2 -1
  9. package/dist/_internal/server/features/seo/og.js +7 -1
  10. package/dist/_internal/server/features/tester/visibility.d.ts +13 -0
  11. package/dist/_internal/server/features/tester/visibility.js +28 -0
  12. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  13. package/dist/_internal/server/functions/scheduled.js +9 -1
  14. package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
  15. package/dist/_internal/server/jobs/core/resetOtpVerification.d.ts +17 -0
  16. package/dist/_internal/server/jobs/core/resetOtpVerification.js +88 -0
  17. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +6 -0
  18. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +35 -0
  19. package/dist/_internal/server/jobs/handlers/_helpers.d.ts +6 -0
  20. package/dist/_internal/server/jobs/handlers/_helpers.js +12 -0
  21. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  22. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  23. package/dist/_internal/server/jobs/handlers/messages.d.ts +2 -0
  24. package/dist/_internal/server/jobs/handlers/messages.js +2 -0
  25. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +2 -0
  26. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +2 -0
  27. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  28. package/dist/_internal/shared/actions/action-registry.js +48 -0
  29. package/dist/_internal/shared/tokens/index.d.ts +12 -12
  30. package/dist/client.d.ts +15 -3
  31. package/dist/client.js +10 -2
  32. package/dist/constants/api-endpoints.d.ts +36 -12
  33. package/dist/constants/api-endpoints.js +16 -8
  34. package/dist/constants/field-names.d.ts +9 -0
  35. package/dist/constants/field-names.js +12 -0
  36. package/dist/constants/index.d.ts +1 -1
  37. package/dist/constants/index.js +1 -1
  38. package/dist/features/account/components/LinkedAccountsSection.d.ts +11 -0
  39. package/dist/features/account/components/LinkedAccountsSection.js +11 -0
  40. package/dist/features/account/components/UserSidebar.d.ts +3 -1
  41. package/dist/features/account/components/UserSidebar.js +9 -13
  42. package/dist/features/account/hooks/useCollapsedSections.d.ts +20 -0
  43. package/dist/features/account/hooks/useCollapsedSections.js +63 -0
  44. package/dist/features/account/hooks/useProfile.d.ts +8 -0
  45. package/dist/features/account/index.d.ts +1 -0
  46. package/dist/features/account/index.js +1 -0
  47. package/dist/features/admin/components/AdminAddressEditorView.js +2 -2
  48. package/dist/features/admin/components/AdminCarouselView.js +2 -2
  49. package/dist/features/admin/components/AdminCategoryEditorView.js +2 -2
  50. package/dist/features/admin/components/AdminProductEditorView.js +5 -2
  51. package/dist/features/admin/components/AdminSectionsView.js +3 -18
  52. package/dist/features/admin/components/AdminSidebar.js +8 -6
  53. package/dist/features/admin/components/AdminSiteSettingsView.js +133 -127
  54. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.d.ts +9 -0
  55. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +129 -0
  56. package/dist/features/admin/components/AdminTesterChecklistView.d.ts +6 -0
  57. package/dist/features/admin/components/AdminTesterChecklistView.js +93 -0
  58. package/dist/features/admin/components/AdminUserEditorView.d.ts +3 -1
  59. package/dist/features/admin/components/AdminUserEditorView.js +7 -1
  60. package/dist/features/admin/components/AdminUsersView.js +1 -1
  61. package/dist/features/admin/components/DataListingView.js +2 -2
  62. package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +8 -0
  63. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +41 -0
  64. package/dist/features/admin/components/index.d.ts +6 -0
  65. package/dist/features/admin/components/index.js +3 -0
  66. package/dist/features/admin/schemas/firestore.d.ts +0 -7
  67. package/dist/features/admin/schemas/firestore.js +6 -6
  68. package/dist/features/analytics/components/PageViewTracker.d.ts +12 -0
  69. package/dist/features/analytics/components/PageViewTracker.js +22 -0
  70. package/dist/features/analytics/repository/page-views.repository.d.ts +32 -0
  71. package/dist/features/analytics/repository/page-views.repository.js +43 -0
  72. package/dist/features/analytics/types.d.ts +6 -0
  73. package/dist/features/analytics/types.js +13 -0
  74. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  75. package/dist/features/auth/hooks/useAuth.d.ts +12 -0
  76. package/dist/features/auth/hooks/useAuth.js +109 -0
  77. package/dist/features/auth/permissions/constants.d.ts +1 -1
  78. package/dist/features/auth/schemas/firestore.d.ts +17 -1
  79. package/dist/features/auth/schemas/firestore.js +2 -0
  80. package/dist/features/blog/components/BlogIndexListing.js +2 -2
  81. package/dist/features/blog/schemas/firestore.d.ts +4 -0
  82. package/dist/features/cart/actions/cart-actions.js +20 -2
  83. package/dist/features/categories/components/CategoriesIndexListing.js +2 -2
  84. package/dist/features/categories/components/CategoryBundlesListing.js +2 -2
  85. package/dist/features/categories/components/CategoryDetailPageView.js +3 -1
  86. package/dist/features/categories/components/CategoryProductsListing.js +2 -2
  87. package/dist/features/categories/schemas/firestore.d.ts +4 -0
  88. package/dist/features/classified/components/ClassifiedIndexListing.js +2 -2
  89. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +2 -2
  90. package/dist/features/email/primitives.d.ts +2 -2
  91. package/dist/features/email/primitives.js +2 -2
  92. package/dist/features/events/components/EventsIndexListing.js +2 -2
  93. package/dist/features/events/schemas/firestore.d.ts +4 -0
  94. package/dist/features/layout/BackToTop.d.ts +6 -1
  95. package/dist/features/layout/BackToTop.js +31 -6
  96. package/dist/features/live/components/LiveItemsIndexListing.js +2 -2
  97. package/dist/features/media/MediaVideo.js +5 -1
  98. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +2 -2
  99. package/dist/features/products/components/AuctionsIndexListing.js +2 -2
  100. package/dist/features/products/components/PrizeDrawsIndexListing.js +2 -2
  101. package/dist/features/products/components/ProductsIndexListing.js +2 -2
  102. package/dist/features/products/constants/action-defs.d.ts +2 -0
  103. package/dist/features/products/constants/action-defs.js +2 -0
  104. package/dist/features/products/schemas/firestore.d.ts +4 -0
  105. package/dist/features/promotions/components/CouponsIndexListing.js +2 -2
  106. package/dist/features/reviews/components/ReviewsIndexListing.js +2 -2
  107. package/dist/features/seller/actions/seller-actions.js +11 -6
  108. package/dist/features/seller/components/SellerAddressesView.js +2 -2
  109. package/dist/features/seller/components/SellerBidsView.js +2 -2
  110. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  111. package/dist/features/seller/components/SellerProductsView.js +2 -2
  112. package/dist/features/seller/components/SellerSidebar.d.ts +3 -1
  113. package/dist/features/seller/components/SellerSidebar.js +9 -8
  114. package/dist/features/seller/components/SellerStoreCategoriesView.js +2 -2
  115. package/dist/features/seller/components/SellerTemplatesView.js +2 -2
  116. package/dist/features/shell/StepForm.js +4 -4
  117. package/dist/features/stores/actions/store-query-actions.d.ts +5 -4
  118. package/dist/features/stores/actions/store-query-actions.js +11 -8
  119. package/dist/features/stores/components/StoreAuctionsListing.js +2 -2
  120. package/dist/features/stores/components/StoreClassifiedsListing.js +2 -2
  121. package/dist/features/stores/components/StoreDigitalCodesListing.js +2 -2
  122. package/dist/features/stores/components/StoreLiveItemsListing.js +2 -2
  123. package/dist/features/stores/components/StorePreOrdersListing.js +2 -2
  124. package/dist/features/stores/components/StoreProductsListing.js +2 -2
  125. package/dist/features/stores/components/StoreReviewsListing.js +2 -2
  126. package/dist/features/stores/components/StoresIndexListing.js +2 -2
  127. package/dist/features/stores/schemas/firestore.d.ts +4 -0
  128. package/dist/features/tester/actions/checklist-item-actions.d.ts +69 -0
  129. package/dist/features/tester/actions/checklist-item-actions.js +42 -0
  130. package/dist/features/tester/actions/index.d.ts +1 -0
  131. package/dist/features/tester/actions/index.js +1 -0
  132. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.d.ts +2 -0
  133. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +22 -0
  134. package/dist/features/tester/components/AdminTesterFeedbackListView.d.ts +3 -0
  135. package/dist/features/tester/components/AdminTesterFeedbackListView.js +100 -0
  136. package/dist/features/tester/components/AdminTesterFeedbackReportView.d.ts +2 -0
  137. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +30 -0
  138. package/dist/features/tester/components/AdminTesterFeedbackView.d.ts +2 -0
  139. package/dist/features/tester/components/AdminTesterFeedbackView.js +9 -0
  140. package/dist/features/tester/components/TesterChecklistStepRow.d.ts +18 -0
  141. package/dist/features/tester/components/TesterChecklistStepRow.js +29 -0
  142. package/dist/features/tester/components/TesterFeedbackChart.d.ts +14 -0
  143. package/dist/features/tester/components/TesterFeedbackChart.js +18 -0
  144. package/dist/features/tester/components/TesterHubView.d.ts +5 -0
  145. package/dist/features/tester/components/TesterHubView.js +71 -0
  146. package/dist/features/tester/components/index.d.ts +11 -0
  147. package/dist/features/tester/components/index.js +7 -0
  148. package/dist/features/tester/index.d.ts +2 -0
  149. package/dist/features/tester/index.js +2 -0
  150. package/dist/features/tester/repository/index.d.ts +2 -0
  151. package/dist/features/tester/repository/index.js +2 -0
  152. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +13 -0
  153. package/dist/features/tester/repository/tester-checklist-item.repository.js +55 -0
  154. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +42 -0
  155. package/dist/features/tester/repository/tester-checklist-response.repository.js +98 -0
  156. package/dist/features/tester/schemas/firestore.d.ts +85 -0
  157. package/dist/features/tester/schemas/firestore.js +93 -0
  158. package/dist/features/tester/schemas/index.d.ts +1 -0
  159. package/dist/features/tester/schemas/index.js +1 -0
  160. package/dist/features/tester/seed-data/blog-tester-seed-data.d.ts +2 -0
  161. package/dist/features/tester/seed-data/blog-tester-seed-data.js +40 -0
  162. package/dist/features/tester/seed-data/categories-tester-seed-data.d.ts +2 -0
  163. package/dist/features/tester/seed-data/categories-tester-seed-data.js +177 -0
  164. package/dist/features/tester/seed-data/events-tester-seed-data.d.ts +2 -0
  165. package/dist/features/tester/seed-data/events-tester-seed-data.js +50 -0
  166. package/dist/features/tester/seed-data/index.d.ts +6 -0
  167. package/dist/features/tester/seed-data/index.js +6 -0
  168. package/dist/features/tester/seed-data/products-tester-seed-data.d.ts +2 -0
  169. package/dist/features/tester/seed-data/products-tester-seed-data.js +151 -0
  170. package/dist/features/tester/seed-data/stores-tester-seed-data.d.ts +2 -0
  171. package/dist/features/tester/seed-data/stores-tester-seed-data.js +40 -0
  172. package/dist/features/tester/seed-data/tester-checklist-seed-data.d.ts +2 -0
  173. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +248 -0
  174. package/dist/features/tester/seed-data/tester-ttl.d.ts +4 -0
  175. package/dist/features/tester/seed-data/tester-ttl.js +6 -0
  176. package/dist/features/tester/server.d.ts +9 -0
  177. package/dist/features/tester/server.js +8 -0
  178. package/dist/index.d.ts +28 -2
  179. package/dist/index.js +51 -1
  180. package/dist/next/api/api-response.js +5 -1
  181. package/dist/next/api/routeHandler.js +10 -1
  182. package/dist/next/middleware/chain.js +1 -1
  183. package/dist/next/routing/route-map.d.ts +6 -7
  184. package/dist/next/routing/route-map.js +3 -7
  185. package/dist/providers/payment-manual/index.d.ts +2 -2
  186. package/dist/providers/payment-manual/index.js +2 -2
  187. package/dist/react/contexts/SessionContext.d.ts +4 -0
  188. package/dist/react/contexts/SessionContext.js +4 -0
  189. package/dist/repositories/index.d.ts +4 -0
  190. package/dist/repositories/index.js +3 -0
  191. package/dist/security/pii-encrypt.js +12 -2
  192. package/dist/seed/addresses-seed-data.js +1 -1
  193. package/dist/seed/bids-seed-data.js +1 -1
  194. package/dist/seed/blog-posts-seed-data.js +1 -1
  195. package/dist/seed/carousel-slides-seed-data.js +22 -16
  196. package/dist/seed/carousels-seed-data.js +1 -1
  197. package/dist/seed/cart-seed-data.js +1 -1
  198. package/dist/seed/categories-seed-data.js +1 -1
  199. package/dist/seed/claimed-coupons-seed-data.js +1 -1
  200. package/dist/seed/conversations-seed-data.js +1 -1
  201. package/dist/seed/coupon-usage-seed-data.js +1 -1
  202. package/dist/seed/coupons-seed-data.js +1 -1
  203. package/dist/seed/events-seed-data.js +1 -1
  204. package/dist/seed/faq-seed-data.js +119 -3
  205. package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
  206. package/dist/seed/grouped-listings-seed-data.js +1 -1
  207. package/dist/seed/history-seed-data.js +1 -1
  208. package/dist/seed/homepage-sections-seed-data.js +1 -1
  209. package/dist/seed/index.d.ts +2 -2
  210. package/dist/seed/index.js +7 -3
  211. package/dist/seed/lottery-entries-seed-data.js +1 -1
  212. package/dist/seed/manifest.d.ts +4 -3
  213. package/dist/seed/manifest.js +13 -6
  214. package/dist/seed/notifications-seed-data.js +1 -1
  215. package/dist/seed/offers-seed-data.js +1 -1
  216. package/dist/seed/orders-seed-data.js +1 -1
  217. package/dist/seed/payouts-seed-data.js +1 -1
  218. package/dist/seed/products-art-seed-data.js +1 -1
  219. package/dist/seed/products-auctions-seed-data.js +1 -1
  220. package/dist/seed/products-classifieds-seed-data.js +1 -1
  221. package/dist/seed/products-digital-codes-seed-data.js +1 -1
  222. package/dist/seed/products-live-items-seed-data.js +1 -1
  223. package/dist/seed/products-preorders-seed-data.d.ts +2 -0
  224. package/dist/seed/products-preorders-seed-data.js +1 -1
  225. package/dist/seed/products-prize-draws-seed-data.js +1 -1
  226. package/dist/seed/products-standard-seed-data.js +1 -1
  227. package/dist/seed/products-stickers-seed-data.js +1 -1
  228. package/dist/seed/reviews-seed-data.js +1 -1
  229. package/dist/seed/scammers-seed-data.js +1 -1
  230. package/dist/seed/sessions-seed-data.js +1 -1
  231. package/dist/seed/site-settings-seed-data.js +22 -16
  232. package/dist/seed/store-addresses-seed-data.js +1 -1
  233. package/dist/seed/store-extensions-seed-data.js +1 -1
  234. package/dist/seed/stores-seed-data.js +1 -1
  235. package/dist/seed/support-tickets-seed-data.js +1 -1
  236. package/dist/seed/tester-checklist-seed-data.d.ts +2 -0
  237. package/dist/seed/tester-checklist-seed-data.js +248 -0
  238. package/dist/seed/users-seed-data.js +1 -1
  239. package/dist/seed/wishlists-seed-data.js +1 -1
  240. package/dist/server.d.ts +7 -2
  241. package/dist/server.js +19 -12
  242. package/dist/styles.css +163 -147
  243. package/dist/tailwind-utilities.css +1 -1
  244. package/dist/tokens/color-pairs.d.ts +46 -0
  245. package/dist/tokens/color-pairs.js +93 -0
  246. package/dist/tokens/index.d.ts +60 -60
  247. package/dist/tokens/index.js +30 -30
  248. package/dist/tokens/themes/cobalt-night.d.ts +3 -2
  249. package/dist/tokens/themes/cobalt-night.js +3 -2
  250. package/dist/tokens/themes/default-dark.d.ts +4 -1
  251. package/dist/tokens/themes/default-dark.js +44 -41
  252. package/dist/tokens/themes/default-light.d.ts +5 -1
  253. package/dist/tokens/themes/default-light.js +39 -35
  254. package/dist/tokens/themes/sunset.js +4 -4
  255. package/dist/tokens/tokens.css +110 -104
  256. package/dist/ui/components/CollapsibleSection.d.ts +15 -0
  257. package/dist/ui/components/CollapsibleSection.js +13 -0
  258. package/dist/ui/components/ListingToolbar.js +2 -1
  259. package/dist/ui/components/Semantic.style.css +3 -3
  260. package/dist/ui/components/SideDrawer.style.css +19 -19
  261. package/dist/ui/components/Slider.js +1 -1
  262. package/dist/ui/components/StickyToolbar.d.ts +36 -7
  263. package/dist/ui/components/StickyToolbar.js +63 -5
  264. package/dist/ui/components/Tabs.js +17 -1
  265. package/dist/ui/components/Tabs.style.css +10 -0
  266. package/dist/ui/components/Typography.js +7 -0
  267. package/dist/ui/components/surface-tokens.d.ts +0 -7
  268. package/dist/ui/components/surface-tokens.js +7 -0
  269. package/dist/ui/index.d.ts +6 -0
  270. package/dist/ui/index.js +3 -0
  271. package/dist/ui/rich-text/RichText.style.css +20 -20
  272. package/dist/utils/id-generators.d.ts +13 -0
  273. package/dist/utils/id-generators.js +17 -0
  274. package/package.json +1 -1
  275. package/scripts/seed-cli-loader.mjs +15 -0
  276. package/scripts/seed-cli.mjs +71 -17
@@ -19,11 +19,11 @@ export function StepIndicator({ steps, currentStep, onStepClick, stepErrors, })
19
19
  const isDone = i < currentStep;
20
20
  const isActive = i === currentStep;
21
21
  const hasError = stepErrors?.[i] === true;
22
- return (_jsxs(Row, { className: "flex-shrink-0", align: "center", gap: "none", children: [_jsxs(Button, { variant: "ghost", type: "button", disabled: !isDone && !isActive, onClick: () => isDone && onStepClick?.(i), gap: "sm", rounded: "lg", paddingX: "sm", paddingY: "xs", textSize: "sm", className: classNames(isActive
22
+ return (_jsxs(Row, { className: "flex-shrink-0", align: "center", gap: "none", children: [_jsxs(Button, { variant: "ghost", type: "button", onClick: () => onStepClick?.(i), gap: "sm", rounded: "lg", paddingX: "sm", paddingY: "xs", textSize: "sm", className: classNames("cursor-pointer", isActive
23
23
  ? "font-semibold text-[var(--appkit-color-primary)]"
24
24
  : isDone
25
- ? "text-[var(--appkit-color-text-muted)] cursor-pointer hover:text-[var(--appkit-color-text)]"
26
- : "text-[var(--appkit-color-text-faint)] cursor-default"), children: [_jsxs(Span, { className: "relative flex-shrink-0", children: [hasError && (_jsx(Span, { className: "absolute -top-0.5 -right-0.5 w-2 h-2 bg-[var(--appkit-color-error)] z-10", rounded: "full", "aria-label": "This step has errors" })), _jsx(Span, { className: classNames("flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-[length:var(--appkit-text-xs)] font-bold border-2", hasError
25
+ ? "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]"
26
+ : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"), children: [_jsxs(Span, { className: "relative flex-shrink-0", children: [hasError && (_jsx(Span, { className: "absolute -top-0.5 -right-0.5 w-2 h-2 bg-[var(--appkit-color-error)] z-10", rounded: "full", "aria-label": "This step has errors" })), _jsx(Span, { className: classNames("flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-[length:var(--appkit-text-xs)] font-bold border-2", hasError
27
27
  ? "border-[var(--appkit-color-error)] bg-[var(--appkit-color-error)] text-white"
28
28
  : isActive
29
29
  ? "border-[var(--appkit-color-primary)] bg-[var(--appkit-color-primary)] text-white"
@@ -86,7 +86,7 @@ export function StepForm({ steps, values, onChange, onComplete, completeLabel =
86
86
  }, [currentStep, onStepChange]);
87
87
  const currentStepDef = steps[currentStep];
88
88
  const isLast = currentStep === steps.length - 1;
89
- return (_jsxs(Stack, { children: [_jsx(StepIndicator, { steps: steps, currentStep: currentStep, stepErrors: stepErrors, onStepClick: (i) => { if (i < currentStep) {
89
+ return (_jsxs(Stack, { children: [_jsx(StepIndicator, { steps: steps, currentStep: currentStep, stepErrors: stepErrors, onStepClick: (i) => { if (i !== currentStep) {
90
90
  setStepError(null);
91
91
  onStepChange(i);
92
92
  } } }), _jsx(Div, { className: "flex-1", children: currentStepDef?.render({ values, onChange, errors: fieldErrors }) }), !hideActions && stepError && (_jsx(Text, { className: "mt-3 text-[var(--appkit-color-error)]", size: "sm", children: stepError })), !hideActions && (_jsx(StepFormActions, { currentStep: currentStep, totalSteps: steps.length, onNext: () => void goNext(), onPrev: currentStep > 0 ? goPrev : undefined, completeLabel: completeLabel, isLoading: isLoading && isLast, disabled: isLoading }))] }));
@@ -4,6 +4,7 @@
4
4
  * Public and seller-facing read functions for store listings, products,
5
5
  * auctions and reviews. No auth — callers decide whether auth is needed.
6
6
  */
7
+ import { type ViewerLike } from "../../../_internal/server/features/tester/visibility";
7
8
  import type { StoreDocument } from "../schemas";
8
9
  import type { ProductDocument } from "../../products/schemas";
9
10
  import type { ReviewDocument } from "../../reviews/schemas";
@@ -27,8 +28,8 @@ export interface StoreReviewsResult {
27
28
  totalReviews: number;
28
29
  ratingDistribution: Record<number, number>;
29
30
  }
30
- export declare function listStores(params?: StoreQueryListParams): Promise<FirebaseSieveResult<StoreDocument>>;
31
- export declare function getStoreBySlug(storeSlug: string): Promise<StoreDocument | null>;
32
- export declare function getStoreProducts(storeSlug: string, params?: StoreContentParams): Promise<FirebaseSieveResult<ProductDocument>>;
33
- export declare function getStoreAuctions(storeSlug: string, params?: StoreContentParams): Promise<FirebaseSieveResult<ProductDocument>>;
31
+ export declare function listStores(params?: StoreQueryListParams, viewer?: ViewerLike | null): Promise<FirebaseSieveResult<StoreDocument>>;
32
+ export declare function getStoreBySlug(storeSlug: string, viewer?: ViewerLike | null): Promise<StoreDocument | null>;
33
+ export declare function getStoreProducts(storeSlug: string, params?: StoreContentParams, viewer?: ViewerLike | null): Promise<FirebaseSieveResult<ProductDocument>>;
34
+ export declare function getStoreAuctions(storeSlug: string, params?: StoreContentParams, viewer?: ViewerLike | null): Promise<FirebaseSieveResult<ProductDocument>>;
34
35
  export declare function getStoreReviews(storeSlug: string): Promise<StoreReviewsResult>;
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
8
8
  import { NotFoundError } from "../../../errors";
9
+ import { filterTestDataForViewer, filterSingleTestData } from "../../../_internal/server/features/tester/visibility";
9
10
  const ERR_STORE_NOT_FOUND = "Store not found";
10
11
  import { maskPublicReview } from "../../../security";
11
12
  import { storeRepository } from "../repository/store.repository";
@@ -13,7 +14,7 @@ import { STORE_FIELDS } from "../schemas";
13
14
  import { productRepository } from "../../products/repository/products.repository";
14
15
  import { ProductStatusValues } from "../../products/schemas";
15
16
  import { reviewRepository } from "../../reviews/repository/reviews.repository";
16
- export async function listStores(params = {}) {
17
+ export async function listStores(params = {}, viewer) {
17
18
  const { filters, sorts = "-createdAt", page = 1, pageSize = 24, q } = params;
18
19
  const model = { filters, sorts, page, pageSize };
19
20
  const filtersArr = [];
@@ -22,14 +23,16 @@ export async function listStores(params = {}) {
22
23
  if (filters)
23
24
  filtersArr.push(filters);
24
25
  model.filters = filtersArr.join(",") || undefined;
25
- return storeRepository.listStores(model);
26
+ const result = await storeRepository.listStores(model);
27
+ return { ...result, items: filterTestDataForViewer(result.items, viewer) };
26
28
  }
27
- export async function getStoreBySlug(storeSlug) {
28
- return storeRepository.findBySlug(storeSlug);
29
+ export async function getStoreBySlug(storeSlug, viewer) {
30
+ const store = await storeRepository.findBySlug(storeSlug);
31
+ return filterSingleTestData(store, viewer);
29
32
  }
30
- export async function getStoreProducts(storeSlug, params = {}) {
33
+ export async function getStoreProducts(storeSlug, params = {}, viewer) {
31
34
  const { sorts = "-createdAt", page = 1, pageSize = 24, filters } = params;
32
- const storeDoc = await storeRepository.findBySlug(storeSlug);
35
+ const storeDoc = await filterSingleTestData(await storeRepository.findBySlug(storeSlug), viewer);
33
36
  if (!storeDoc ||
34
37
  storeDoc.status !== STORE_FIELDS.STATUS_VALUES.ACTIVE ||
35
38
  !storeDoc.isPublic) {
@@ -49,9 +52,9 @@ export async function getStoreProducts(storeSlug, params = {}) {
49
52
  pageSize,
50
53
  });
51
54
  }
52
- export async function getStoreAuctions(storeSlug, params = {}) {
55
+ export async function getStoreAuctions(storeSlug, params = {}, viewer) {
53
56
  const { sorts = "auctionEndDate", page = 1, pageSize = 24, filters } = params;
54
- const storeDoc = await storeRepository.findBySlug(storeSlug);
57
+ const storeDoc = await filterSingleTestData(await storeRepository.findBySlug(storeSlug), viewer);
55
58
  if (!storeDoc ||
56
59
  storeDoc.status !== STORE_FIELDS.STATUS_VALUES.ACTIVE ||
57
60
  !storeDoc.isPublic) {
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Div, FilterDrawer, ListingToolbar, LoginRequiredModal, Pagination, Row, Stack, useToast } from "../../../ui";
7
+ import { Div, FilterDrawer, ListingToolbar, LoginRequiredModal, Pagination, Row, Stack, useToast, StickyToolbar } from "../../../ui";
8
8
  import { useAuthGate } from "../../../react/hooks/useAuthGate";
9
9
  import { ACTION_ID } from "../../products/constants/action-defs";
10
10
  import { MarketplaceAuctionGrid } from "../../auctions/components/MarketplaceAuctionGrid";
@@ -113,7 +113,7 @@ export function StoreAuctionsListing({ storeId, initialData }) {
113
113
  return;
114
114
  setView(v);
115
115
  table.set("view", v);
116
- }, onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-[var(--appkit-space-4)]" : gridClass, wishlistActions: wishlistActions, labels: {
116
+ }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-[var(--appkit-space-4)]" : gridClass, wishlistActions: wishlistActions, labels: {
117
117
  emptyTitle: "No auctions yet",
118
118
  emptyDescription: "This store has no active auctions.",
119
119
  } })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(AuctionFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text } from "../../../ui";
7
+ import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text, StickyToolbar } from "../../../ui";
8
8
  import { ClassifiedFilters } from "../../classified/components/ClassifiedFilters";
9
9
  import { InteractiveProductCard } from "../../products/components/InteractiveProductCard";
10
10
  import { PRODUCT_FIELDS } from "../../../constants/field-names";
@@ -40,7 +40,7 @@ export function StoreClassifiedsListing({ storeId, initialData }) {
40
40
  };
41
41
  const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
42
42
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-[var(--appkit-space-4)]";
43
- return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store classifieds\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
43
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store classifieds\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
44
44
  ? Array.from({ length: 8 }).map((_, i) => _jsx(Div, { surface: "muted", rounded: "xl", className: "aspect-square animate-pulse" }, i))
45
45
  : products.length === 0
46
46
  ? _jsx(Div, { paddingY: "y-4xl", className: "col-span-full text-left", children: _jsx(Text, { color: "muted", children: "No classified listings in this store yet." }) })
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text } from "../../../ui";
7
+ import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text, StickyToolbar } from "../../../ui";
8
8
  import { DigitalCodeFilters } from "../../digital-codes/components/DigitalCodeFilters";
9
9
  import { InteractiveProductCard } from "../../products/components/InteractiveProductCard";
10
10
  import { PRODUCT_FIELDS } from "../../../constants/field-names";
@@ -40,7 +40,7 @@ export function StoreDigitalCodesListing({ storeId, initialData }) {
40
40
  };
41
41
  const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
42
42
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-[var(--appkit-space-4)]";
43
- return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search digital codes\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
43
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search digital codes\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
44
44
  ? Array.from({ length: 8 }).map((_, i) => _jsx(Div, { surface: "muted", rounded: "xl", className: "aspect-square animate-pulse" }, i))
45
45
  : products.length === 0
46
46
  ? _jsx(Div, { paddingY: "y-4xl", className: "col-span-full text-left", children: _jsx(Text, { color: "muted", children: "No digital codes in this store yet." }) })
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text } from "../../../ui";
7
+ import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text, StickyToolbar } from "../../../ui";
8
8
  import { LiveItemFilters } from "../../live/components/LiveItemFilters";
9
9
  import { InteractiveProductCard } from "../../products/components/InteractiveProductCard";
10
10
  import { PRODUCT_FIELDS } from "../../../constants/field-names";
@@ -40,7 +40,7 @@ export function StoreLiveItemsListing({ storeId, initialData }) {
40
40
  };
41
41
  const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
42
42
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-[var(--appkit-space-4)]";
43
- return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search live animals & plants\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
43
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search live animals & plants\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
44
44
  ? Array.from({ length: 8 }).map((_, i) => _jsx(Div, { surface: "muted", rounded: "xl", className: "aspect-square animate-pulse" }, i))
45
45
  : products.length === 0
46
46
  ? _jsx(Div, { paddingY: "y-4xl", className: "col-span-full text-left", children: _jsx(Text, { color: "muted", children: "No live items in this store yet." }) })
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Stack, Text } from "../../../ui";
7
+ import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, StickyToolbar } from "../../../ui";
8
8
  import { MarketplacePreorderCard } from "../../pre-orders/components/MarketplacePreorderCard";
9
9
  import { ProductFilters } from "../../products/components/ProductFilters";
10
10
  import { ROUTES } from "../../../next";
@@ -66,5 +66,5 @@ export function StorePreOrdersListing({ storeId, initialData }) {
66
66
  return;
67
67
  setView(v);
68
68
  table.set("view", v);
69
- }, onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8", surface: "subtle", rounded: "default" })] })] }, i))) })) : preOrders.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "This store has no pre-orders yet." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "list", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })) : (_jsx(Div, { className: gridClass, children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "grid", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) })] }));
69
+ }, onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8", surface: "subtle", rounded: "default" })] })] }, i))) })) : preOrders.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "This store has no pre-orders yet." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "list", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })) : (_jsx(Div, { className: gridClass, children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "grid", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) })] }));
70
70
  }
@@ -4,7 +4,7 @@ import { useState, useCallback } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
6
6
  import { useProducts } from "../../products/hooks/useProducts";
7
- import { Div, FilterDrawer, ListingToolbar, LoginRequiredModal, Pagination, Row, Stack, useToast } from "../../../ui";
7
+ import { Div, FilterDrawer, ListingToolbar, LoginRequiredModal, Pagination, Row, Stack, useToast, StickyToolbar } from "../../../ui";
8
8
  import { useAuthGate } from "../../../react/hooks/useAuthGate";
9
9
  import { ACTION_ID } from "../../products/constants/action-defs";
10
10
  import { ROUTES } from "../../../next";
@@ -93,5 +93,5 @@ export function StoreProductsListing({ storeId, initialData }) {
93
93
  }, [localCart, showToast]);
94
94
  return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
95
95
  table.set("sort", v);
96
- }, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), 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: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view, emptyLabel: "This store has no products yet.", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
96
+ }, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view, emptyLabel: "This store has no products yet.", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
97
97
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback, useMemo } from "react";
4
4
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
- import { Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, Span, Stack, Text } from "../../../ui";
5
+ import { Div, ListingFilterDrawer, ListingToolbar, Pagination, Row, Span, Stack, Text, StickyToolbar } from "../../../ui";
6
6
  import { ReviewCard } from "../../reviews/components/ReviewsList";
7
7
  import { ReviewFilters, REVIEW_PUBLIC_SORT_OPTIONS } from "../../reviews/components/ReviewFilters";
8
8
  import { useStoreReviews } from "../hooks/useStores";
@@ -107,5 +107,5 @@ export function StoreReviewsListing({ storeSlug }) {
107
107
  value: opt.value,
108
108
  label: SORT_OPTION_LABELS[opt.key] ?? opt.key,
109
109
  }));
110
- return (_jsxs(Div, { className: "min-h-screen", children: [totalReviews > 0 && (_jsxs(Row, { border: "default", align: "center", gap: "sm", className: "border-b", padding: "inline", children: [_jsx(Span, { weight: "bold", size: "2xl", color: "primary", children: averageRating.toFixed(1) }), _jsxs(Span, { size: "sm", color: "muted", children: ["/ 5 \u00B7 ", totalReviews, " reviews"] })] })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search reviews...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: sortOptions, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState }), 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) }) })), _jsx(Div, { paddingY: "y-lg", paddingX: "x-md", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "3", children: [_jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-2/3", surface: "subtle", rounded: "default" })] }) }, i))) })) : reviews.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No reviews found." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: reviews.map((review) => (_jsx(ReviewCard, { review: review, context: "store" }, review.id))) })) : (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: reviews.map((review) => (_jsx(ReviewCard, { review: review, context: "store" }, review.id))) })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(ReviewFilters, { table: pendingTable, variant: "public" }) })] }));
110
+ return (_jsxs(Div, { className: "min-h-screen", children: [totalReviews > 0 && (_jsxs(Row, { border: "default", align: "center", gap: "sm", className: "border-b", padding: "inline", children: [_jsx(Span, { weight: "bold", size: "2xl", color: "primary", children: averageRating.toFixed(1) }), _jsxs(Span, { size: "sm", color: "muted", children: ["/ 5 \u00B7 ", totalReviews, " reviews"] })] })), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search reviews...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: sort, sortOptions: sortOptions, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState }), 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) }) }) })), _jsx(Div, { paddingY: "y-lg", paddingX: "x-md", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "3", children: [_jsx(Div, { className: "h-4 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-2/3", surface: "subtle", rounded: "default" })] }) }, i))) })) : reviews.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No reviews found." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: reviews.map((review) => (_jsx(ReviewCard, { review: review, context: "store" }, review.id))) })) : (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: reviews.map((review) => (_jsx(ReviewCard, { review: review, context: "store" }, review.id))) })) }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsx(ReviewFilters, { table: pendingTable, variant: "public" }) })] }));
111
111
  }
@@ -4,7 +4,7 @@ import { Row, SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
4
  import { useState, useCallback, useMemo } from "react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useStores } from "../hooks/useStores";
7
- import { BulkActionBar, Div, ListingFilterDrawer, ListingToolbar, Pagination, Text } from "../../../ui";
7
+ import { BulkActionBar, Div, ListingFilterDrawer, ListingToolbar, Pagination, Text, StickyToolbar } from "../../../ui";
8
8
  import { ROUTES } from "../../../next";
9
9
  import { InteractiveStoreCard } from "./InteractiveStoreCard";
10
10
  import { StoreFilters } from "./StoreFilters";
@@ -161,7 +161,7 @@ export function StoresIndexListing({ initialData }) {
161
161
  disabled: selection.selectedCount !== 1,
162
162
  onClick: () => { selection.clearSelection(); },
163
163
  },
164
- ] }), 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: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Div, { className: `${__P.p4} space-y-[0.625rem]`, children: [_jsx(Row, { align: "center", gap: "sm", children: _jsx(Div, { className: "h-10 w-10", surface: "subtle", rounded: "lg" }) }), _jsx(Div, { className: "h-4 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] })] }, i))) })) : stores.length === 0 ? (_jsx(Text, { paddingY: "4xl", color: "muted", size: "sm", align: "start", children: "No stores found." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: stores.map((store) => {
164
+ ] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: `${__O.hidden} animate-pulse`, border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-video", surface: "subtle" }), _jsxs(Div, { className: `${__P.p4} space-y-[0.625rem]`, children: [_jsx(Row, { align: "center", gap: "sm", children: _jsx(Div, { className: "h-10 w-10", surface: "subtle", rounded: "lg" }) }), _jsx(Div, { className: "h-4 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] })] }, i))) })) : stores.length === 0 ? (_jsx(Text, { paddingY: "4xl", color: "muted", size: "sm", align: "start", children: "No stores found." })) : view === "list" ? (_jsx(Stack, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", border: "subtle", rounded: "xl", children: stores.map((store) => {
165
165
  const storeKey = store.storeSlug ?? store.id;
166
166
  return (_jsx(InteractiveStoreCard, { store: store, href: String(ROUTES.PUBLIC.STORE_DETAIL(storeKey)), selectable: selection.isSelecting, isSelected: selection.isSelected(store.id ?? store.storeSlug), onSelect: (id, sel) => { void sel; selection.toggle(id); } }, storeKey));
167
167
  }) })) : (_jsx(Div, { layout: "grid", gap: "6", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: stores.map((store) => {
@@ -50,6 +50,10 @@ export type StoreStatus = (typeof StoreStatusValues)[keyof typeof StoreStatusVal
50
50
  export interface StoreDocument extends BaseDocument {
51
51
  storeSlug: string;
52
52
  ownerId: string;
53
+ /** Tester sandbox flag — set on the shared admin-seeded test store; swept by testerSandboxCleanup. */
54
+ isTestData?: boolean;
55
+ /** When isTestData, the cutoff after which testerSandboxCleanup deletes this doc. */
56
+ testDataExpiresAt?: Date;
53
57
  storeName: string;
54
58
  storeDescription?: string;
55
59
  storeCategory?: string;
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import type { TesterChecklistItemDocument } from "../schemas/firestore";
3
+ export declare const checklistItemCreateSchema: z.ZodObject<{
4
+ groupKey: z.ZodString;
5
+ groupLabel: z.ZodString;
6
+ pageKey: z.ZodString;
7
+ pageLabel: z.ZodString;
8
+ label: z.ZodString;
9
+ description: z.ZodOptional<z.ZodString>;
10
+ href: z.ZodOptional<z.ZodString>;
11
+ order: z.ZodDefault<z.ZodNumber>;
12
+ isActive: z.ZodDefault<z.ZodBoolean>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ label: string;
15
+ order: number;
16
+ isActive: boolean;
17
+ groupKey: string;
18
+ pageKey: string;
19
+ groupLabel: string;
20
+ pageLabel: string;
21
+ href?: string | undefined;
22
+ description?: string | undefined;
23
+ }, {
24
+ label: string;
25
+ groupKey: string;
26
+ pageKey: string;
27
+ groupLabel: string;
28
+ pageLabel: string;
29
+ href?: string | undefined;
30
+ description?: string | undefined;
31
+ order?: number | undefined;
32
+ isActive?: boolean | undefined;
33
+ }>;
34
+ export declare const checklistItemUpdateSchema: z.ZodObject<{
35
+ groupKey: z.ZodOptional<z.ZodString>;
36
+ groupLabel: z.ZodOptional<z.ZodString>;
37
+ pageKey: z.ZodOptional<z.ZodString>;
38
+ pageLabel: z.ZodOptional<z.ZodString>;
39
+ label: z.ZodOptional<z.ZodString>;
40
+ description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
41
+ href: z.ZodOptional<z.ZodOptional<z.ZodString>>;
42
+ order: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
43
+ isActive: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
44
+ }, "strip", z.ZodTypeAny, {
45
+ label?: string | undefined;
46
+ href?: string | undefined;
47
+ description?: string | undefined;
48
+ order?: number | undefined;
49
+ isActive?: boolean | undefined;
50
+ groupKey?: string | undefined;
51
+ pageKey?: string | undefined;
52
+ groupLabel?: string | undefined;
53
+ pageLabel?: string | undefined;
54
+ }, {
55
+ label?: string | undefined;
56
+ href?: string | undefined;
57
+ description?: string | undefined;
58
+ order?: number | undefined;
59
+ isActive?: boolean | undefined;
60
+ groupKey?: string | undefined;
61
+ pageKey?: string | undefined;
62
+ groupLabel?: string | undefined;
63
+ pageLabel?: string | undefined;
64
+ }>;
65
+ export type ChecklistItemCreateInput = z.infer<typeof checklistItemCreateSchema>;
66
+ export type ChecklistItemUpdateInput = z.infer<typeof checklistItemUpdateSchema>;
67
+ export declare function createChecklistItem(input: ChecklistItemCreateInput): Promise<TesterChecklistItemDocument>;
68
+ export declare function updateChecklistItem(id: string, input: ChecklistItemUpdateInput): Promise<TesterChecklistItemDocument>;
69
+ export declare function deleteChecklistItem(id: string): Promise<void>;
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { serverLogger } from "../../../monitoring";
3
+ import { testerChecklistItemRepository } from "../repository/tester-checklist-item.repository";
4
+ // --- Schemas --------------------------------------------------------------
5
+ const checklistItemBaseSchema = z.object({
6
+ groupKey: z.string().min(1).max(100),
7
+ groupLabel: z.string().min(1).max(200),
8
+ pageKey: z.string().min(1).max(100),
9
+ pageLabel: z.string().min(1).max(200),
10
+ label: z.string().min(3).max(500),
11
+ description: z.string().max(2000).optional(),
12
+ href: z.string().max(300).optional(),
13
+ order: z.number().int().min(0).default(0),
14
+ isActive: z.boolean().default(true),
15
+ });
16
+ export const checklistItemCreateSchema = checklistItemBaseSchema;
17
+ export const checklistItemUpdateSchema = checklistItemBaseSchema.partial();
18
+ // --- Actions --------------------------------------------------------------
19
+ export async function createChecklistItem(input) {
20
+ const item = await testerChecklistItemRepository.createItem({
21
+ groupKey: input.groupKey,
22
+ groupLabel: input.groupLabel,
23
+ pageKey: input.pageKey,
24
+ pageLabel: input.pageLabel,
25
+ label: input.label,
26
+ description: input.description,
27
+ href: input.href,
28
+ order: input.order ?? 0,
29
+ isActive: input.isActive ?? true,
30
+ });
31
+ serverLogger.info("createChecklistItem", { itemId: item.id });
32
+ return item;
33
+ }
34
+ export async function updateChecklistItem(id, input) {
35
+ const item = await testerChecklistItemRepository.update(id, input);
36
+ serverLogger.info("updateChecklistItem", { itemId: id });
37
+ return item;
38
+ }
39
+ export async function deleteChecklistItem(id) {
40
+ await testerChecklistItemRepository.delete(id);
41
+ serverLogger.info("deleteChecklistItem", { itemId: id });
42
+ }
@@ -0,0 +1 @@
1
+ export * from "./checklist-item-actions";
@@ -0,0 +1 @@
1
+ export * from "./checklist-item-actions";
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminTesterFeedbackIssuesView(): React.JSX.Element;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { Card, Stack, Text } from "../../../ui";
5
+ import { MediaImage } from "../../media/MediaImage";
6
+ import { apiClient } from "../../../http";
7
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ export function AdminTesterFeedbackIssuesView() {
9
+ const query = useQuery({
10
+ queryKey: ["admin", "tester-feedback", "report"],
11
+ queryFn: async () => {
12
+ const res = await apiClient.get(ADMIN_ENDPOINTS.TESTER_FEEDBACK_REPORT);
13
+ return res?.data ?? res;
14
+ },
15
+ });
16
+ const issues = query.data?.issues ?? [];
17
+ if (query.isLoading)
18
+ return _jsx(Text, { color: "muted", children: "Loading issues\u2026" });
19
+ if (issues.length === 0)
20
+ return _jsx(Text, { color: "muted", children: "No \"No\" answers recorded yet." });
21
+ return (_jsx(Stack, { gap: "sm", children: issues.map((issue) => (_jsx(Card, { padding: "md", variant: "default", children: _jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { weight: "semibold", children: [issue.groupKey, " / ", issue.pageKey] }), _jsxs(Text, { size: "sm", color: "muted", children: ["Reported by ", issue.testerDisplayName] }), issue.comment && _jsx(Text, { size: "sm", children: issue.comment }), issue.screenshotUrl && (_jsx(MediaImage, { src: issue.screenshotUrl, alt: "Tester screenshot", size: "card" }))] }) }, issue.id))) }));
22
+ }
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ export type AdminTesterFeedbackListViewProps = Record<string, never>;
3
+ export declare function AdminTesterFeedbackListView(_props: AdminTesterFeedbackListViewProps): React.JSX.Element;
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useApiMutation } from "../../../client";
4
+ import { SIEVE_OP, sieveFilter } from "../../../utils/sieve-builder";
5
+ import { sortBy } from "../../../constants/sort";
6
+ import { TESTER_CHECKLIST_RESPONSE_FIELDS } from "../schemas/firestore";
7
+ import { useQueryClient } from "@tanstack/react-query";
8
+ import { FilterChipGroup, RowActionMenu, Stack, Text } from "../../../ui";
9
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
+ import { toRecordArray, toRelativeDate, toStringValue, } from "../../admin/hooks/useAdminListingData";
12
+ import { DataListingView } from "../../admin/components/DataListingView";
13
+ import { apiClient } from "../../../http";
14
+ const COLUMNS = [
15
+ {
16
+ key: "primary",
17
+ header: "Tester / Case",
18
+ sortable: true,
19
+ 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 })] })),
20
+ },
21
+ {
22
+ key: "answer",
23
+ header: "Answer",
24
+ className: "w-24",
25
+ render: (row) => (_jsx(Text, { weight: "medium", color: row.answer === "no" ? "error" : "success", children: row.answer === "no" ? "No" : row.answer === "yes" ? "Yes" : "—" })),
26
+ },
27
+ {
28
+ key: "status",
29
+ header: "Status",
30
+ className: "w-28",
31
+ render: (row) => _jsx(Text, { size: "sm", color: "muted", children: row.status }),
32
+ },
33
+ {
34
+ key: "updatedAt",
35
+ header: "Updated",
36
+ className: "w-32",
37
+ render: (row) => _jsx(Text, { size: "sm", color: "muted", children: row.updatedAt }),
38
+ },
39
+ ];
40
+ export function AdminTesterFeedbackListView(_props) {
41
+ const queryClient = useQueryClient();
42
+ const patchMutation = useApiMutation({
43
+ mutationFn: async (id) => {
44
+ await apiClient.patch(ADMIN_ENDPOINTS.TESTER_FEEDBACK_BY_ID(id), {});
45
+ },
46
+ onSuccess: () => {
47
+ queryClient.invalidateQueries({ queryKey: ["admin", "tester-feedback"] });
48
+ },
49
+ });
50
+ const config = {
51
+ portal: "admin",
52
+ title: "All Submissions",
53
+ searchPlaceholder: "Search tester, comment, or case",
54
+ emptyLabel: "No feedback submitted yet",
55
+ filterKeys: ["answer", "status"],
56
+ defaultSort: sortBy(TESTER_CHECKLIST_RESPONSE_FIELDS.CREATED_AT, "DESC"),
57
+ queryKey: ["admin", "tester-feedback", "listing"],
58
+ endpoint: ADMIN_ENDPOINTS.TESTER_FEEDBACK,
59
+ sortOptions: [
60
+ { value: sortBy(TESTER_CHECKLIST_RESPONSE_FIELDS.CREATED_AT, "DESC"), label: "Newest" },
61
+ { value: sortBy(TESTER_CHECKLIST_RESPONSE_FIELDS.CREATED_AT, "ASC"), label: "Oldest" },
62
+ ],
63
+ columns: COLUMNS,
64
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
65
+ id: toStringValue(item.id, `feedback-${index}`),
66
+ primary: toStringValue(item.testerDisplayName, "Unknown tester"),
67
+ secondary: `${toStringValue(item.groupKey, "")} / ${toStringValue(item.pageKey, "")}${item.comment ? " — " + toStringValue(item.comment, "") : ""}`,
68
+ answer: toStringValue(item.answer, ""),
69
+ status: toStringValue(item.status, "new"),
70
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
71
+ screenshotUrl: item.screenshotUrl ? toStringValue(item.screenshotUrl, "") : undefined,
72
+ })),
73
+ getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
74
+ buildFilters: (state) => {
75
+ const parts = [];
76
+ if (state.answer && state.answer !== "All")
77
+ parts.push(sieveFilter("answer", SIEVE_OP.EQ, state.answer));
78
+ if (state.status && state.status !== "All")
79
+ parts.push(sieveFilter("status", SIEVE_OP.EQ, state.status));
80
+ return parts.join(",") || undefined;
81
+ },
82
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Answer", tabs: [
83
+ { id: "All", label: "All" },
84
+ { id: "yes", label: "Yes" },
85
+ { id: "no", label: "No" },
86
+ ], value: pendingFilters.answer || "", onChange: (v) => setPendingFilters((p) => ({ ...p, answer: v })) }), _jsx(FilterChipGroup, { label: "Status", tabs: [
87
+ { id: "All", label: "All" },
88
+ { id: "new", label: "New" },
89
+ { id: "reviewed", label: "Reviewed" },
90
+ ], value: pendingFilters.status || "", onChange: (v) => setPendingFilters((p) => ({ ...p, status: v })) })] })),
91
+ renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
92
+ {
93
+ label: ACTIONS.ADMIN["mark-feedback-reviewed"].label,
94
+ disabled: row.status === "reviewed",
95
+ onClick: () => patchMutation.mutate(row.id),
96
+ },
97
+ ] })),
98
+ };
99
+ return _jsx(DataListingView, { config: config });
100
+ }
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminTesterFeedbackReportView(): React.JSX.Element;
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { Stack, Text } from "../../../ui";
5
+ import { apiClient } from "../../../http";
6
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
7
+ import { TesterFeedbackChart } from "./TesterFeedbackChart";
8
+ export function AdminTesterFeedbackReportView() {
9
+ const query = useQuery({
10
+ queryKey: ["admin", "tester-feedback", "report"],
11
+ queryFn: async () => {
12
+ const res = await apiClient.get(ADMIN_ENDPOINTS.TESTER_FEEDBACK_REPORT);
13
+ return res?.data ?? res;
14
+ },
15
+ });
16
+ if (query.isLoading)
17
+ return _jsx(Text, { color: "muted", children: "Loading report\u2026" });
18
+ const itemCoverage = query.data?.itemCoverage ?? [];
19
+ const totals = query.data?.totals ?? { totalAnswered: 0, totalYes: 0, totalNo: 0 };
20
+ const byGroup = new Map();
21
+ for (const item of itemCoverage) {
22
+ if (!byGroup.has(item.groupKey)) {
23
+ byGroup.set(item.groupKey, { groupLabel: item.groupKey, yes: 0, no: 0 });
24
+ }
25
+ const entry = byGroup.get(item.groupKey);
26
+ entry.yes += item.yesCount;
27
+ entry.no += item.noCount;
28
+ }
29
+ return (_jsx(Stack, { gap: "lg", children: _jsx(TesterFeedbackChart, { data: Array.from(byGroup.values()), totalCases: itemCoverage.length, totalAnswered: totals.totalAnswered, totalYes: totals.totalYes, totalNo: totals.totalNo }) }));
30
+ }
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminTesterFeedbackView(): React.JSX.Element;