@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
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
- import { useApiMutation } from "@mohasinac/appkit/client";
4
+ import { useApiMutation, useBulkEvent, RTDB_PATHS } from "@mohasinac/appkit/client";
5
5
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
6
6
  import { sortBy } from "@mohasinac/appkit";
7
- import React, { useCallback, useState } from "react";
7
+ import React, { useCallback, useEffect, useState } from "react";
8
8
  import { useQueryClient } from "@tanstack/react-query";
9
9
  import { Button, ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu, useToast } from "../../../ui";
10
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -33,24 +33,55 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
33
33
  showToast(err?.message ?? "Failed to unsubscribe.", "error");
34
34
  },
35
35
  });
36
+ // Export runs as an async `newsletterExport` job (Async Job Primitive) —
37
+ // the route only enqueues; this subscribes to the same RTDB bulk-event
38
+ // channel every other async job in the admin panel uses.
39
+ const [exporting, setExporting] = useState(false);
40
+ const exportEvent = useBulkEvent({ rtdbPath: RTDB_PATHS.BULK_EVENTS });
36
41
  const handleExportCsv = useCallback(async () => {
42
+ setExporting(true);
37
43
  try {
38
44
  const response = await fetch(ADMIN_ENDPOINTS.NEWSLETTER_EXPORT);
39
45
  if (!response.ok)
40
46
  throw new Error("Export failed");
41
- const blob = await response.blob();
42
- const url = URL.createObjectURL(blob);
43
- const a = document.createElement("a");
44
- a.href = url;
45
- a.download = "newsletter-subscribers.csv";
46
- a.click();
47
- URL.revokeObjectURL(url);
47
+ const body = (await response.json());
48
+ const { jobId, customToken } = body.data ?? {};
49
+ if (jobId && customToken) {
50
+ exportEvent.subscribe(jobId, customToken);
51
+ }
52
+ else {
53
+ throw new Error("Export failed to start");
54
+ }
48
55
  }
49
56
  catch (_err) {
50
57
  void normalizeError(_err);
58
+ setExporting(false);
51
59
  showToast("Failed to export CSV.", "error");
52
60
  }
53
- }, [showToast]);
61
+ }, [showToast, exportEvent]);
62
+ useEffect(() => {
63
+ if (exportEvent.status === "success") {
64
+ setExporting(false);
65
+ const csv = exportEvent.result?.data?.csv;
66
+ if (csv) {
67
+ const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
68
+ const url = URL.createObjectURL(blob);
69
+ const a = document.createElement("a");
70
+ a.href = url;
71
+ a.download = `newsletter-subscribers-${new Date().toISOString().slice(0, 10)}.csv`;
72
+ a.click();
73
+ URL.revokeObjectURL(url);
74
+ }
75
+ else {
76
+ showToast("Export finished with no data.", "error");
77
+ }
78
+ }
79
+ else if (exportEvent.status === "failed" || exportEvent.status === "timeout") {
80
+ setExporting(false);
81
+ showToast(exportEvent.error ?? "Failed to export CSV.", "error");
82
+ }
83
+ // eslint-disable-next-line react-hooks/exhaustive-deps
84
+ }, [exportEvent.status]);
54
85
  if (React.Children.count(children) > 0) {
55
86
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
56
87
  }
@@ -85,7 +116,7 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
85
116
  return mappedRows.length;
86
117
  },
87
118
  buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
88
- toolbarExtra: (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label })),
119
+ toolbarExtra: (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, isLoading: exporting, disabled: exporting, children: ACTIONS.ADMIN["export-csv"].label })),
89
120
  buildBulkActions: onBulkUnsubscribe
