@mohasinac/appkit 4.1.1 → 4.2.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 (225) hide show
  1. package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
  2. package/dist/_internal/client/features/maintenance/index.js +1 -0
  3. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
  5. package/dist/_internal/server/features/auctions/index.d.ts +1 -1
  6. package/dist/_internal/server/features/auctions/index.js +1 -1
  7. package/dist/_internal/server/features/auctions/service.js +2 -2
  8. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  9. package/dist/_internal/server/features/checkout/actions.js +245 -182
  10. package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
  11. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -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 +10 -0
  15. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  16. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  17. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  18. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  19. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  20. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  25. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  28. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  29. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  30. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  32. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  33. package/dist/_internal/shared/features/auctions/config.js +1 -1
  34. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  35. package/dist/_internal/shared/features/cart/schema.js +2 -2
  36. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  37. package/dist/_internal/shared/features/checkout/config.js +7 -0
  38. package/dist/_internal/shared/features/events/schema.js +1 -1
  39. package/dist/_internal/shared/features/orders/schema.js +1 -1
  40. package/dist/_internal/shared/features/products/schema.js +3 -3
  41. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  42. package/dist/_internal/shared/fees/calculator.d.ts +32 -7
  43. package/dist/_internal/shared/fees/calculator.js +25 -0
  44. package/dist/client.d.ts +4 -4
  45. package/dist/client.js +4 -2
  46. package/dist/constants/api-endpoints.d.ts +9 -6
  47. package/dist/constants/api-endpoints.js +3 -2
  48. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  49. package/dist/features/about/components/AboutView.d.ts +6 -16
  50. package/dist/features/about/components/AboutView.js +3 -3
  51. package/dist/features/about/components/FeesView.js +18 -0
  52. package/dist/features/about/index.d.ts +2 -1
  53. package/dist/features/about/schemas/firestore.d.ts +47 -0
  54. package/dist/features/about/schemas/firestore.js +6 -0
  55. package/dist/features/about/schemas/index.d.ts +210 -0
  56. package/dist/features/about/schemas/index.js +45 -0
  57. package/dist/features/account/hooks/useProfile.js +3 -0
  58. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  59. package/dist/features/admin/actions/notification-actions.js +56 -10
  60. package/dist/features/admin/components/AdminAdsView.js +1 -1
  61. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  62. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  63. package/dist/features/admin/components/AdminCartsView.js +16 -3
  64. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  65. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  66. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  67. package/dist/features/admin/components/AdminMediaView.js +1 -1
  68. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  69. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  70. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  71. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  72. package/dist/features/admin/components/AdminProductsView.js +17 -2
  73. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  74. package/dist/features/admin/components/AdminScammersView.js +7 -0
  75. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  76. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  77. package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
  78. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  79. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  80. package/dist/features/admin/components/AdminStoresView.js +1 -1
  81. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  82. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  83. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  84. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  85. package/dist/features/admin/components/index.d.ts +2 -0
  86. package/dist/features/admin/components/index.js +1 -0
  87. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  88. package/dist/features/admin/constants/filter-tabs.js +55 -27
  89. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  90. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  91. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  92. package/dist/features/admin/schemas/firestore.d.ts +46 -0
  93. package/dist/features/admin/schemas/firestore.js +62 -24
  94. package/dist/features/auctions/actions/bid-actions.js +1 -1
  95. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  96. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  97. package/dist/features/auth/components/LoginForm.js +2 -2
  98. package/dist/features/auth/components/RegisterForm.js +2 -2
  99. package/dist/features/auth/permissions/constants.d.ts +16 -0
  100. package/dist/features/auth/permissions/constants.js +53 -0
  101. package/dist/features/auth/schemas/firestore.js +1 -0
  102. package/dist/features/blog/components/BlogFilters.js +1 -1
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  104. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  105. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  106. package/dist/features/events/components/EventFilters.js +2 -0
  107. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  108. package/dist/features/layout/BackToTop.d.ts +9 -5
  109. package/dist/features/layout/BackToTop.js +17 -21
  110. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  111. package/dist/features/layout/TitleBarLayout.js +4 -4
  112. package/dist/features/media/AvatarUpload.js +7 -3
  113. package/dist/features/media/MediaVideo.js +1 -1
  114. package/dist/features/media/finalize.d.ts +9 -13
  115. package/dist/features/media/finalize.js +23 -74
  116. package/dist/features/media/server.d.ts +1 -1
  117. package/dist/features/media/server.js +1 -1
  118. package/dist/features/orders/actions/refund-actions.js +1 -0
  119. package/dist/features/orders/hooks/useOrders.js +16 -4
  120. package/dist/features/orders/schemas/firestore.d.ts +19 -5
  121. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  122. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  123. package/dist/features/products/components/ArtStickersListView.js +5 -0
  124. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  125. package/dist/features/products/components/ProductForm.js +14 -1
  126. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  127. package/dist/features/products/constants/action-defs.d.ts +29 -3
  128. package/dist/features/products/constants/action-defs.js +77 -1
  129. package/dist/features/products/schemas/firestore.d.ts +1 -1
  130. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  131. package/dist/features/products/types/index.d.ts +4 -0
  132. package/dist/features/scams/schemas/index.js +2 -2
  133. package/dist/features/search/components/Search.js +3 -3
  134. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  135. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  137. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  138. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  139. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  140. package/dist/features/seller/components/SellerProductShell.js +1 -1
  141. package/dist/features/seller/components/SellerProductsView.js +8 -6
  142. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  143. package/dist/features/seller/components/index.d.ts +0 -2
  144. package/dist/features/seller/components/index.js +0 -1
  145. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  146. package/dist/features/stores/components/StoreProductsView.js +1 -1
  147. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  148. package/dist/features/stores/schemas/firestore.js +5 -0
  149. package/dist/features/stores/schemas/index.d.ts +2 -2
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  152. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  153. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  154. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  155. package/dist/features/tester/components/TesterHubView.js +34 -21
  156. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  157. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  158. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  159. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  160. package/dist/features/tester/schemas/firestore.js +6 -0
  161. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  162. package/dist/features/tester/utils/phases.d.ts +31 -0
  163. package/dist/features/tester/utils/phases.js +46 -0
  164. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  165. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  166. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  167. package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
  168. package/dist/features/wishlist/types/index.d.ts +3 -0
  169. package/dist/index.d.ts +15 -6
  170. package/dist/index.js +19 -9
  171. package/dist/next/components/NotFoundView.js +2 -1
  172. package/dist/next/routing/route-map.d.ts +6 -12
  173. package/dist/next/routing/route-map.js +3 -4
  174. package/dist/providers/db-firebase/admin.d.ts +1 -0
  175. package/dist/providers/db-firebase/admin.js +1 -1
  176. package/dist/repositories/index.d.ts +2 -0
  177. package/dist/repositories/index.js +1 -0
  178. package/dist/schemas/registry.d.ts +2 -2
  179. package/dist/seed/addresses-seed-data.js +18 -18
  180. package/dist/seed/bids-seed-data.js +4 -4
  181. package/dist/seed/blog-posts-seed-data.js +82 -82
  182. package/dist/seed/carousel-slides-seed-data.js +14 -10
  183. package/dist/seed/carousels-seed-data.js +3 -3
  184. package/dist/seed/cart-seed-data.js +92 -92
  185. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  186. package/dist/seed/conversations-seed-data.js +75 -75
  187. package/dist/seed/coupon-usage-seed-data.js +12 -12
  188. package/dist/seed/coupons-seed-data.js +41 -41
  189. package/dist/seed/events-seed-data.js +105 -105
  190. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  191. package/dist/seed/grouped-listings-seed-data.js +79 -143
  192. package/dist/seed/history-seed-data.js +38 -38
  193. package/dist/seed/homepage-sections-seed-data.js +9 -9
  194. package/dist/seed/notifications-seed-data.js +56 -56
  195. package/dist/seed/offers-seed-data.js +91 -91
  196. package/dist/seed/orders-seed-data.js +121 -106
  197. package/dist/seed/payouts-seed-data.js +42 -42
  198. package/dist/seed/products-auctions-seed-data.js +10 -2
  199. package/dist/seed/products-standard-seed-data.js +40 -8
  200. package/dist/seed/reviews-seed-data.js +42 -23
  201. package/dist/seed/scammers-seed-data.js +22 -22
  202. package/dist/seed/sessions-seed-data.js +7 -7
  203. package/dist/seed/shipments-seed-data.js +10 -10
  204. package/dist/seed/site-settings-seed-data.js +89 -0
  205. package/dist/seed/store-addresses-seed-data.js +2 -2
  206. package/dist/seed/store-extensions-seed-data.js +93 -93
  207. package/dist/seed/stores-seed-data.js +2 -0
  208. package/dist/seed/support-tickets-seed-data.js +47 -47
  209. package/dist/seed/users-seed-data.js +55 -89
  210. package/dist/seed/wishlists-seed-data.js +22 -22
  211. package/dist/server.d.ts +4 -3
  212. package/dist/server.js +7 -7
  213. package/dist/styles.css +1 -1
  214. package/dist/tailwind-utilities.css +1 -1
  215. package/dist/ui/components/PageLoader.js +8 -3
  216. package/dist/ui/components/Select.d.ts +8 -1
  217. package/dist/ui/components/Select.js +2 -2
  218. package/dist/ui/components/SiteMark.d.ts +23 -0
  219. package/dist/ui/components/SiteMark.js +76 -0
  220. package/dist/ui/index.d.ts +2 -0
  221. package/dist/ui/index.js +1 -0
  222. package/dist/validation/schemas.d.ts +0 -2
  223. package/dist/validation/schemas.js +0 -2
  224. package/package.json +1 -1
  225. package/scripts/seed-cli.mjs +17 -15
