@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
@@ -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) => {
@@ -119,6 +119,9 @@ export class StoreRepository extends BaseRepository {
119
119
  [STORE_FIELDS.STATUS]: { canFilter: true, canSort: false },
120
120
  [STORE_FIELDS.IS_PUBLIC]: { canFilter: true, canSort: false },
121
121
  [STORE_FIELDS.CREATED_AT]: { canFilter: false, canSort: true },
122
+ "stats.itemsSold": { canFilter: false, canSort: true },
123
+ "stats.averageRating": { canFilter: false, canSort: true },
124
+ "stats.totalProducts": { canFilter: true, canSort: false },
122
125
  };
123
126
  let baseQuery = this.getCollection();
124
127
  if (activeOnly) {
@@ -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;
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, Heading, Row, Stack, Tabs, TabsContent, TabsList, TabsTrigger, useToast } from "../../../ui";
4
+ import { apiClient } from "../../../http";
5
+ import { normalizeError } from "../../../errors/normalize";
6
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
7
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { AdminTesterFeedbackReportView } from "./AdminTesterFeedbackReportView";
9
+ import { AdminTesterFeedbackIssuesView } from "./AdminTesterFeedbackIssuesView";
10
+ import { AdminTesterFeedbackListView } from "./AdminTesterFeedbackListView";
11
+ export function AdminTesterFeedbackView() {
12
+ const { showToast } = useToast();
13
+ const handleExportReport = async () => {
14
+ try {
15
+ const blob = await apiClient.blob(ADMIN_ENDPOINTS.TESTER_FEEDBACK_EXPORT);
16
+ const url = URL.createObjectURL(blob);
17
+ const a = document.createElement("a");
18
+ a.href = url;
19
+ a.download = `tester-feedback-report-${new Date().toISOString().slice(0, 10)}.md`;
20
+ a.click();
21
+ URL.revokeObjectURL(url);
22
+ }
23
+ catch (_err) {
24
+ void normalizeError(_err);
25
+ showToast("Report export failed.", "error");
26
+ }
27
+ };
28
+ return (_jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { justify: "between", align: "center", wrap: true, children: [_jsx(Heading, { level: 1, children: "Tester Feedback" }), _jsx(Button, { variant: "ghost", action: ACTIONS.ADMIN["export-tester-feedback"], onClick: handleExportReport })] }), _jsxs(Tabs, { defaultValue: "report", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "report", children: "Report" }), _jsx(TabsTrigger, { value: "issues", children: "Main Issues" }), _jsx(TabsTrigger, { value: "submissions", children: "All Submissions" })] }), _jsx(TabsContent, { value: "report", children: _jsx(AdminTesterFeedbackReportView, {}) }), _jsx(TabsContent, { value: "issues", children: _jsx(AdminTesterFeedbackIssuesView, {}) }), _jsx(TabsContent, { value: "submissions", children: _jsx(AdminTesterFeedbackListView, {}) })] })] }));
29
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import type { TesterAnswer } from "../schemas/firestore";
3
+ export interface TesterChecklistStepItem {
4
+ checklistItemId: string;
5
+ label: string;
6
+ description?: string;
7
+ href?: string;
8
+ answer: TesterAnswer | null;
9
+ comment?: string;
10
+ screenshotUrl?: string;
11
+ }
12
+ export interface TesterChecklistStepRowProps {
13
+ item: TesterChecklistStepItem;
14
+ testerDisplayName: string;
15
+ onAnswer: (checklistItemId: string, answer: TesterAnswer) => void;
16
+ onSaveNote: (checklistItemId: string, comment: string, screenshotUrl: string) => Promise<void>;
17
+ }
18
+ export declare function TesterChecklistStepRow({ item, testerDisplayName, onAnswer, onSaveNote, }: TesterChecklistStepRowProps): React.JSX.Element;
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { Anchor, Button, Div, FieldTextarea, Row, Stack, Text } from "../../../ui";
5
+ import { MediaUploadField } from "../../media/upload/MediaUploadField";
6
+ import { useMediaUpload } from "../../media/hooks/useMedia";
7
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
8
+ export function TesterChecklistStepRow({ item, testerDisplayName, onAnswer, onSaveNote, }) {
9
+ const [expanded, setExpanded] = React.useState(false);
10
+ const [comment, setComment] = React.useState(item.comment ?? "");
11
+ const [screenshotUrl, setScreenshotUrl] = React.useState(item.screenshotUrl ?? "");
12
+ const [saving, setSaving] = React.useState(false);
13
+ const { upload } = useMediaUpload();
14
+ const handleUpload = async (file) => upload(file, "tester-screenshots", false, {
15
+ type: "tester-screenshot",
16
+ testerName: testerDisplayName,
17
+ checklistItemId: item.checklistItemId,
18
+ });
19
+ const handleSave = async () => {
20
+ setSaving(true);
21
+ try {
22
+ await onSaveNote(item.checklistItemId, comment, screenshotUrl);
23
+ }
24
+ finally {
25
+ setSaving(false);
26
+ }
27
+ };
28
+ return (_jsxs(Stack, { gap: "xs", padding: "sm", border: "default", rounded: "md", children: [_jsxs(Row, { gap: "sm", justify: "between", align: "center", wrap: true, children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { weight: "medium", children: item.label }), item.description && (_jsx(Text, { size: "xs", color: "muted", children: item.description })), item.href && (_jsx(Anchor, { href: item.href, tone: "brand", underline: "hover", children: "Go test this \u2192" }))] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", size: "sm", variant: item.answer === "yes" ? "primary" : "outline", onClick: () => onAnswer(item.checklistItemId, "yes"), children: "Yes" }), _jsx(Button, { type: "button", size: "sm", variant: item.answer === "no" ? "danger" : "outline", onClick: () => onAnswer(item.checklistItemId, "no"), children: "No" }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setExpanded((v) => !v), children: expanded ? "Hide note" : "Add note" })] })] }), expanded && (_jsx(Div, { padding: "t-sm", border: "default", className: "border-t", children: _jsxs(Stack, { gap: "sm", children: [_jsx(FieldTextarea, { name: `comment-${item.checklistItemId}`, label: "Comment", value: comment, onChange: setComment, placeholder: "What did you notice? Colors, styles, readability, bugs..." }), _jsx(MediaUploadField, { label: "Screenshot (optional)", value: screenshotUrl, onChange: setScreenshotUrl, onUpload: handleUpload, kind: "image" }), _jsx(Row, { children: _jsx(Button, { type: "button", action: ACTIONS.TESTER["save-note"], isLoading: saving, onClick: handleSave }) })] }) }))] }));
29
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ export interface GroupCoverageDatum {
3
+ groupLabel: string;
4
+ yes: number;
5
+ no: number;
6
+ }
7
+ export interface TesterFeedbackChartProps {
8
+ data: GroupCoverageDatum[];
9
+ totalCases: number;
10
+ totalAnswered: number;
11
+ totalYes: number;
12
+ totalNo: number;
13
+ }
14
+ export declare function TesterFeedbackChart({ data, totalCases, totalAnswered, totalYes, totalNo, }: TesterFeedbackChartProps): React.JSX.Element;
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import dynamic from "next/dynamic";
4
+ import { Card, Heading, Row, Stack, Text } from "../../../ui";
5
+ // recharts exports generic components (<DataPointType>) that don't satisfy
6
+ // next/dynamic's ComponentType<P> constraint — cast each to ComponentType<any>.
7
+ const ResponsiveContainer = dynamic(() => import("recharts").then((m) => m.ResponsiveContainer), { ssr: false });
8
+ const BarChart = dynamic(() => import("recharts").then((m) => m.BarChart), { ssr: false });
9
+ const Bar = dynamic(() => import("recharts").then((m) => m.Bar), { ssr: false });
10
+ const XAxis = dynamic(() => import("recharts").then((m) => m.XAxis), { ssr: false });
11
+ const YAxis = dynamic(() => import("recharts").then((m) => m.YAxis), { ssr: false });
12
+ const CartesianGrid = dynamic(() => import("recharts").then((m) => m.CartesianGrid), { ssr: false });
13
+ const Tooltip = dynamic(() => import("recharts").then((m) => m.Tooltip), { ssr: false });
14
+ const Legend = dynamic(() => import("recharts").then((m) => m.Legend), { ssr: false });
15
+ export function TesterFeedbackChart({ data, totalCases, totalAnswered, totalYes, totalNo, }) {
16
+ const passRate = totalAnswered > 0 ? Math.round((totalYes / totalAnswered) * 100) : 0;
17
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { gap: "md", wrap: true, children: [_jsxs(Card, { padding: "md", variant: "default", children: [_jsx(Text, { size: "xs", color: "muted", children: "Total test cases" }), _jsx(Heading, { level: 3, children: totalCases })] }), _jsxs(Card, { padding: "md", variant: "default", children: [_jsx(Text, { size: "xs", color: "muted", children: "Answers recorded" }), _jsx(Heading, { level: 3, children: totalAnswered })] }), _jsxs(Card, { padding: "md", variant: "default", children: [_jsx(Text, { size: "xs", color: "muted", children: "Pass rate" }), _jsxs(Heading, { level: 3, children: [passRate, "%"] })] }), _jsxs(Card, { padding: "md", variant: "default", children: [_jsx(Text, { size: "xs", color: "muted", children: "Issues (No)" }), _jsx(Heading, { level: 3, color: "error", children: totalNo })] })] }), _jsx(ResponsiveContainer, { width: "100%", height: 360, children: _jsxs(BarChart, { data: data, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "groupLabel", tick: { fontSize: 12 } }), _jsx(YAxis, { allowDecimals: false }), _jsx(Tooltip, {}), _jsx(Legend, {}), _jsx(Bar, { dataKey: "yes", name: "Yes", fill: "var(--appkit-color-success)" }), _jsx(Bar, { dataKey: "no", name: "No", fill: "var(--appkit-color-error)" })] }) })] }));
18
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export interface TesterHubViewProps {
3
+ sandboxExpiresAt?: string | null;
4
+ }
5
+ export declare function TesterHubView({ sandboxExpiresAt }: TesterHubViewProps): React.JSX.Element;
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
5
+ import { Alert, Details, Div, FieldInput, Heading, Stack, Summary, Text } from "../../../ui";
6
+ import { apiClient } from "../../../http";
7
+ import { useApiMutation } from "../../../client";
8
+ import { useSession } from "../../../react";
9
+ import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { TesterChecklistStepRow } from "./TesterChecklistStepRow";
11
+ function matchesQuery(item, query) {
12
+ if (!query)
13
+ return true;
14
+ const haystack = `${item.label} ${item.description ?? ""} ${item.groupLabel} ${item.pageLabel}`.toLowerCase();
15
+ return haystack.includes(query.toLowerCase());
16
+ }
17
+ export function TesterHubView({ sandboxExpiresAt }) {
18
+ const { user } = useSession();
19
+ const queryClient = useQueryClient();
20
+ const [search, setSearch] = React.useState("");
21
+ const query = useQuery({
22
+ queryKey: ["user", "tester-checklist"],
23
+ queryFn: async () => {
24
+ const res = await apiClient.get(ACCOUNT_ENDPOINTS.TESTER_CHECKLIST);
25
+ return res?.data ?? res;
26
+ },
27
+ });
28
+ const upsertMutation = useApiMutation({
29
+ mutationFn: async (vars) => apiClient.put(ACCOUNT_ENDPOINTS.TESTER_CHECKLIST_ITEM_BY_ID(vars.checklistItemId), vars.patch),
30
+ onSuccess: () => {
31
+ void queryClient.invalidateQueries({ queryKey: ["user", "tester-checklist"] });
32
+ },
33
+ });
34
+ if (!user?.isTester) {
35
+ return (_jsx(Alert, { variant: "warning", title: "Testers only", children: "This page is only available to accounts flagged as testers. Contact an admin if you believe this is a mistake." }));
36
+ }
37
+ const items = query.data?.items ?? [];
38
+ const filtered = items.filter((item) => matchesQuery(item, search));
39
+ const groups = new Map();
40
+ for (const item of filtered) {
41
+ if (!groups.has(item.groupKey)) {
42
+ groups.set(item.groupKey, { groupLabel: item.groupLabel, pages: new Map() });
43
+ }
44
+ const group = groups.get(item.groupKey);
45
+ if (!group.pages.has(item.pageKey)) {
46
+ group.pages.set(item.pageKey, { pageLabel: item.pageLabel, items: [] });
47
+ }
48
+ group.pages.get(item.pageKey).items.push(item);
49
+ }
50
+ const handleAnswer = (checklistItemId, answer) => {
51
+ upsertMutation.mutate({ checklistItemId, patch: { answer } });
52
+ };
53
+ const handleSaveNote = async (checklistItemId, comment, screenshotUrl) => {
54
+ await upsertMutation.mutateAsync({ checklistItemId, patch: { comment, screenshotUrl } });
55
+ };
56
+ return (_jsxs(Stack, { gap: "lg", children: [_jsx(Heading, { level: 1, children: "Tester Hub" }), _jsx(Text, { color: "muted", children: "Work through the checklist below and answer Yes/No for each test case. Add a comment and screenshot wherever something looks off \u2014 colors, styles, readability, bugs. Your answers save automatically." }), sandboxExpiresAt && (_jsxs(Alert, { variant: "info", title: "Shared test sandbox", children: ["The shared test store/products/categories expire on ", new Date(sandboxExpiresAt).toLocaleDateString(), "."] })), _jsx(FieldInput, { name: "checklist-search", label: "Search test cases", value: search, onChange: setSearch, placeholder: "e.g. checkout, google, bidding..." }), query.isLoading && _jsx(Text, { color: "muted", children: "Loading checklist\u2026" }), Array.from(groups.entries()).map(([groupKey, group]) => (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 3, children: group.groupLabel }), Array.from(group.pages.entries()).map(([pageKey, page]) => {
57
+ const answeredCount = page.items.filter((i) => i.answer).length;
58
+ return (_jsxs(Details, { tone: "card", defaultOpen: false, children: [_jsxs(Summary, { children: [page.pageLabel, " (", answeredCount, "/", page.items.length, " answered)"] }), _jsx(Div, { padding: "t-sm", children: _jsx(Stack, { gap: "sm", children: page.items
59
+ .slice()
60
+ .sort((a, b) => a.order - b.order)
61
+ .map((item) => (_jsx(TesterChecklistStepRow, { testerDisplayName: user?.displayName ?? user?.email ?? "tester", item: {
62
+ checklistItemId: item.id,
63
+ label: item.label,
64
+ description: item.description,
65
+ href: item.href,
66
+ answer: item.answer,
67
+ comment: item.comment,
68
+ screenshotUrl: item.screenshotUrl,
69
+ }, onAnswer: handleAnswer, onSaveNote: handleSaveNote }, item.id))) }) })] }, pageKey));
70
+ })] }, groupKey)))] }));
71
+ }
@@ -0,0 +1,11 @@
1
+ export { TesterHubView } from "./TesterHubView";
2
+ export type { TesterHubViewProps } from "./TesterHubView";
3
+ export { TesterChecklistStepRow } from "./TesterChecklistStepRow";
4
+ export type { TesterChecklistStepRowProps, TesterChecklistStepItem } from "./TesterChecklistStepRow";
5
+ export { TesterFeedbackChart } from "./TesterFeedbackChart";
6
+ export type { TesterFeedbackChartProps, GroupCoverageDatum } from "./TesterFeedbackChart";
7
+ export { AdminTesterFeedbackView } from "./AdminTesterFeedbackView";
8
+ export { AdminTesterFeedbackReportView } from "./AdminTesterFeedbackReportView";
9
+ export { AdminTesterFeedbackIssuesView } from "./AdminTesterFeedbackIssuesView";
10
+ export { AdminTesterFeedbackListView } from "./AdminTesterFeedbackListView";
11
+ export type { AdminTesterFeedbackListViewProps } from "./AdminTesterFeedbackListView";
@@ -0,0 +1,7 @@
1
+ export { TesterHubView } from "./TesterHubView";
2
+ export { TesterChecklistStepRow } from "./TesterChecklistStepRow";
3
+ export { TesterFeedbackChart } from "./TesterFeedbackChart";
4
+ export { AdminTesterFeedbackView } from "./AdminTesterFeedbackView";
5
+ export { AdminTesterFeedbackReportView } from "./AdminTesterFeedbackReportView";
6
+ export { AdminTesterFeedbackIssuesView } from "./AdminTesterFeedbackIssuesView";
7
+ export { AdminTesterFeedbackListView } from "./AdminTesterFeedbackListView";
@@ -0,0 +1,2 @@
1
+ export * from "./schemas";
2
+ export * from "./components";
@@ -0,0 +1,2 @@
1
+ export * from "./schemas";
2
+ export * from "./components";
@@ -0,0 +1,2 @@
1
+ export * from "./tester-checklist-item.repository";
2
+ export * from "./tester-checklist-response.repository";
@@ -0,0 +1,2 @@
1
+ export * from "./tester-checklist-item.repository";
2
+ export * from "./tester-checklist-response.repository";
@@ -0,0 +1,13 @@
1
+ import { BaseRepository } from "../../../providers/db-firebase";
2
+ import type { FirebaseSieveFields, FirebaseSieveResult, SieveModel } from "../../../providers/db-firebase";
3
+ import { type TesterChecklistItemDocument, type TesterChecklistItemCreateInput, type TesterChecklistItemUpdateInput } from "../schemas/firestore";
4
+ export declare class TesterChecklistItemRepository extends BaseRepository<TesterChecklistItemDocument> {
5
+ static readonly SIEVE_FIELDS: FirebaseSieveFields;
6
+ constructor();
7
+ private buildSearchTokens;
8
+ createItem(input: TesterChecklistItemCreateInput): Promise<TesterChecklistItemDocument>;
9
+ update(id: string, data: TesterChecklistItemUpdateInput): Promise<TesterChecklistItemDocument>;
10
+ listActive(): Promise<TesterChecklistItemDocument[]>;
11
+ list(model: SieveModel): Promise<FirebaseSieveResult<TesterChecklistItemDocument>>;
12
+ }
13
+ export declare const testerChecklistItemRepository: TesterChecklistItemRepository;