90
121
  ? (selection) => [
91
122
  buildBulkAction(ACTIONS.ADMIN["unsubscribe-newsletter"], async () => {
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
8
8
  import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu, useToast } from "../../../ui";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
10
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
11
- import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
11
+ import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
12
12
  import { useBulkAction } from "../../../react";
13
13
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
14
14
  import { DataListingView } from "./DataListingView";
@@ -64,7 +64,7 @@ export function AdminNotificationsView({ children, ...props }) {
64
64
  title: "Notifications",
65
65
  searchPlaceholder: "Search by title or user ID",
66
66
  emptyLabel: "No notifications found",
67
- filterKeys: ["type"],
67
+ filterKeys: ["type", "readState"],
68
68
  defaultSort: sortBy("createdAt", "DESC"),
69
69
  queryKey: ["admin", "notifications", "listing"],
70
70
  endpoint: ADMIN_ENDPOINTS.ADMIN_NOTIFICATIONS,
@@ -83,25 +83,33 @@ export function AdminNotificationsView({ children, ...props }) {
83
83
  type: toStringValue(item.type, ""),
84
84
  })),
85
85
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
86
- buildFilters: (state) => state.type && state.type !== "All" ? sieveFilter("type", SIEVE_OP.EQ, state.type) : undefined,
87
- buildBulkActions: (selection) => [
88
- {
89
- id: ROW_ACTION_ID.MARK_READ,
90
- label: ACTIONS.ADMIN["mark-read"].label,
91
- variant: "primary",
92
- loading: bulkNotifs.isLoading,
93
- onClick: () => {
86
+ buildFilters: (state) => {
87
+ const parts = [];
88
+ if (state.type && state.type !== "All")
89
+ parts.push(sieveFilter("type", SIEVE_OP.EQ, state.type));
90
+ if (state.readState === "unread")
91
+ parts.push(sieveFilter("isRead", SIEVE_OP.EQ, false));
92
+ else if (state.readState === "read")
93
+ parts.push(sieveFilter("isRead", SIEVE_OP.EQ, true));
94
+ return parts.join(",") || undefined;
95
+ },
96
+ // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
97
+ buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.notifications.map((id) => ({
98
+ id,
99
+ label: ROW_ACTION_META[id].label,
100
+ destructive: ROW_ACTION_META[id].destructive,
101
+ variant: id === ROW_ACTION_ID.MARK_READ ? "primary" : "secondary",
102
+ loading: id === ROW_ACTION_ID.MARK_READ ? bulkNotifs.isLoading : undefined,
103
+ onClick: () => {
104
+ if (id === ROW_ACTION_ID.MARK_READ) {
94
105
  void bulkNotifs.execute({ action: "mark_read", ids: selection.selectedIds });
95
106
  selection.clearSelection();
96
- },
97
- },
98
- {
99
- id: ROW_ACTION_ID.DELETE,
100
- label: ACTIONS.ADMIN["delete-notification"].label,
101
- variant: "secondary",
102
- onClick: () => setBulkDeleteIds(selection.selectedIds),
107
+ }
108
+ else {
109
+ setBulkDeleteIds(selection.selectedIds);
110
+ }
103
111
  },
104
- ],
112
+ })),
105
113
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
106
114
  {
107
115
  label: ACTIONS.ADMIN["resend-notification"].label,
@@ -113,7 +121,11 @@ export function AdminNotificationsView({ children, ...props }) {
113
121
  onClick: () => setDeleteTarget(row),
114
122
  },
115
123
  ] })),
116
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Type", tabs: NOTIF_TYPES.map((opt) => ({ id: opt, label: opt })), value: pendingFilters.type || "All", onChange: (v) => setPendingFilters((p) => ({ ...p, type: v })) })),
124
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Type", tabs: NOTIF_TYPES.map((opt) => ({ id: opt, label: opt })), value: pendingFilters.type || "All", onChange: (v) => setPendingFilters((p) => ({ ...p, type: v })) }), _jsx(FilterChipGroup, { label: "Read state", tabs: [
125
+ { id: "", label: "All" },
126
+ { id: "unread", label: "Unread" },
127
+ { id: "read", label: "Read" },
128
+ ], value: pendingFilters.readState ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, readState: id })), allId: "" })] })),
117
129
  };