@@ -3,9 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React, { useState } from "react";
6
+ import { useRouter } from "next/navigation";
6
7
  import { FilterChipGroup, ListingLayout, RowActionMenu, Span, Text } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
10
+ import { ROUTES } from "../../../next/routing/route-map";
9
11
  import { ADMIN_SUPPORT_TICKET_STATUS_TABS, ADMIN_SUPPORT_TICKET_PRIORITY_TABS, } from "../constants/filter-tabs";
10
12
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
11
13
  import { DataListingView } from "./DataListingView";
@@ -46,6 +48,7 @@ const TICKET_COLUMNS = [
46
48
  },
47
49
  ];
48
50
  export function AdminSupportTicketsView({ children, ...props }) {
51
+ const router = useRouter();
49
52
  const [drawerOpen, setDrawerOpen] = useState(false);
50
53
  const [selectedRow, setSelectedRow] = useState(null);
51
54
  if (React.Children.count(children) > 0) {
@@ -56,7 +59,7 @@ export function AdminSupportTicketsView({ children, ...props }) {
56
59
  title: "Support Tickets",
57
60
  searchPlaceholder: "Search by subject",
58
61
  emptyLabel: "No support tickets found",
59
- filterKeys: ["status", "priority"],
62
+ filterKeys: ["status", "priority", "showResolved"],
60
63
  defaultSort: sortBy("createdAt", "DESC"),
61
64
  queryKey: ["admin", "support-tickets", "listing"],
62
65
  endpoint: ADMIN_ENDPOINTS.SUPPORT_TICKETS,
@@ -91,8 +94,19 @@ export function AdminSupportTicketsView({ children, ...props }) {
91
94
  },
92
95
  buildFilters: (f) => {
93
96
  const parts = [];
94
- if (f.status && f.status !== "All")
97
+ if (f.status && f.status !== "All") {
95
98
  parts.push(sieveFilter("status", SIEVE_OP.EQ, f.status));
99
+ }
100
+ else if (f.showResolved !== "true") {
101
+ // Hide resolved and closed tickets by default (open-only quick
102
+ // view). Pipe-joined EQ, not NEQ: sievejs parses a same-field pipe
103
+ // as an OR group, upgraded by the Firestore adapter to an IN query.
104
+ // Unlike NEQ, IN has no orderBy-must-match-filtered-field
105
+ // restriction, so this stays compatible with the default createdAt
106
+ // sort. See sieve.ts for the documented Sieve/Firestore
107
+ // incompatibility this avoids.
108
+ parts.push(sieveFilter("status", SIEVE_OP.EQ, "open|in_progress|waiting_on_user"));
109
+ }
96
110
  if (f.priority && f.priority !== "All")
97
111
  parts.push(sieveFilter("priority", SIEVE_OP.EQ, f.priority));
98
112
  return parts.join(",") || undefined;
@@ -105,6 +119,10 @@ export function AdminSupportTicketsView({ children, ...props }) {
105
119
  setDrawerOpen(true);
106
120
  },
107
121
  },
122
+ {
123
+ label: "Open full page",
124
+ onClick: () => router.push(String(ROUTES.ADMIN.SUPPORT_TICKET_BY_ID(row.id))),
125
+ },
108
126
  ] })),