118
130
  return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(ConfirmDeleteModal, { isOpen: Boolean(deleteTarget) || Boolean(bulkDeleteIds), onClose: () => {
119
131
  setDeleteTarget(null);
@@ -3,10 +3,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React, { useState, useCallback } from "react";
6
+ import { useRouter } from "next/navigation";
6
7
  import { FilterChipGroup, ListingLayout, useToast } from "../../../ui";
7
8
  import { apiClient } from "../../../http";
8
9
  import { QuickEditMenu } from "./QuickEditMenu";
9
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { ROUTES } from "../../../next/routing/route-map";
10
12
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
11
13
  import { ADMIN_ORDER_STATUS_TABS } from "../constants/filter-tabs";
12
14
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
@@ -14,6 +16,7 @@ import { DataListingView } from "./DataListingView";
14
16
  import { AdminOrderEditorView } from "./AdminOrderEditorView";
15
17
  export function AdminOrdersView({ children, ...props }) {
16
18
  const { showToast } = useToast();
19
+ const router = useRouter();
17
20
  const [drawerOpen, setDrawerOpen] = useState(false);
18
21
  const [selectedRow, setSelectedRow] = useState(null);
19
22
  const handleQuickStatus = useCallback(async (id, status) => {
@@ -94,6 +97,10 @@ export function AdminOrdersView({ children, ...props }) {
94
97
  setDrawerOpen(true);
95
98
  },
96
99
  },
100
+ {
101
+ label: "Open full page",
102
+ onClick: () => router.push(String(ROUTES.ADMIN.ORDER_DETAIL(row.id))),
103
+ },
97
104
  {
98
105
  label: "Update status",
99
106
  separator: true,
@@ -104,7 +104,7 @@ export function AdminPayoutsView({ children, ...props }) {
104
104
  title: "Payouts",
105
105
  searchPlaceholder: "Search stores, payout IDs, or order groups",
106
106
  emptyLabel: "No payouts found",
107
- filterKeys: ["status"],
107
+ filterKeys: ["status", "showAllPayouts"],
108
108
  defaultSort: sortBy("createdAt", "DESC"),
109
109
  queryKey: ["admin", "payouts", "listing"],
110
110
  endpoint: ADMIN_ENDPOINTS.PAYOUTS,
@@ -121,7 +121,19 @@ export function AdminPayoutsView({ children, ...props }) {
121
121
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
122
122
  })),
123
123
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
124
- buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
124
+ buildFilters: (state) => {
125
+ // An explicit status chip always wins. Otherwise, hide the noise
126
+ // (already-paid payouts) by default — mirrors the isSold-hide-by-
127
+ // default precedent — with an escape hatch to see everything.
128
+ // Pipe-joined EQ, not NEQ — the Firestore rule requiring orderBy to
129
+ // match the inequality field does not apply to IN queries, so this
130
+ // stays compatible with the default createdAt sort.
131
+ if (state.status && state.status !== "All")
132
+ return sieveFilter("status", SIEVE_OP.EQ, state.status);
133
+ return state.showAllPayouts !== "true"
134
+ ? sieveFilter("status", SIEVE_OP.EQ, "pending|processing|failed|cancelled")
135
+ : undefined;
136
+ },
125
137
  toolbarExtra: (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", action: ACTIONS.ADMIN["calculate-payouts"], size: "sm", isLoading: calculating, disabled: calculating, onClick: handleCalculatePayouts }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label })] })),
126
138
  // Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
127
139
  buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.payouts.map((id) => ({
@@ -140,7 +152,10 @@ export function AdminPayoutsView({ children, ...props }) {
140
152
  disabled: row.status.toLowerCase() === "paid" || row.status.toLowerCase() === "cancelled",
141
153
  },
142
154
  ] })),
143
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
155
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Paid payouts", tabs: [
156
+ { id: "", label: "Hide paid" },
157
+ { id: "true", label: "Show all" },
158
+ ], value: pendingFilters.showAllPayouts ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showAllPayouts: id })), allId: "" })] })),
144
159
  };
145
160
  return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(Modal, { isOpen: markPaidOpen, onClose: closePaidModal, title: "Mark payout as paid", children: _jsxs(Form, { onSubmit: (e) => {
146
161
  e.preventDefault();
@@ -108,7 +108,7 @@ export function AdminProductsView({ children, ...props }) {
108
108
  title: "Products",
109
109
  searchPlaceholder: "Search products, SKUs, or seller names",
110
110
  emptyLabel: "No products found",
111
- filterKeys: ["status", "type", "showSold"],
111
+ filterKeys: ["status", "type", "showSold", "showEnded", "showClosed"],
112
112
  defaultSort: sortBy("createdAt", "DESC"),
113
113
  queryKey: ["admin", "products", "listing"],
114
114
  endpoint: ADMIN_ENDPOINTS.PRODUCTS,
@@ -149,6 +149,15 @@ export function AdminProductsView({ children, ...props }) {
149
149
  const typeFilter = TYPE_FILTER_MAP[state.type];
150
150
  if (typeFilter)
151
151
  parts.push(typeFilter);
152
+ // Ended/closed only apply to auctions/prize-draws — auctionEndDate
153
+ // and prizeRevealWindowEnd do not exist on other listing types, so
154
+ // these are no-ops unless the matching Type tab is also selected.
155
+ if (typeFilter === "listingType==auction" && state.showEnded !== "true") {
156
+ parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, new Date().toISOString()));
157
+ }
158
+ if (typeFilter === "listingType==prize-draw" && state.showClosed !== "true") {
159
+ parts.push(sieveFilter("prizeRevealWindowEnd", SIEVE_OP.GTE, new Date().toISOString()));
160
+ }
152
161
  return parts.join(",") || undefined;
153
162
  },
154
163
  primaryAction: {
@@ -222,7 +231,13 @@ export function AdminProductsView({ children, ...props }) {
222
231
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PRODUCT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_PRODUCT_LISTING_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }), _jsx(FilterChipGroup, { label: "Sold", tabs: [
223
232
  { id: "", label: "Hide sold" },
224
233
  { id: "true", label: "Show sold" },
225
- ], value: pendingFilters.showSold ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showSold: id })), allId: "" })] })),
234
+ ], value: pendingFilters.showSold ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showSold: id })), allId: "" }), _jsx(FilterChipGroup, { label: "Ended", tabs: [
235
+ { id: "", label: "Hide ended" },
236
+ { id: "true", label: "Show ended" },
237
+ ], value: pendingFilters.showEnded ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showEnded: id })), allId: "" }), _jsx(FilterChipGroup, { label: "Closed", tabs: [
238
+ { id: "", label: "Hide closed" },
239
+ { id: "true", label: "Show closed" },
240
+ ], value: pendingFilters.showClosed ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showClosed: id })), allId: "" })] })),
226
241
  renderEditor: ({ editId, closePanel }) => (_jsx(AdminProductEditorView, { productId: editId ?? undefined, initialData: editId ? rawByIdRef.current[editId] : undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })),
227
242
  resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Product" : "Edit Product"),
228
243
  };
@@ -99,8 +99,22 @@ export function AdminReviewsView({ children, ...props }) {
99
99
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
100
100
  buildFilters: (f) => {
101
101
  const parts = [];
102
- if (f.status && f.status !== "All")
102
+ // Default (f.status === "") to pending-only — hides the moderation
103
+ // noise (already-approved/rejected reviews) without a redundant
104
+ // separate toggle, since Status is already a 1-click 3-value chip
105
+ // group. Explicit "All" is reachable via renderFilterPanel allId
106
+ // override below, which makes the FilterChipGroup write the literal
107
+ // string "All" (not "") when clicked — otherwise "never touched" and
108
+ // "explicitly chose All" are indistinguishable.
109
+ if (f.status === "All") {
110
+ // explicit "show everything" — no status filter
111
+ }
112
+ else if (f.status) {
103
113
  parts.push(sieveFilter("status", SIEVE_OP.EQ, f.status));
114
+ }
115
+ else {
116
+ parts.push(sieveFilter("status", SIEVE_OP.EQ, "pending"));
117
+ }
104
118
  if (f.rating && f.rating !== "All")
105
119
  parts.push(sieveFilter("rating", SIEVE_OP.EQ, f.rating));
106
120
  return parts.join(",") || undefined;
@@ -145,7 +159,7 @@ export function AdminReviewsView({ children, ...props }) {
145
159
  onClick: () => handleViewReview(row),
146
160
  },
147
161
  ] })),