109
127
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_SUPPORT_TICKET_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Priority", tabs: ADMIN_SUPPORT_TICKET_PRIORITY_TABS, value: pendingFilters.priority ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, priority: id })) })] })),
110
128
  };
@@ -39,6 +39,7 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
39
39
  const [description, setDescription] = React.useState("");
40
40
  const [href, setHref] = React.useState("");
41
41
  const [order, setOrder] = React.useState(0);
42
+ const [phase, setPhase] = React.useState(1);
42
43
  const [isActive, setIsActive] = React.useState(true);
43
44
  const [adminOnly, setAdminOnly] = React.useState(false);
44
45
  const [deleteConfirmOpen, setDeleteConfirmOpen] = React.useState(false);
@@ -63,6 +64,7 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
63
64
  setDescription(item.description ?? "");
64
65
  setHref(item.href ?? "");
65
66
  setOrder(typeof item.order === "number" ? item.order : 0);
67
+ setPhase(typeof item.phase === "number" ? item.phase : 1);
66
68
  setIsActive(item.isActive ?? true);
67
69
  setAdminOnly(item.adminOnly ?? false);
68
70
  }, [itemQuery.data]);
@@ -87,6 +89,7 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
87
89
  description: description || "",
88
90
  href: href || "",
89
91
  order,
92
+ phase,
90
93
  isActive,
91
94
  adminOnly,
92
95
  };
@@ -116,7 +119,7 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
116
119
  });
117
120
  const isSubmitting = saveMutation.isPending || itemQuery.isLoading;
118
121
  const canSave = Boolean(groupLabel.trim()) && Boolean(pageLabel.trim()) && Boolean(label.trim());
119
- const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: checklistItemFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(FieldInput, { name: "groupLabel", label: "Group", value: groupLabel, onChange: handleGroupLabelChange, required: true, placeholder: "e.g. Buying", hint: "Top-level accordion section on the Tester Hub." }), _jsx(Input, { label: "Group key", value: groupKey, onChange: (e) => setGroupKey(e.target.value), placeholder: "buying", helperText: "Auto-generated from the group name." })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(FieldInput, { name: "pageLabel", label: "Page", value: pageLabel, onChange: handlePageLabelChange, required: true, placeholder: "e.g. Checkout", hint: "Sub-accordion within the group." }), _jsx(Input, { label: "Page key", value: pageKey, onChange: (e) => setPageKey(e.target.value), placeholder: "checkout", helperText: "Auto-generated from the page name." })] }), _jsx(FieldInput, { name: "label", label: "Test case (Yes/No question)", value: label, onChange: setLabel, required: true, placeholder: "e.g. Coupon discount is correctly reflected in the order total" }), _jsx(FieldTextarea, { name: "description", label: "Description (optional)", value: description, onChange: setDescription, placeholder: "Extra context for the tester." }), _jsx(FieldInput, { name: "href", label: "Deep link (optional)", value: href, onChange: setHref, placeholder: "/cart", hint: "Jumps the tester straight to the feature being tested." }), _jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within the page." }), _jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to testers)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Admin-only (requires canTestAdmin)", checked: adminOnly, onChange: setAdminOnly })] }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
122
+ const formSection = (_jsxs(_Fragment, { children: [_jsx(Form, { schema: checklistItemFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(FieldInput, { name: "groupLabel", label: "Group", value: groupLabel, onChange: handleGroupLabelChange, required: true, placeholder: "e.g. Buying", hint: "Top-level accordion section on the Tester Hub." }), _jsx(Input, { label: "Group key", value: groupKey, onChange: (e) => setGroupKey(e.target.value), placeholder: "buying", helperText: "Auto-generated from the group name." })] }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(FieldInput, { name: "pageLabel", label: "Page", value: pageLabel, onChange: handlePageLabelChange, required: true, placeholder: "e.g. Checkout", hint: "Sub-accordion within the group." }), _jsx(Input, { label: "Page key", value: pageKey, onChange: (e) => setPageKey(e.target.value), placeholder: "checkout", helperText: "Auto-generated from the page name." })] }), _jsx(FieldInput, { name: "label", label: "Test case (Yes/No question)", value: label, onChange: setLabel, required: true, placeholder: "e.g. Coupon discount is correctly reflected in the order total" }), _jsx(FieldTextarea, { name: "description", label: "Description (optional)", value: description, onChange: setDescription, placeholder: "Extra context for the tester." }), _jsx(FieldInput, { name: "href", label: "Deep link (optional)", value: href, onChange: setHref, placeholder: "/cart", hint: "Jumps the tester straight to the feature being tested." }), _jsxs(Div, { layout: "grid", gap: "4", className: "grid-cols-2", children: [_jsx(Input, { label: "Display order", value: String(order), onChange: (e) => setOrder(parseInt(e.target.value, 10) || 0), type: "number", min: 0, helperText: "Lower = shown first within the page." }), _jsx(Input, { label: "Phase", value: String(phase), onChange: (e) => setPhase(Math.max(1, parseInt(e.target.value, 10) || 1)), type: "number", min: 1, helperText: "Test batch this case belongs to \u2014 testers work through one phase at a time." })] }), _jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "lg", border: "default", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Visibility" }), _jsx(Toggle, { label: "Active (visible to testers)", checked: isActive, onChange: setIsActive }), _jsx(Toggle, { label: "Admin-only (requires canTestAdmin)", checked: adminOnly, onChange: setAdminOnly })] }), _jsxs(Row, { gap: "3", padding: "t-xs", children: [_jsx(Button, { type: "submit", isLoading: isSubmitting, disabled: !canSave || isSubmitting, onClick: () => {
120
123
  clearErrors();
121
124
  if (!label.trim()) {
122
125
  setFieldError("label", "Test case label is required");
@@ -16,6 +16,12 @@ const COLUMNS = [
16
16
  sortable: true,
17
17
  render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { weight: "semibold", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", children: row.secondary })] })),
18
18
  },
19
+ {
20
+ key: "phase",
21
+ header: "Phase",
22
+ className: "w-20",
23
+ render: (row) => _jsx(Text, { size: "sm", color: "muted", children: row.phase }),
24
+ },
19
25
  {
20
26
  key: "status",
21
27
  header: "Status",
@@ -41,6 +47,7 @@ export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
41
47
  endpoint: ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEMS,
42
48
  sortOptions: [
43
49
  { value: "order", label: "Order" },
50
+ { value: "phase", label: "Phase" },
44
51
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
45
52
  { value: "label", label: "Test case A–Z" },
46
53
  ],
@@ -49,6 +56,7 @@ export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
49
56
  id: toStringValue(item.id, `checklist-${index}`),
50
57
  primary: toStringValue(item.label, "Untitled test case"),
51
58
  secondary: `${toStringValue(item.groupLabel, "Uncategorized")} / ${toStringValue(item.pageLabel, "")}`,
59
+ phase: typeof item.phase === "number" ? item.phase : 1,
52
60
  status: toStringValue(typeof item.isActive === "boolean"
53
61
  ? item.isActive
54
62
  ? "Active"
@@ -46,6 +46,8 @@ export { AdminStickersView } from "./AdminStickersView";
46
46
  export type { AdminStickersViewProps } from "./AdminStickersView";
47
47
  export { AdminGroupedListingsView } from "./AdminGroupedListingsView";
48
48
  export type { AdminGroupedListingsViewProps } from "./AdminGroupedListingsView";
49
+ export { AdminCarouselGroupEditorView } from "./AdminCarouselGroupEditorView";
50
+ export type { AdminCarouselGroupEditorViewProps } from "./AdminCarouselGroupEditorView";
49
51
  export { AdminBundlesView } from "./AdminBundlesView";
50
52
  export type { AdminBundlesViewProps } from "./AdminBundlesView";
51
53
  export { AdminBundleEditorView } from "./AdminBundleEditorView";
@@ -26,6 +26,7 @@ export { AdminLiveView } from "./AdminLiveView";
26
26
  export { AdminArtView } from "./AdminArtView";
27
27
  export { AdminStickersView } from "./AdminStickersView";
28
28
  export { AdminGroupedListingsView } from "./AdminGroupedListingsView";
29
+ export { AdminCarouselGroupEditorView } from "./AdminCarouselGroupEditorView";
29
30
  // S-SBUNI-4 2026-05-13 — admin bundle CRUD views.
30
31
  export { AdminBundlesView } from "./AdminBundlesView";
31
32
  export { AdminBundleEditorView } from "./AdminBundleEditorView";
@@ -19,12 +19,14 @@ export declare const ALL_TAB: {
19
19
  readonly id: "All";
20
20
  readonly label: "All";
21
21
  };
22
- /** Admin > Products — listing status filter chip set. */
22
+ /** Admin > Products — listing status filter chip set. Real `ProductStatus`
23
+ * is draft|published|in_review|archived — "pending" fixed to "in_review"
24
+ * (its real meaning); the previous id never matched any document. */
23
25
  export declare const ADMIN_PRODUCT_STATUS_TABS: readonly [{
24
26
  readonly id: "All";
25
27
  readonly label: "All";
26
28
  }, {
27
- readonly id: "pending";
29
+ readonly id: "in_review";
28
30
  readonly label: "Pending";
29
31
  }, {
30
32
  readonly id: "published";
@@ -264,13 +266,15 @@ export declare const ADMIN_EVENT_ENTRY_STATUS_TABS: readonly [{
264
266
  readonly id: "CANCELLED";
265
267
  readonly label: "Cancelled";
266
268
  }];
267
- /** Admin > Events — event-state filter chip set. */
269
+ /** Admin > Events — event-state filter chip set. Real `EventStatus` is
270
+ * draft|active|paused|ended|cancelled — there is no "published" value
271
+ * (field-names.ts's copy of this enum is stale; verified against the
272
+ * feature's own events/types/index.ts). A "Published" chip previously
273
+ * returned zero rows every time — removed rather than aliased since
274
+ * "Active" already covers the intended meaning. */
268
275
  export declare const ADMIN_EVENT_STATUS_TABS: readonly [{
269
276
  readonly id: "All";
270
277
  readonly label: "All";
271
- }, {
272
- readonly id: "published";
273
- readonly label: "Published";
274
278
  }, {
275
279
  readonly id: "draft";
276
280
  readonly label: "Draft";
@@ -371,12 +375,18 @@ export declare const EMPTY_TAB: {
371
375
  readonly id: "";
372
376
  readonly label: "All";
373
377
  };
374
- /** Seller > Products — listing-state filter chip set. */
378
+ /** Seller > Products — listing-state filter chip set. Real `ProductStatus`
379
+ * is draft|published|in_review|archived — there is no "active" or "sold"
380
+ * value on the `status` field. "Active" was fixed to "published" (its real
381
+ * meaning); "Sold" was removed since sold-filtering already has its own
382
+ * correct, working mechanism (the `isSold`-driven "Show sold" toolbar
383
+ * toggle in SellerProductsView.tsx) — the status chip duplicated it with
384
+ * a broken `status=="sold"` query that always returned zero rows. */
375
385
  export declare const SELLER_PRODUCT_STATUS_TABS: readonly [{
376
386
  readonly id: "All";
377
387
  readonly label: "All";
378
388
  }, {
379
- readonly id: "active";
389
+ readonly id: "published";
380
390
  readonly label: "Active";
381
391
  }, {
382
392
  readonly id: "draft";
@@ -384,33 +394,36 @@ export declare const SELLER_PRODUCT_STATUS_TABS: readonly [{
384
394
  }, {
385
395
  readonly id: "archived";
386
396
  readonly label: "Archived";
387
- }, {
388
- readonly id: "sold";
389
- readonly label: "Sold";
390
397
  }];
391
- /** Seller > Auctions — auction-state filter chip set. */
398
+ /** Seller > Auctions — auction-state filter chip set. Real `ProductStatus`
399
+ * has no "active"/"ended"/"cancelled" values. "Active" fixed to
400
+ * "published". "Ended" and "Cancelled" removed — "ended" is a derived
401
+ * state (auctionEndDate vs now), not a stored status, and is now handled
402
+ * by SellerAuctionsView's "Show ended" toolbar toggle; there is no stored
403
+ * "cancelled" state for auctions at all (an auction that expires with no
404
+ * bids becomes `archived`, indistinguishable in storage from a listing the
405
+ * seller archived for any other reason — aliasing "Cancelled" to
406
+ * `status==archived` would be misleading, so it's dropped rather than
407
+ * reintroduced under a wrong mapping). */
392
408
  export declare const SELLER_AUCTION_STATUS_TABS: readonly [{
393
409
  readonly id: "All";
394
410
  readonly label: "All";
395
411
  }, {
396
- readonly id: "active";
412
+ readonly id: "published";
397
413
  readonly label: "Active";
398
414
  }, {
399
415
  readonly id: "draft";
400
416
  readonly label: "Draft";
401
- }, {
402
- readonly id: "ended";
403
- readonly label: "Ended";
404
- }, {
405
- readonly id: "cancelled";
406
- readonly label: "Cancelled";
407
417
  }];
408
- /** Seller > Pre-orders — pre-order-state filter chip set. */
418
+ /** Seller > Pre-orders — pre-order-state filter chip set. Same root cause
419
+ * and fix as SELLER_AUCTION_STATUS_TABS above — no "active"/"cancelled"
420
+ * status values exist; pre-order cancellation, if tracked, lives in the
421
+ * separate `preOrderProductionStatus` field, not `status`. */
409
422
  export declare const SELLER_PRE_ORDER_STATUS_TABS: readonly [{
410
423
  readonly id: "All";
411
424
  readonly label: "All";
412
425
  }, {
413
- readonly id: "active";
426
+ readonly id: "published";
414
427
  readonly label: "Active";
415
428
  }, {
416
429
  readonly id: "draft";
@@ -418,53 +431,54 @@ export declare const SELLER_PRE_ORDER_STATUS_TABS: readonly [{
418
431
  }, {
419
432
  readonly id: "archived";
420
433
  readonly label: "Archived";
421
- }, {
422
- readonly id: "cancelled";
423
- readonly label: "Cancelled";
424
434
  }];
425
- /** Seller > Prize Draws — draw-state filter chip set. */
435
+ /** Seller > Prize Draws — draw-state filter chip set. Same root cause and
436
+ * fix as SELLER_AUCTION_STATUS_TABS — "ended" is derived from
437
+ * `prizeRevealWindowEnd` vs now (handled by the "Show closed" toolbar
438
+ * toggle), no stored "cancelled" state exists. */
426
439
  export declare const SELLER_PRIZE_DRAW_STATUS_TABS: readonly [{
427
440
  readonly id: "All";
428
441
  readonly label: "All";
429
442
  }, {
430
- readonly id: "active";
443
+ readonly id: "published";
431
444
  readonly label: "Active";
432
445
  }, {
433
446
  readonly id: "draft";
434
447
  readonly label: "Draft";
435
- }, {
436
- readonly id: "ended";
437
- readonly label: "Ended";
438
- }, {
439
- readonly id: "cancelled";
440
- readonly label: "Cancelled";
441
448
  }];
442
449
  /** Seller > Orders — order-state filter chip set. Subset of
443
450
  * `ADMIN_ORDER_STATUS_TABS` — sellers don't see `RETURN_REQUESTED` until
444
- * the buyer initiates one through support. */
451
+ * the buyer initiates one through support. IDs are lowercase to match the
452
+ * real stored `OrderStatusValues` (Firestore `==` is byte-exact — the
453
+ * previous uppercase ids never matched any document, every chip here
454
+ * silently returned zero rows). */
445
455
  export declare const SELLER_ORDER_STATUS_TABS: readonly [{
446
456
  readonly id: "All";
447
457
  readonly label: "All";
448
458
  }, {
449
- readonly id: "PENDING";
459
+ readonly id: "pending";
450
460
  readonly label: "Pending";
451
461
  }, {
452
- readonly id: "PROCESSING";
462
+ readonly id: "processing";
453
463
  readonly label: "Processing";
454
464
  }, {
455
- readonly id: "SHIPPED";
465
+ readonly id: "shipped";
456
466
  readonly label: "Shipped";
457
467
  }, {
458
- readonly id: "DELIVERED";
468
+ readonly id: "delivered";
459
469
  readonly label: "Delivered";
460
470
  }, {
461
- readonly id: "CANCELLED";
471
+ readonly id: "cancelled";
462
472
  readonly label: "Cancelled";
463
473
  }, {
464
- readonly id: "REFUNDED";
474
+ readonly id: "refunded";
465
475
  readonly label: "Refunded";
466
476
  }];
467
- /** Seller > Offers — offer-state filter chip set. */
477
+ /** Seller > Offers — offer-state filter chip set. Real `OfferStatus` is
478
+ * pending|accepted|declined|countered|expired|withdrawn|paid — there is no
479
+ * "rejected" value, the real one is "declined" (the id below was fixed;
480
+ * the previous "rejected" id always returned zero rows). Label kept as
481
+ * "Rejected" for continuity with existing seller-facing wording. */
468
482
  export declare const SELLER_OFFER_STATUS_TABS: readonly [{
469
483
  readonly id: "All";
470
484
  readonly label: "All";
@@ -475,7 +489,7 @@ export declare const SELLER_OFFER_STATUS_TABS: readonly [{
475
489
  readonly id: "accepted";
476
490
  readonly label: "Accepted";
477
491
  }, {
478
- readonly id: "rejected";
492
+ readonly id: "declined";
479
493
  readonly label: "Rejected";
480
494
  }, {
481
495
  readonly id: "expired";
@@ -10,10 +10,12 @@
10
10
  * primitive and stay in sync when statuses are added.
11
11
  */
12
12
  export const ALL_TAB = { id: "All", label: "All" };
13
- /** Admin > Products — listing status filter chip set. */
13
+ /** Admin > Products — listing status filter chip set. Real `ProductStatus`
14
+ * is draft|published|in_review|archived — "pending" fixed to "in_review"
15
+ * (its real meaning); the previous id never matched any document. */
14
16
  export const ADMIN_PRODUCT_STATUS_TABS = [
15
17
  ALL_TAB,
16
- { id: "pending", label: "Pending" },
18
+ { id: "in_review", label: "Pending" },
17
19
  { id: "published", label: "Published" },
18
20
  { id: "draft", label: "Draft" },
19
21
  { id: "archived", label: "Archived" },
@@ -129,10 +131,14 @@ export const ADMIN_EVENT_ENTRY_STATUS_TABS = [
129
131
  { id: "WAITLISTED", label: "Waitlisted" },
130
132
  { id: "CANCELLED", label: "Cancelled" },
131
133
  ];
132
- /** Admin > Events — event-state filter chip set. */
134
+ /** Admin > Events — event-state filter chip set. Real `EventStatus` is
135
+ * draft|active|paused|ended|cancelled — there is no "published" value
136
+ * (field-names.ts's copy of this enum is stale; verified against the
137
+ * feature's own events/types/index.ts). A "Published" chip previously
138
+ * returned zero rows every time — removed rather than aliased since
139
+ * "Active" already covers the intended meaning. */
133
140
  export const ADMIN_EVENT_STATUS_TABS = [
134
141
  ALL_TAB,
135
- { id: "published", label: "Published" },
136
142
  { id: "draft", label: "Draft" },
137
143
  { id: "active", label: "Active" },
138
144
  { id: "ended", label: "Ended" },
@@ -184,56 +190,78 @@ export const ADMIN_COUPON_TYPE_TABS = [
184
190
  * filter URL with `""` instead of `"All"` so the `"" == no filter` invariant
185
191
  * stays a single shape across queries. */
186
192
  export const EMPTY_TAB = { id: "", label: "All" };
187
- /** Seller > Products — listing-state filter chip set. */
193
+ /** Seller > Products — listing-state filter chip set. Real `ProductStatus`
194
+ * is draft|published|in_review|archived — there is no "active" or "sold"
195
+ * value on the `status` field. "Active" was fixed to "published" (its real
196
+ * meaning); "Sold" was removed since sold-filtering already has its own
197
+ * correct, working mechanism (the `isSold`-driven "Show sold" toolbar
198
+ * toggle in SellerProductsView.tsx) — the status chip duplicated it with
199
+ * a broken `status=="sold"` query that always returned zero rows. */
188
200
  export const SELLER_PRODUCT_STATUS_TABS = [
189
201
  ALL_TAB,
190
- { id: "active", label: "Active" },
202
+ { id: "published", label: "Active" },
191
203
  { id: "draft", label: "Draft" },
192
204
  { id: "archived", label: "Archived" },
193
- { id: "sold", label: "Sold" },
194
205
  ];
195
- /** Seller > Auctions — auction-state filter chip set. */
206
+ /** Seller > Auctions — auction-state filter chip set. Real `ProductStatus`
207
+ * has no "active"/"ended"/"cancelled" values. "Active" fixed to
208
+ * "published". "Ended" and "Cancelled" removed — "ended" is a derived
209
+ * state (auctionEndDate vs now), not a stored status, and is now handled
210
+ * by SellerAuctionsView's "Show ended" toolbar toggle; there is no stored
211
+ * "cancelled" state for auctions at all (an auction that expires with no
212
+ * bids becomes `archived`, indistinguishable in storage from a listing the
213
+ * seller archived for any other reason — aliasing "Cancelled" to
214
+ * `status==archived` would be misleading, so it's dropped rather than
215
+ * reintroduced under a wrong mapping). */
196
216
  export const SELLER_AUCTION_STATUS_TABS = [
197
217
  ALL_TAB,
198
- { id: "active", label: "Active" },
218
+ { id: "published", label: "Active" },
199
219
  { id: "draft", label: "Draft" },
200
- { id: "ended", label: "Ended" },
201
- { id: "cancelled", label: "Cancelled" },
202
220
  ];
203
- /** Seller > Pre-orders — pre-order-state filter chip set. */
221
+ /** Seller > Pre-orders — pre-order-state filter chip set. Same root cause
222
+ * and fix as SELLER_AUCTION_STATUS_TABS above — no "active"/"cancelled"
223
+ * status values exist; pre-order cancellation, if tracked, lives in the
224
+ * separate `preOrderProductionStatus` field, not `status`. */
204
225
  export const SELLER_PRE_ORDER_STATUS_TABS = [
205
226
  ALL_TAB,
206
- { id: "active", label: "Active" },
227
+ { id: "published", label: "Active" },
207
228
  { id: "draft", label: "Draft" },
208
229
  { id: "archived", label: "Archived" },
209
- { id: "cancelled", label: "Cancelled" },
210
230
  ];
211
- /** Seller > Prize Draws — draw-state filter chip set. */
231
+ /** Seller > Prize Draws — draw-state filter chip set. Same root cause and
232
+ * fix as SELLER_AUCTION_STATUS_TABS — "ended" is derived from
233
+ * `prizeRevealWindowEnd` vs now (handled by the "Show closed" toolbar
234
+ * toggle), no stored "cancelled" state exists. */
212
235
  export const SELLER_PRIZE_DRAW_STATUS_TABS = [
213
236
  ALL_TAB,
214
- { id: "active", label: "Active" },
237
+ { id: "published", label: "Active" },
215
238
  { id: "draft", label: "Draft" },
216
- { id: "ended", label: "Ended" },
217
- { id: "cancelled", label: "Cancelled" },
218
239
  ];
219
240
  /** Seller > Orders — order-state filter chip set. Subset of
220
241
  * `ADMIN_ORDER_STATUS_TABS` — sellers don't see `RETURN_REQUESTED` until
221
- * the buyer initiates one through support. */
242
+ * the buyer initiates one through support. IDs are lowercase to match the
243
+ * real stored `OrderStatusValues` (Firestore `==` is byte-exact — the
244
+ * previous uppercase ids never matched any document, every chip here
245
+ * silently returned zero rows). */
222
246
  export const SELLER_ORDER_STATUS_TABS = [
223
247
  ALL_TAB,
224
- { id: "PENDING", label: "Pending" },
225
- { id: "PROCESSING", label: "Processing" },
226
- { id: "SHIPPED", label: "Shipped" },
227
- { id: "DELIVERED", label: "Delivered" },
228
- { id: "CANCELLED", label: "Cancelled" },
229
- { id: "REFUNDED", label: "Refunded" },
248
+ { id: "pending", label: "Pending" },
249
+ { id: "processing", label: "Processing" },
250
+ { id: "shipped", label: "Shipped" },
251
+ { id: "delivered", label: "Delivered" },
252
+ { id: "cancelled", label: "Cancelled" },
253
+ { id: "refunded", label: "Refunded" },
230
254
  ];
231
- /** Seller > Offers — offer-state filter chip set. */
255
+ /** Seller > Offers — offer-state filter chip set. Real `OfferStatus` is
256
+ * pending|accepted|declined|countered|expired|withdrawn|paid — there is no
257
+ * "rejected" value, the real one is "declined" (the id below was fixed;
258
+ * the previous "rejected" id always returned zero rows). Label kept as
259
+ * "Rejected" for continuity with existing seller-facing wording. */
232
260
  export const SELLER_OFFER_STATUS_TABS = [
233
261
  ALL_TAB,
234
262
  { id: "pending", label: "Pending" },
235
263
  { id: "accepted", label: "Accepted" },
236
- { id: "rejected", label: "Rejected" },
264
+ { id: "declined", label: "Rejected" },
237
265
  { id: "expired", label: "Expired" },
238
266
  ];
239
267
  /** Seller > Bids — bid-state filter chip set. Uses the empty-sentinel
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Analytics Rollup Repository
3
+ *
4
+ * Singleton `analytics/dashboardRollup` doc — a pre-computed aggregate that
5
+ * replaces an unbounded `orderRepository.findByStatus("delivered")` scan on
6
+ * every admin dashboard page load. Written daily by the `revenueRollup`
7
+ * scheduled Firebase Function (`appkit/src/_internal/server/jobs/core/revenueRollup.ts`),
8
+ * read by `GET /api/admin/dashboard`.
9
+ */
10
+ import { BaseRepository } from "../../../providers/db-firebase";
11
+ export interface AnalyticsRollupDocument {
12
+ id: string;
13
+ totalRevenue: number;
14
+ deliveredOrderCount: number;
15
+ computedAt: Date;
16
+ }
17
+ export declare class AnalyticsRollupRepository extends BaseRepository<AnalyticsRollupDocument> {
18
+ constructor();
19
+ getDashboardRollup(): Promise<AnalyticsRollupDocument | null>;
20
+ setDashboardRollup(data: {
21
+ totalRevenue: number;
22
+ deliveredOrderCount: number;
23
+ }): Promise<void>;
24
+ }
25
+ export declare const analyticsRollupRepository: AnalyticsRollupRepository;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Analytics Rollup Repository
3
+ *
4
+ * Singleton `analytics/dashboardRollup` doc — a pre-computed aggregate that
5
+ * replaces an unbounded `orderRepository.findByStatus("delivered")` scan on
6
+ * every admin dashboard page load. Written daily by the `revenueRollup`
7
+ * scheduled Firebase Function (`appkit/src/_internal/server/jobs/core/revenueRollup.ts`),
8
+ * read by `GET /api/admin/dashboard`.
9
+ */
10
+ import { BaseRepository } from "../../../providers/db-firebase";
11
+ const ANALYTICS_COLLECTION = "analytics";
12
+ const DASHBOARD_ROLLUP_ID = "dashboardRollup";
13
+ export class AnalyticsRollupRepository extends BaseRepository {
14
+ constructor() {
15
+ super(ANALYTICS_COLLECTION);
16
+ }
17
+ async getDashboardRollup() {
18
+ return this.findById(DASHBOARD_ROLLUP_ID);
19
+ }
20
+ async setDashboardRollup(data) {
21
+ const existing = await this.findById(DASHBOARD_ROLLUP_ID);
22
+ const payload = { ...data, computedAt: new Date() };
23
+ if (existing) {
24
+ await this.update(DASHBOARD_ROLLUP_ID, payload);
25
+ }
26
+ else {
27
+ await this.createWithId(DASHBOARD_ROLLUP_ID, payload);
28
+ }
29
+ }
30
+ }
31
+ export const analyticsRollupRepository = new AnalyticsRollupRepository();
@@ -72,9 +72,16 @@ export class SiteSettingsRepository extends BaseRepository {
72
72
  mergeEncryptedCredentials(existing, updates) {
73
73
  const encrypted = { ...existing };
74
74
  for (const [key, value] of Object.entries(updates)) {
75
- if (value && value.trim()) {
75
+ if (typeof value === "string" && value.trim()) {
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
77
  encrypted[key] = encryptSecret(value.trim());
77
78
  }
79
+ else if (value !== undefined && typeof value !== "string") {
80
+ // Non-string credential fields (e.g. whatsappTemplates: Record<string,string>)
81
+ // are stored as-is — they are not secret values to encrypt.
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ encrypted[key] = value;
84
+ }
78
85
  // Empty / undefined → keep whatever was already stored
79
86
  }
80
87
  return encrypted;
@@ -2,6 +2,7 @@
2
2
  * Admin Feature Firestore Document Types & Constants
3
3
  * Covers: notifications, chat rooms, site settings
4
4
  */
5
+ import type { AboutContentDocument } from "../../about/schemas/firestore";
5
6
  export declare const ADMIN_CHECKOUT_BYPASS_FLAG_KEY: "adminCheckoutBypass";
6
7
  export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_reveal_ready" | "prize_reveal_expired" | "prize_reveal_reminder" | "emi_installment_due_soon" | "emi_installment_overdue" | "payment_review";
7
8
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
@@ -19,6 +20,10 @@ export interface NotificationDocument extends BaseDocument {
19
20
  readAt?: Date;
20
21
  relatedId?: string;
21
22
  relatedType?: "order" | "product" | "bid" | "review" | "blog" | "user" | "offer" | "support_ticket" | "scammer";
23
+ /** Outcome of the async whatsappNotify job for this notification's WhatsApp send, if one was enqueued. */
24
+ whatsappStatus?: "queued" | "sent" | "failed" | "skipped";
25
+ /** jobs/{id} doc that ran (or is running) the WhatsApp send for this notification. */
26
+ whatsappJobId?: string;
22
27
  }
23
28
  export declare const NOTIFICATIONS_COLLECTION: "notifications";
24
29
  export declare const NOTIFICATIONS_INDEXED_FIELDS: readonly ["userId", "isRead", "createdAt", "type"];
@@ -162,6 +167,23 @@ export interface SiteSettingsCredentials {
162
167
  whatsappCloudApiToken?: string;
163
168
  /** Comma-separated digits-only phone numbers that receive order announcements */
164
169
  whatsappAdminNotifyNumbers?: string;
170
+ /**
171
+ * Approved Meta message-template names, one flat string field per
172
+ * order-lifecycle notification type — every `SiteSettingsCredentials`
173
+ * field is stored as an individually-encrypted string (see
174
+ * `getDecryptedCredentials`/`getCredentialsMasked`, which iterate + mask
175
+ * every field as a string), so this can't be a `Record<string,string>`
176
+ * map. Business-initiated WhatsApp sends outside the 24h customer-service
177
+ * window require a pre-approved template.
178
+ */
179
+ whatsappTemplateOrderPlaced?: string;
180
+ whatsappTemplateOrderConfirmed?: string;
181
+ whatsappTemplateOrderShipped?: string;
182
+ whatsappTemplateOrderDelivered?: string;
183
+ whatsappTemplateOrderCancelled?: string;
184
+ whatsappTemplateRefundInitiated?: string;
185
+ /** BCP-47 language code the templates above were approved in, e.g. "en" or "en_US". */
186
+ whatsappTemplateLanguage?: string;
165
187
  }
166
188
  export interface SiteSettingsCredentialsMasked {
167
189
  razorpayKeyId?: string;
@@ -183,6 +205,13 @@ export interface SiteSettingsCredentialsMasked {
183
205
  whatsappPhoneNumberId?: string;
184
206
  whatsappCloudApiToken?: string;
185
207
  whatsappAdminNotifyNumbers?: string;
208
+ whatsappTemplateOrderPlaced?: string;
209
+ whatsappTemplateOrderConfirmed?: string;
210
+ whatsappTemplateOrderShipped?: string;
211
+ whatsappTemplateOrderDelivered?: string;
212
+ whatsappTemplateOrderCancelled?: string;
213
+ whatsappTemplateRefundInitiated?: string;
214
+ whatsappTemplateLanguage?: string;
186
215
  }
187
216
  export interface NotificationChannelConfig {
188
217
  inApp: {
@@ -319,6 +348,19 @@ export interface SiteSettingsDocument extends BaseDocument {
319
348
  /** COD handling fee charged to the buyer: max(codHandlingFeeMin, subtotal × codHandlingFeePercent / 100). */
320
349
  codHandlingFeeMin: number;
321
350
  codHandlingFeePercent: number;
351
+ /** Admin master toggle — whether the WhatsApp order-updates addon is offered at checkout at all. */
352
+ whatsappNotifyFeeEnabled: boolean;
353
+ /** Flat rupee fee charged to the buyer when they opt into the WhatsApp order-updates addon. */
354
+ whatsappNotifyFee: number;
355
+ /** Admin master toggle — whether the gift-wrap addon is offered at checkout at all. */
356
+ giftWrapFeeEnabled: boolean;
357
+ /** Flat rupee fee charged to the buyer when they opt into gift wrap. */
358
+ giftWrapFee: number;
359
+ /** Admin master toggle — whether the shipment-protection addon is offered at checkout at all. */
360
+ shipmentProtectionFeeEnabled: boolean;
361
+ /** Shipment protection fee: max(shipmentProtectionFeeMin, subtotal × shipmentProtectionFeePercent / 100). */
362
+ shipmentProtectionFeePercent: number;
363
+ shipmentProtectionFeeMin: number;
322
364
  sellerShippingFixed: number;
323
365
  platformShippingPercent: number;
324
366
  platformShippingFixedMin: number;
@@ -426,6 +468,10 @@ export interface SiteSettingsDocument extends BaseDocument {
426
468
  codDeposit: number;
427
469
  };
428
470
  };
471
+ /** About Us page content, admin-editable via Site Settings. Optional so
472
+ * pre-existing docs without it still type-check; `about/page.tsx` falls
473
+ * back to i18n defaults when absent. */
474
+ aboutContent?: AboutContentDocument;
429
475
  navbarConfig?: {
430
476
  hiddenNavItems?: string[];
431
477
  };