148
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_REVIEW_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Rating", tabs: ADMIN_REVIEW_RATING_TABS, value: pendingFilters.rating ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, rating: id })) })] })),
162
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status (defaults to Pending)", tabs: ADMIN_REVIEW_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })), allId: "__all__" }), _jsx(FilterChipGroup, { label: "Rating", tabs: ADMIN_REVIEW_RATING_TABS, value: pendingFilters.rating ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, rating: id })) })] })),
149
163
  };
150
164
  return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(ViewReviewModal, { review: viewReview, isOpen: Boolean(viewReview), onClose: () => setViewReview(null) }), _jsx(Modal, { isOpen: replyOpen, onClose: () => {
151
165
  setReplyOpen(false);
@@ -3,9 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { sieveFilter, SIEVE_OP } 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, Stack, Text } from "../../../ui";
7
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
+ import { ROUTES } from "../../../next/routing/route-map";
9
11
  import { ADMIN_SCAMMER_STATUS_TABS } from "../constants/filter-tabs";
10
12
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
11
13
  import { DataListingView } from "./DataListingView";
@@ -36,6 +38,7 @@ const SCAMMER_COLUMNS = [
36
38
  },
37
39
  ];
38
40
  export function AdminScammersView({ children, ...props }) {
41
+ const router = useRouter();
39
42
  const [drawerOpen, setDrawerOpen] = useState(false);
40
43
  const [selectedRow, setSelectedRow] = useState(null);
41
44
  if (React.Children.count(children) > 0) {
@@ -91,6 +94,10 @@ export function AdminScammersView({ children, ...props }) {
91
94
  setDrawerOpen(true);
92
95
  },
93
96
  },
97
+ {
98
+ label: "Open full page",
99
+ onClick: () => router.push(String(ROUTES.ADMIN.SCAMMER_BY_ID(row.id))),
100
+ },
94
101
  ] })),
95
102
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_SCAMMER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
96
103
  };
@@ -200,6 +200,8 @@ export function AdminSectionsView({ children }) {
200
200
  },
201
201
  onSuccess: async () => {
202
202
  await queryClient.invalidateQueries({ queryKey: ["admin", "sections", "listing"] });
203
+ // Public-facing useHomepageSections() reads under a separate key.
204
+ await queryClient.invalidateQueries({ queryKey: ["homepage-sections"] });
203
205
  toast.showToast(mode === "create" ? "Section created." : "Section updated.", "success");
204
206
  setIsModalOpen(false);
205
207
  },
@@ -215,6 +217,8 @@ export function AdminSectionsView({ children }) {
215
217
  },
216
218
  onSuccess: async () => {
217
219
  await queryClient.invalidateQueries({ queryKey: ["admin", "sections", "listing"] });
220
+ // Public-facing useHomepageSections() reads under a separate key.
221
+ await queryClient.invalidateQueries({ queryKey: ["homepage-sections"] });
218
222
  setReorderServerSnapshot(cloneReorderItems(reorderDraft));
219
223
  setReorderUndoStack([]);
220
224
  toast.showToast("Section order updated.", "success");
@@ -13,7 +13,7 @@ export function AdminSiteConfigGuideView() {
13
13
  { name: "watermark", desc: "Watermark text and opacity applied to all product images via the /api/media proxy." },
14
14
  { name: "fees", desc: "platformFee (%), Razorpay gateway fee pass-through toggle, GST rate on platform fees." },
15
15
  { name: "integrations", desc: "API keys: Razorpay (live/test, disabled by default — manual payment is the default), Resend, WhatsApp Cloud API. Keys are encrypted at rest — never log or export." },
16
- { name: "shipping", desc: "Default shipping carriers, free shipping threshold (paise), COD availability toggle." },
16
+ { name: "shipping", desc: "Default shipping carriers, free shipping threshold (), COD availability toggle." },
17
17
  { name: "auctionConfig", desc: "Global bid increment default, auto-extend window (minutes), minimum auction duration." },
18
18
  { name: "platformLimits", desc: "Cart item cap (50), wishlist cap (20), history cap (50), support ticket limits (2 general + 1 per order)." },
19
19
  { name: "legalPages", desc: "Body HTML for Terms of Service, Privacy Policy, Refund Policy, and Shipping Policy. Rendered on their respective static pages." },