@mohasinac/appkit 4.29.0 → 4.30.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 (160) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
  2. package/dist/_internal/server/jobs/handlers/_helpers.d.ts +22 -0
  3. package/dist/_internal/server/jobs/handlers/_helpers.js +25 -1
  4. package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
  5. package/dist/_internal/shared/features/blog/schema.js +2 -1
  6. package/dist/_internal/shared/features/brands/schema.d.ts +2 -2
  7. package/dist/_internal/shared/features/brands/schema.js +3 -1
  8. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +3 -3
  9. package/dist/_internal/shared/features/categories/bundle-schemas.js +2 -1
  10. package/dist/_internal/shared/features/events/schema.d.ts +2 -2
  11. package/dist/_internal/shared/features/events/schema.js +2 -1
  12. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  13. package/dist/_internal/shared/features/products/schema.js +3 -2
  14. package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
  15. package/dist/_internal/shared/features/reviews/schema.js +2 -1
  16. package/dist/_internal/shared/types/action-result.d.ts +0 -2
  17. package/dist/client/api/surface-error.d.ts +9 -0
  18. package/dist/client/api/surface-error.js +29 -3
  19. package/dist/client/api/useApiMutation.d.ts +10 -0
  20. package/dist/client/api/useApiMutation.js +2 -1
  21. package/dist/core/server-action.d.ts +3 -14
  22. package/dist/core/server-action.js +11 -2
  23. package/dist/errors/error-envelope.d.ts +73 -0
  24. package/dist/errors/error-envelope.js +96 -0
  25. package/dist/errors/error-handler.js +14 -7
  26. package/dist/features/account/components/NotificationBell.js +1 -1
  27. package/dist/features/account/components/UserSidebar.js +2 -2
  28. package/dist/features/account/components/UserSupportView.js +2 -6
  29. package/dist/features/account/hooks/useProfile.d.ts +6 -0
  30. package/dist/features/account/hooks/useProfile.js +1 -0
  31. package/dist/features/admin/components/AdminAdEditorView.js +1 -3
  32. package/dist/features/admin/components/AdminAddressClustersView.js +1 -3
  33. package/dist/features/admin/components/AdminAddressEditorView.js +2 -4
  34. package/dist/features/admin/components/AdminAddressesView.js +1 -3
  35. package/dist/features/admin/components/AdminAllEventEntriesView.js +1 -3
  36. package/dist/features/admin/components/AdminBidsView.js +1 -3
  37. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  38. package/dist/features/admin/components/AdminBrandEditorView.js +4 -5
  39. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +1 -3
  40. package/dist/features/admin/components/AdminCategoryEditorView.js +2 -4
  41. package/dist/features/admin/components/AdminContactEditorView.js +1 -3
  42. package/dist/features/admin/components/AdminContactView.js +2 -6
  43. package/dist/features/admin/components/AdminCouponEditorView.js +2 -4
  44. package/dist/features/admin/components/AdminEmployeeEditorView.js +3 -9
  45. package/dist/features/admin/components/AdminFaqEditorView.js +2 -4
  46. package/dist/features/admin/components/AdminFeatureEditorView.js +0 -9
  47. package/dist/features/admin/components/AdminFeatureFlagsView.js +2 -4
  48. package/dist/features/admin/components/AdminNavEditorView.js +1 -3
  49. package/dist/features/admin/components/AdminNavigationView.js +3 -3
  50. package/dist/features/admin/components/AdminNewsletterView.js +1 -3
  51. package/dist/features/admin/components/AdminNotificationsView.js +2 -6
  52. package/dist/features/admin/components/AdminOffersView.js +1 -3
  53. package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
  54. package/dist/features/admin/components/AdminPaymentClustersView.js +1 -3
  55. package/dist/features/admin/components/AdminPaymentMethodsView.js +1 -3
  56. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +2 -4
  57. package/dist/features/admin/components/AdminProductEditorView.js +2 -4
  58. package/dist/features/admin/components/AdminReturnRequestsView.js +2 -6
  59. package/dist/features/admin/components/AdminReviewsView.js +2 -6
  60. package/dist/features/admin/components/AdminScammerEditorView.js +2 -6
  61. package/dist/features/admin/components/AdminSectionsView.js +2 -6
  62. package/dist/features/admin/components/AdminSessionsView.js +1 -3
  63. package/dist/features/admin/components/AdminSidebar.js +2 -2
  64. package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
  65. package/dist/features/admin/components/AdminStoreEditorView.js +1 -3
  66. package/dist/features/admin/components/AdminStoresView.js +2 -6
  67. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -6
  68. package/dist/features/admin/components/AdminSupportTicketDetailView.js +7 -21
  69. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -4
  70. package/dist/features/admin/components/AdminUserEditorView.js +6 -18
  71. package/dist/features/admin/components/AdminUsersView.js +2 -6
  72. package/dist/features/admin/components/BrandQuickCreateForm.js +2 -4
  73. package/dist/features/admin/components/CategoryQuickCreateForm.js +2 -4
  74. package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
  75. package/dist/features/auth/components/ForgotPasswordView.js +1 -1
  76. package/dist/features/auth/components/LoginForm.js +1 -1
  77. package/dist/features/auth/components/RegisterForm.js +1 -1
  78. package/dist/features/auth/components/ResetPasswordView.js +1 -1
  79. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  80. package/dist/features/auth/repository/session.repository.js +13 -1
  81. package/dist/features/blog/actions/blog-actions.d.ts +14 -14
  82. package/dist/features/blog/actions/blog-actions.js +6 -8
  83. package/dist/features/blog/schemas/index.d.ts +1 -1
  84. package/dist/features/blog/schemas/index.js +2 -4
  85. package/dist/features/categories/api/[id]/route.js +2 -1
  86. package/dist/features/categories/api/route.js +2 -1
  87. package/dist/features/events/schemas/index.d.ts +1 -1
  88. package/dist/features/events/schemas/index.js +2 -4
  89. package/dist/features/faq/components/ContactCTA.js +1 -1
  90. package/dist/features/filters/AsyncFacetSection.js +1 -1
  91. package/dist/features/filters/FilterFacetSection.js +1 -1
  92. package/dist/features/filters/RangeFilter.js +1 -1
  93. package/dist/features/filters/SwitchFilter.js +1 -1
  94. package/dist/features/homepage/api/carousel/[id]/route.js +4 -3
  95. package/dist/features/homepage/api/carousel/route.js +4 -3
  96. package/dist/features/homepage/api/route.js +3 -2
  97. package/dist/features/homepage/components/GoogleReviewsSection.js +1 -1
  98. package/dist/features/homepage/components/HeroBanner.js +1 -1
  99. package/dist/features/homepage/components/HeroCarousel.js +1 -1
  100. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  101. package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
  102. package/dist/features/layout/AppLayoutShell.js +4 -4
  103. package/dist/features/layout/BottomNavbar.js +2 -2
  104. package/dist/features/layout/MainNavbar.js +1 -1
  105. package/dist/features/layout/TitleBarLayout.js +24 -12
  106. package/dist/features/media/AvatarUpload.js +28 -10
  107. package/dist/features/media/modals/ImageCropModal.js +1 -1
  108. package/dist/features/media/modals/ImageEditor.js +1 -1
  109. package/dist/features/products/components/MakeOfferButton.js +1 -1
  110. package/dist/features/reviews/api/[id]/route.js +3 -2
  111. package/dist/features/reviews/api/route.js +3 -2
  112. package/dist/features/search/components/Search.js +2 -2
  113. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +3 -3
  114. package/dist/features/seller/components/SellerSidebar.js +2 -2
  115. package/dist/features/store-extensions/components/ReviewDecisionModal.js +1 -1
  116. package/dist/features/stores/repository/store.repository.d.ts +21 -0
  117. package/dist/features/stores/repository/store.repository.js +32 -0
  118. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -2
  119. package/dist/index.d.ts +3 -2
  120. package/dist/index.js +4 -1
  121. package/dist/next/api/api-response.d.ts +29 -12
  122. package/dist/next/api/api-response.js +9 -8
  123. package/dist/next/api/apiHandler.d.ts +6 -1
  124. package/dist/next/api/apiHandler.js +13 -1
  125. package/dist/next/api/routeHandler.js +6 -6
  126. package/dist/next/middleware/pii-scrubber.js +5 -2
  127. package/dist/providers/db-firebase/base.repository.d.ts +14 -0
  128. package/dist/providers/db-firebase/base.repository.js +22 -0
  129. package/dist/providers/db-firebase/sieve.d.ts +14 -1
  130. package/dist/providers/db-firebase/sieve.js +3 -1
  131. package/dist/security/pii-encrypt.d.ts +0 -4
  132. package/dist/security/pii-encrypt.js +79 -13
  133. package/dist/security/pii-mask.d.ts +39 -1
  134. package/dist/security/pii-mask.js +33 -4
  135. package/dist/security/pii-redact.d.ts +0 -13
  136. package/dist/security/pii-redact.js +3 -1
  137. package/dist/security/pii-schemas.d.ts +0 -2
  138. package/dist/security/pii-schemas.js +16 -2
  139. package/dist/security/settings-encryption.js +31 -3
  140. package/dist/seed/stores-seed-data.js +18 -0
  141. package/dist/seed/test-utils.js +6 -2
  142. package/dist/ui/DataTable.js +2 -2
  143. package/dist/ui/components/CollapsibleSection.js +1 -1
  144. package/dist/ui/components/ImageGallery.js +1 -1
  145. package/dist/ui/components/ListingLayout.js +1 -1
  146. package/dist/ui/forms/FormShell.d.ts +0 -19
  147. package/dist/ui/forms/FormShell.js +4 -23
  148. package/dist/ui/forms/apply-zod-issues.d.ts +34 -0
  149. package/dist/ui/forms/apply-zod-issues.js +38 -0
  150. package/dist/ui/forms/index.d.ts +2 -1
  151. package/dist/ui/forms/index.js +2 -1
  152. package/dist/ui/icons/icon-registry.d.ts +8 -0
  153. package/dist/ui/icons/icon-registry.js +13 -1
  154. package/dist/utils/action-response.d.ts +1 -1
  155. package/dist/utils/media-url.d.ts +49 -0
  156. package/dist/utils/media-url.js +72 -1
  157. package/dist/validation/schemas.d.ts +15 -2
  158. package/dist/validation/schemas.js +18 -22
  159. package/package.json +1 -1
  160. package/scripts/seed-cli.mjs +10 -2
@@ -7,7 +7,6 @@ import { useQuery } from "@tanstack/react-query";
7
7
  import { Button, Checkbox, ConfirmDeleteModal, Div, Form, Grid, Input, Label, Select, Stack, StackedViewShell, Text, Toggle, useToast, } from "../../../ui";
8
8
  import { apiClient } from "../../../http";
9
9
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
- import { ERROR_MESSAGES } from "../../../errors/messages";
11
10
  import { FormErrorSummary, applyZodIssues } from "../../../ui/forms";
12
11
  import { productFeatureFormSchema } from "../../products/schemas/product-features.validators";
13
12
  import { PRODUCT_FEATURE_CATEGORY_OPTIONS, PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER, PRODUCT_FEATURE_ICON_COLOR_OPTIONS, PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS, PRODUCT_FEATURE_SCOPE_OPTIONS, } from "../../products/constants/product-features.constants";
@@ -116,12 +115,6 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
116
115
  if (onSaved && id)
117
116
  onSaved(String(id));
118
117
  },
119
- onError: (err) => {
120
- showToast(err?.message ??
121
- (isEdit
122
- ? ERROR_MESSAGES.PRODUCT_FEATURES.UPDATE_FAILED
123
- : ERROR_MESSAGES.PRODUCT_FEATURES.CREATE_FAILED), "error");
124
- },
125
118
  });
126
119
  const deleteMutation = useApiMutation({
127
120
  mutationFn: () => apiClient.delete(byIdEndpoint(featureId)),
@@ -130,8 +123,6 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
130
123
  if (onDeleted)
131
124
  onDeleted();
132
125
  },
133
- onError: (err) => showToast(err?.message ??
134
- ERROR_MESSAGES.PRODUCT_FEATURES.DELETE_FAILED, "error"),
135
126
  });
136
127
  const isSubmitting = saveMutation.isPending || featureQuery.isLoading;
137
128
  const storeOptions = React.useMemo(() => [
@@ -45,7 +45,7 @@ const CATEGORY_TYPE_FLAGS = [
45
45
  ];
46
46
  function AccordionSection({ title, defaultOpen = true, children }) {
47
47
  const [open, setOpen] = React.useState(defaultOpen);
48
- return (_jsxs(Div, { className: `${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => setOpen((v) => !v), paddingX: "md", paddingY: "md", className: "w-full text-left bg-[var(--appkit-color-surface-elevated)]/60", "aria-expanded": open, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: title }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform ${open ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }) }), open && (_jsx(Div, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", children: children }))] }));
48
+ return (_jsxs(Div, { className: `${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => setOpen((v) => !v), paddingX: "md", paddingY: "md", className: "w-full text-left bg-[var(--appkit-color-surface-elevated)]/60", "aria-expanded": open, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: title }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform ${open ? "rotate-180" : ""} shrink-0`, fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }) }), open && (_jsx(Div, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", children: children }))] }));
49
49
  }
50
50
  function FlagRow({ label, desc, checked, onChange, rollout, onRolloutChange, showRollout }) {
51
51
  return (_jsxs(Row, { surface: "default", padding: "inline", align: "center", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Toggle, { checked: checked, onChange: onChange, label: label }), _jsx(Text, { className: "mt-0.5 ml-10 truncate", color: "faint", size: "xs", children: desc })] }), showRollout && (_jsxs(Stack, { className: "w-28 shrink-0", gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Rollout %" }), _jsx(Input, { type: "number", min: 0, max: 100, value: rollout ?? 100, onChange: (e) => onRolloutChange?.(Math.min(100, Math.max(0, Number(e.target.value)))), disabled: !checked, className: "w-full" })] }))] }));
@@ -87,6 +87,7 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
87
87
  categoryTypes: { ...(prev.categoryTypes ?? {}), [key]: val },
88
88
  }));
89
89
  const saveFlags = useApiMutation({
90
+ errorMessage: "Failed to save feature flags.",
90
91
  mutationFn: async () => {
91
92
  await apiClient.put(ADMIN_ENDPOINTS.FEATURE_FLAGS, { flags, rollouts });
92
93
  },
@@ -94,9 +95,6 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
94
95
  await queryClient.invalidateQueries({ queryKey: ["admin", "feature-flags"] });
95
96
  showToast("Feature flags saved.", "success");
96
97
  },
97
- onError: () => {
98
- showToast("Failed to save feature flags.", "error");
99
- },
100
98
  });
101
99
  const defaultFlags = () => (_jsxs(Form, { schema: featureFlagsUpdateSchema, onSubmit: (event) => {
102
100
  event.preventDefault();
@@ -52,6 +52,7 @@ export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions
52
52
  }, [open, item]);
53
53
  const { showToast } = useToast();
54
54
  const saveMutation = useApiMutation({
55
+ errorMessage: "Failed to save nav item.",
55
56
  mutationFn: async () => {
56
57
  const payload = {
57
58
  label,
@@ -71,9 +72,6 @@ export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions
71
72
  onSaved();
72
73
  onClose();
73
74
  },
74
- onError: (err) => {
75
- showToast(err?.message ?? "Failed to save nav item.", "error");
76
- },
77
75
  });
78
76
  const canSave = Boolean(label.trim()) && Boolean(href.trim());
79
77
  const parentSelectOptions = [
@@ -34,24 +34,25 @@ export function AdminNavigationView({ labels = {}, ...rest }) {
34
34
  const items = data?.items ?? [];
35
35
  const sorted = [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
36
36
  const visibilityMutation = useApiMutation({
37
+ errorMessage: "Failed to update visibility.",
37
38
  mutationFn: async ({ id, isVisible }) => {
38
39
  await apiClient.patch(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(id), { isVisible });
39
40
  },
40
41
  onSuccess: () => {
41
42
  queryClient.invalidateQueries({ queryKey: ["admin", "navigation"] });
42
43
  },
43
- onError: () => showToast("Failed to update visibility.", "error"),
44
44
  });
45
45
  const reorderMutation = useApiMutation({
46
+ errorMessage: "Failed to reorder.",
46
47
  mutationFn: async ({ id, newOrder }) => {
47
48
  await apiClient.patch(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(id), { order: newOrder });
48
49
  },
49
50
  onSuccess: () => {
50
51
  queryClient.invalidateQueries({ queryKey: ["admin", "navigation"] });
51
52
  },
52
- onError: () => showToast("Failed to reorder.", "error"),
53
53
  });
54
54
  const deleteMutation = useApiMutation({
55
+ errorMessage: "Failed to delete nav item.",
55
56
  mutationFn: async (id) => {
56
57
  await apiClient.delete(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(id));
57
58
  },
@@ -60,7 +61,6 @@ export function AdminNavigationView({ labels = {}, ...rest }) {
60
61
  queryClient.invalidateQueries({ queryKey: ["admin", "navigation"] });
61
62
  setDeleteTarget(null);
62
63
  },
63
- onError: () => showToast("Failed to delete nav item.", "error"),
64
64
  });
65
65
  const handleMoveUp = (idx) => {
66
66
  if (idx === 0)
@@ -21,6 +21,7 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
21
21
  const [unsubscribeOpen, setUnsubscribeOpen] = useState(false);
22
22
  const [selectedRow, setSelectedRow] = useState(null);
23
23
  const unsubscribeMutation = useApiMutation({
24
+ errorMessage: "Failed to unsubscribe.",
24
25
  mutationFn: async (id) => {
25
26
  await apiClient.delete(ADMIN_ENDPOINTS.NEWSLETTER_BY_ID(id));
26
27
  },
@@ -30,9 +31,6 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
30
31
  setUnsubscribeOpen(false);
31
32
  setSelectedRow(null);
32
33
  },
33
- onError: (err) => {
34
- showToast(err?.message ?? "Failed to unsubscribe.", "error");
35
- },
36
34
  });
37
35
  // Export runs as an async `newsletterExport` job (Async Job Primitive) —
38
36
  // the route only enqueues; this subscribes to the same RTDB bulk-event
@@ -34,6 +34,7 @@ export function AdminNotificationsView({ children, ...props }) {
34
34
  },
35
35
  });
36
36
  const deleteMutation = useApiMutation({
37
+ errorMessage: "Failed to delete notification.",
37
38
  mutationFn: async (id) => {
38
39
  await apiClient.delete(ADMIN_ENDPOINTS.ADMIN_NOTIFICATION_BY_ID(id));
39
40
  },
@@ -42,11 +43,9 @@ export function AdminNotificationsView({ children, ...props }) {
42
43
  queryClient.invalidateQueries({ queryKey: ["admin", "notifications"] });
43
44
  setDeleteTarget(null);
44
45
  },
45
- onError: (err) => {
46
- showToast(err?.message ?? "Failed to delete notification.", "error");
47
- },
48
46
  });
49
47
  const resendMutation = useApiMutation({
48
+ errorMessage: "Failed to resend notification.",
50
49
  mutationFn: async (id) => {
51
50
  await apiClient.post(ADMIN_ENDPOINTS.ADMIN_NOTIFICATION_RESEND(id), {});
52
51
  },
@@ -54,9 +53,6 @@ export function AdminNotificationsView({ children, ...props }) {
54
53
  showToast("Notification resent.", "success");
55
54
  queryClient.invalidateQueries({ queryKey: ["admin", "notifications"] });
56
55
  },
57
- onError: (err) => {
58
- showToast(err?.message ?? "Failed to resend notification.", "error");
59
- },
60
56
  });
61
57
  if (React.Children.count(children) > 0) {
62
58
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
@@ -83,6 +83,7 @@ export function AdminOffersView({ children, ...props }) {
83
83
  }
84
84
  };
85
85
  const cancelMutation = useApiMutation({
86
+ errorMessage: "Failed to cancel offer.",
86
87
  mutationFn: async ({ offerId, reason }) => {
87
88
  await apiClient.patch(ADMIN_ENDPOINTS.OFFER_BY_ID(offerId), {
88
89
  status: "expired",
@@ -95,9 +96,6 @@ export function AdminOffersView({ children, ...props }) {
95
96
  setCancelOpen(false);
96
97
  setSelectedRow(null);
97
98
  },
98
- onError: (err) => {
99
- showToast(err?.message ?? "Failed to cancel offer.", "error");
100
- },
101
99
  });
102
100
  if (React.Children.count(children) > 0) {
103
101
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
@@ -69,6 +69,7 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
69
69
  }
70
70
  }, [open, currentStatus]);
71
71
  const saveMutation = useApiMutation({
72
+ errorMessage: "Failed to update order.",
72
73
  mutationFn: async () => {
73
74
  const payload = {
74
75
  status,
@@ -90,9 +91,6 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
90
91
  queryClient.invalidateQueries({ queryKey: ["admin", "orders"] });
91
92
  onClose();
92
93
  },
93
- onError: (err) => {
94
- showToast(err?.message ?? "Failed to update order.", "error");
95
- },
96
94
  });
97
95
  const handleVerifyPayment = async () => {
98
96
  if (!orderId)
@@ -21,6 +21,7 @@ export function AdminPaymentClustersView(_props) {
21
21
  },
22
22
  });
23
23
  const flagMutation = useApiMutation({
24
+ errorMessage: "Failed to flag payment method.",
24
25
  mutationFn: async (id) => {
25
26
  await apiClient.patch(ADMIN_ENDPOINTS.PAYMENT_METHOD_BY_ID(id), { action: "flag_suspicious" });
26
27
  },
@@ -28,9 +29,6 @@ export function AdminPaymentClustersView(_props) {
28
29
  showToast("Payment method flagged as suspicious.", "success");
29
30
  void queryClient.invalidateQueries({ queryKey: ["admin", "payment-clusters"] });
30
31
  },
31
- onError: (err) => {
32
- showToast(err.message ?? "Failed to flag payment method.", "error");
33
- },
34
32
  });
35
33
  const clusters = data?.clusters ?? [];
36
34
  return (_jsxs(Stack, { gap: "lg", padding: "page", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", color: "primary", children: "Payment Method Clusters" }), _jsx(Text, { size: "sm", color: "muted", className: "mt-1", children: "Multiple accounts sharing the same payment identifier. Flagging is informational only \u2014 users are not blocked." })] }), isLoading && (_jsx(Row, { justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), error && (_jsx(Div, { surface: "danger-surface", color: "error", padding: "inline", rounded: "xl", textSize: "sm", children: "Failed to load payment clusters." })), !isLoading && clusters.length === 0 && (_jsx(Div, { surface: "muted", padding: "y-4xl", rounded: "xl", className: "text-center", children: _jsx(Text, { color: "muted", size: "sm", children: "No shared payment identifiers found." }) })), clusters.map((cluster) => (_jsxs(Stack, { surface: "card", padding: "md", gap: "md", rounded: "xl", border: "default", children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", color: "muted", weight: "medium", children: "Identifier Hash" }), _jsx(Span, { size: "xs", color: "muted", className: "font-mono truncate max-w-xs", children: cluster.identifierHash })] }), _jsxs(Badge, { variant: "secondary", size: "sm", children: [cluster.methods.length, " accounts"] })] }), _jsx(Stack, { gap: "xs", children: cluster.methods.map((m) => (_jsxs(Row, { justify: "between", align: "center", gap: "sm", surface: "muted", padding: "inlineSm", rounded: "lg", children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", color: "primary", className: "truncate", children: [m.type.toUpperCase(), " \u00B7 ", m.displayLabel] }), _jsxs(Text, { size: "xs", color: "muted", children: ["User: ", m.userId] })] }), _jsxs(Row, { gap: "xs", align: "center", children: [m.banStatus && (_jsx(Badge, { variant: BAN_BADGE[m.banStatus] ?? "secondary", size: "sm", children: m.banStatus.replace(/_/g, " ") })), m.banStatus !== "suspicious" && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => flagMutation.mutate(m.id), disabled: flagMutation.isPending, children: "Flag Suspicious" }))] })] }, m.id))) })] }, cluster.identifierHash)))] }));
@@ -127,6 +127,7 @@ export function AdminPaymentMethodsView(_props) {
127
127
  const queryClient = useQueryClient();
128
128
  const { showToast } = useToast();
129
129
  const actionMutation = useApiMutation({
130
+ errorMessage: "Failed to update payment method.",
130
131
  mutationFn: async ({ id, action }) => {
131
132
  await apiClient.patch(ADMIN_ENDPOINTS.PAYMENT_METHOD_BY_ID(id), { action });
132
133
  },
@@ -134,9 +135,6 @@ export function AdminPaymentMethodsView(_props) {
134
135
  showToast("Payment method updated.", "success");
135
136
  void queryClient.invalidateQueries({ queryKey: ["admin", "payment-methods"] });
136
137
  },
137
- onError: (err) => {
138
- showToast(err.message ?? "Failed to update payment method.", "error");
139
- },
140
138
  });
141
139
  const handleAction = (id, action) => {
142
140
  actionMutation.mutate({ id, action });
@@ -15,12 +15,13 @@ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
15
15
  import { payoutMarkPaidSchema } from "../schemas/small-forms";
16
16
  import { FieldInput } from "../../../ui/forms/FieldInput";
17
17
  import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
18
- import { applyZodIssues } from "../../../ui/forms/FormShell";
18
+ import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
19
19
  export function AdminPayoutMarkPaidModal({ isOpen, payoutId, onClose, onSuccess }) {
20
20
  const [transactionId, setTransactionId] = useState("");
21
21
  const { showToast } = useToast();
22
22
  const queryClient = useQueryClient();
23
23
  const markPaid = useApiMutation({
24
+ errorMessage: "Failed to update payout.",
24
25
  mutationFn: () => {
25
26
  if (!payoutId)
26
27
  throw new Error("No payout selected");
@@ -36,9 +37,6 @@ export function AdminPayoutMarkPaidModal({ isOpen, payoutId, onClose, onSuccess
36
37
  onSuccess?.();
37
38
  onClose();
38
39
  },
39
- onError: () => {
40
- showToast("Failed to update payout.", "error");
41
- },
42
40
  });
43
41
  const handleClose = () => {
44
42
  setTransactionId("");
@@ -156,6 +156,7 @@ export function AdminProductEditorView({ productId, onSaved, onDeleted, embedded
156
156
  }));
157
157
  };
158
158
  const saveMutation = useApiMutation({
159
+ errorMessage: "Failed to save product.",
159
160
  mutationFn: async () => {
160
161
  const payload = applyMode(product, mode);
161
162
  if (isEdit) {
@@ -176,19 +177,16 @@ export function AdminProductEditorView({ productId, onSaved, onDeleted, embedded
176
177
  if (issues && issues.length > 0) {
177
178
  applyZodIssues(issues, setFieldError);
178
179
  }
179
- showToast(err?.message ?? "Failed to save product.", "error");
180
180
  },
181
181
  });
182
182
  const deleteMutation = useApiMutation({
183
+ errorMessage: "Failed to delete product.",
183
184
  mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.PRODUCT_BY_ID(productId)),
184
185
  onSuccess: () => {
185
186
  showToast("Product deleted.", "success");
186
187
  if (onDeleted)
187
188
  onDeleted();
188
189
  },
189
- onError: (err) => {
190
- showToast(err?.message ?? "Failed to delete product.", "error");
191
- },
192
190
  });
193
191
  const isSubmitting = saveMutation.isPending || productQuery.isLoading;
194
192
  const actionSidebar = (_jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Card, { variant: "outlined", padding: "md", spacing: "sm", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Status" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: isEdit ? product.status ?? "—" : "New" }), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "submit", form: "product-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !product.title || isSubmitting, children: isEdit ? "Save changes" : "Create product" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete product" }))] }) }));
@@ -18,6 +18,7 @@ export function AdminReturnRequestsView(_props) {
18
18
  const [rejectOpen, setRejectOpen] = useState(false);
19
19
  const [selectedRow, setSelectedRow] = useState(null);
20
20
  const approveMutation = useApiMutation({
21
+ errorMessage: "Failed to approve return.",
21
22
  mutationFn: async (orderId) => {
22
23
  await apiClient.patch(ADMIN_ENDPOINTS.ORDER_BY_ID(orderId), { status: "refunded" });
23
24
  },
@@ -28,11 +29,9 @@ export function AdminReturnRequestsView(_props) {
28
29
  setApproveOpen(false);
29
30
  setSelectedRow(null);
30
31
  },
31
- onError: (err) => {
32
- showToast(err?.message ?? "Failed to approve return.", "error");
33
- },
34
32
  });
35
33
  const rejectMutation = useApiMutation({
34
+ errorMessage: "Failed to reject return.",
36
35
  mutationFn: async (orderId) => {
37
36
  await apiClient.patch(ADMIN_ENDPOINTS.ORDER_BY_ID(orderId), { status: "delivered" });
38
37
  },
@@ -43,9 +42,6 @@ export function AdminReturnRequestsView(_props) {
43
42
  setRejectOpen(false);
44
43
  setSelectedRow(null);
45
44
  },
46
- onError: (err) => {
47
- showToast(err?.message ?? "Failed to reject return.", "error");
48
- },
49
45
  });
50
46
  const config = {
51
47
  portal: "admin",
@@ -22,6 +22,7 @@ export function AdminReviewsView({ children, ...props }) {
22
22
  const [replyTarget, setReplyTarget] = useState(null);
23
23
  const [replyText, setReplyText] = useState("");
24
24
  const patchMutation = useApiMutation({
25
+ errorMessage: "Failed to update review.",
25
26
  mutationFn: async ({ id, payload }) => {
26
27
  await apiClient.patch(ADMIN_ENDPOINTS.REVIEW_BY_ID(id), payload);
27
28
  },
@@ -29,11 +30,9 @@ export function AdminReviewsView({ children, ...props }) {
29
30
  showToast("Review updated.", "success");
30
31
  queryClient.invalidateQueries({ queryKey: ["admin", "reviews"] });
31
32
  },
32
- onError: (err) => {
33
- showToast(err?.message ?? "Failed to update review.", "error");
34
- },
35
33
  });
36
34
  const replyMutation = useApiMutation({
35
+ errorMessage: "Failed to save reply.",
37
36
  mutationFn: async () => {
38
37
  await apiClient.patch(ADMIN_ENDPOINTS.REVIEW_BY_ID(replyTarget.id), { adminReply: replyText });
39
38
  },
@@ -44,9 +43,6 @@ export function AdminReviewsView({ children, ...props }) {
44
43
  setReplyText("");
45
44
  setReplyTarget(null);
46
45
  },
47
- onError: (err) => {
48
- showToast(err?.message ?? "Failed to save reply.", "error");
49
- },
50
46
  });
51
47
  const handleViewReview = useCallback((rr) => {
52
48
  const raw = rr._raw ?? {};
@@ -26,6 +26,7 @@ export function AdminScammerEditorView({ open, onClose, scammerId, displayNames
26
26
  }
27
27
  }, [open, currentStatus, verificationNote]);
28
28
  const updateMutation = useApiMutation({
29
+ errorMessage: "Failed to update profile.",
29
30
  mutationFn: async () => {
30
31
  await apiClient.patch(ADMIN_ENDPOINTS.SCAMMER_BY_ID(scammerId), {
31
32
  status,
@@ -37,11 +38,9 @@ export function AdminScammerEditorView({ open, onClose, scammerId, displayNames
37
38
  queryClient.invalidateQueries({ queryKey: ["admin", "scammers"] });
38
39
  onClose();
39
40
  },
40
- onError: (err) => {
41
- showToast(err?.message ?? "Failed to update profile.", "error");
42
- },
43
41
  });
44
42
  const deleteMutation = useApiMutation({
43
+ errorMessage: "Failed to delete profile.",
45
44
  mutationFn: async () => {
46
45
  await apiClient.delete(ADMIN_ENDPOINTS.SCAMMER_BY_ID(scammerId));
47
46
  },
@@ -50,9 +49,6 @@ export function AdminScammerEditorView({ open, onClose, scammerId, displayNames
50
49
  queryClient.invalidateQueries({ queryKey: ["admin", "scammers"] });
51
50
  onClose();
52
51
  },
53
- onError: (err) => {
54
- showToast(err?.message ?? "Failed to delete profile.", "error");
55
- },
56
52
  });
57
53
  return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: displayNames.length > 0 ? displayNames[0] : "Scammer Profile", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "md", children: [_jsx(RecordStatusTimeline, { title: "Decision history", entries: statusHistory, truncatedCount: statusHistoryTruncated }), _jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-flex ${SCAMMER_STATUS_BADGE[currentStatus ?? ScammerStatusValues.PENDING_REVIEW]}`, rounded: "full", padding: "pill-sm-tall", children: SCAMMER_STATUS_LABEL[currentStatus ?? ScammerStatusValues.PENDING_REVIEW] }), scamType && (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: scamType.replace(/_/g, " ") }))] }), displayNames.length > 0 && (_jsxs(Div, { children: [_jsx(Text, { className: "mb-1 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Names / Aliases" }), _jsx(Row, { wrap: true, gap: "xs", children: displayNames.map((name, i) => (_jsx(Span, { size: "xs", rounded: "full", surface: "subtle", padding: "pill-sm", children: name }, i))) })] })), (phones.length > 0 || upiIds.length > 0) && (_jsxs(Div, { className: `${__P.p3}`, rounded: "lg", surface: "muted", border: "default", children: [phones.length > 0 && (_jsxs(Div, { className: "mb-2", children: [_jsx(Text, { className: CLS_SECTION_LABEL, children: "Phone numbers" }), _jsx(Row, { wrap: true, gap: "xs", children: phones.map((p, i) => (_jsx(Code, { size: "xs", rounded: "default", padding: "sm", surface: "subtle", children: p }, i))) })] })), upiIds.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { className: CLS_SECTION_LABEL, children: "UPI IDs" }), _jsx(Row, { wrap: true, gap: "xs", children: upiIds.map((u, i) => (_jsx(Code, { size: "xs", rounded: "default", padding: "sm", surface: "subtle", children: u }, i))) })] }))] })), description && (_jsxs(Div, { className: `${__P.p3}`, rounded: "lg", surface: "muted", border: "default", children: [_jsx(Text, { className: CLS_SECTION_LABEL, children: "Description" }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", size: "sm", children: description })] })), _jsxs(Div, { textSize: "xs", color: "muted", children: ["Reported by:", " ", _jsx(Span, { weight: "medium", color: "muted", children: reportedByAnon ? "Anonymous" : (reportedBy ?? "Unknown") })] }), _jsx(HorizontalRule, { tone: "subtle", spacing: "comfortable" }), _jsx(Select, { label: "Status", options: SCAMMER_STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { className: "uppercase tracking-wide", color: "muted", size: "xs", weight: "semibold", children: "Verification note (internal)" }), _jsx(Textarea, { value: note, onChange: (e) => setNote(e.target.value), rows: 3, placeholder: "e.g. Verified via 3 independent reports\u2026" })] }), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "danger", size: "sm", disabled: !scammerId, isLoading: deleteMutation.isPending, onClick: () => deleteMutation.mutate(), children: "Delete" }), _jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "button", isLoading: updateMutation.isPending, disabled: !scammerId || updateMutation.isPending, onClick: () => updateMutation.mutate(), children: "Save changes" })] })] }) }));
58
54
  }
@@ -170,6 +170,7 @@ export function AdminSectionsView({ children }) {
170
170
  setReorderUndoStack([]);
171
171
  }, [sections]);
172
172
  const saveSection = useApiMutation({
173
+ errorMessage: "Failed to save section.",
173
174
  mutationFn: async () => {
174
175
  let parsedConfig;
175
176
  try {
@@ -207,11 +208,9 @@ export function AdminSectionsView({ children }) {
207
208
  toast.showToast(mode === "create" ? "Section created." : "Section updated.", "success");
208
209
  setIsModalOpen(false);
209
210
  },
210
- onError: (error) => {
211
- toast.showToast(error instanceof Error ? error.message : "Failed to save section.", "error");
212
- },
213
211
  });
214
212
  const reorderSections = useApiMutation({
213
+ errorMessage: "Failed to reorder sections.",
215
214
  mutationFn: async () => {
216
215
  await Promise.all(reorderDraft.map((item) => apiClient.patch(`${ADMIN_ENDPOINTS.SECTIONS}/${item.id}`, {
217
216
  order: item.order,
@@ -225,9 +224,6 @@ export function AdminSectionsView({ children }) {
225
224
  setReorderUndoStack([]);
226
225
  toast.showToast("Section order updated.", "success");
227
226
  },
228
- onError: (error) => {
229
- toast.showToast(error instanceof Error ? error.message : "Failed to reorder sections.", "error");
230
- },
231
227
  });
232
228
  React.useEffect(() => {
233
229
  if (mode !== "edit" || !selectedSectionId)
@@ -25,6 +25,7 @@ export function AdminSessionsView({ children, ...props }) {
25
25
  const { showToast } = useToast();
26
26
  const [revokeTarget, setRevokeTarget] = useState(null);
27
27
  const revokeMutation = useApiMutation({
28
+ errorMessage: "Failed to revoke session.",
28
29
  mutationFn: async (id) => {
29
30
  await apiClient.delete(ADMIN_ENDPOINTS.SESSION_BY_ID(id));
30
31
  },
@@ -33,9 +34,6 @@ export function AdminSessionsView({ children, ...props }) {
33
34
  queryClient.invalidateQueries({ queryKey: ["admin", "sessions"] });
34
35
  setRevokeTarget(null);
35
36
  },
36
- onError: (err) => {
37
- showToast(err?.message ?? "Failed to revoke session.", "error");
38
- },
39
37
  });
40
38
  if (React.Children.count(children) > 0) {
41
39
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
@@ -48,13 +48,13 @@ function GroupsContent({ groups, activePath, onItemClick, }) {
48
48
  const hasActive = !!findActiveNavItem(group.items, activePath);
49
49
  return (_jsxs(Div, { className: "mb-0.5", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => toggle(group.title), paddingX: "md", paddingY: "sm", weight: "semibold", rounded: "none", className: `w-full text-[0.6875rem] uppercase tracking-widest transition-colors ${hasActive && !isOpen
50
50
  ? "text-[var(--appkit-color-text-muted)]"
51
- : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: activeItem?.href === item.href, onClick: onItemClick }) }, item.href))) }))] }, group.title));
51
+ : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""} shrink-0`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: activeItem?.href === item.href, onClick: onItemClick }) }, item.href))) }))] }, group.title));
52
52
  })] }));
53
53
  }
54
54
  function DrawerPanel({ title, onClose, children, }) {
55
55
  const { hand } = useHandMode();
56
56
  const { edgeClass, borderClass } = getSidebarOverlayClasses(hand);
57
- return (_jsxs(Div, { className: "hidden lg:block", children: [_jsx(Div, { surface: "overlay-xs", className: "fixed inset-0 z-40 backdrop-blur-sm", onClick: onClose, "aria-hidden": "true" }), _jsxs(Stack, { border: "default", shadow: "2xl", role: "dialog", "aria-modal": "true", "aria-label": title, className: `fixed top-0 ${edgeClass} z-50 h-full w-64 ${borderClass}`, surface: "default", children: [_jsxs(Row, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", align: "center", justify: "between", children: [_jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: title }), _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "sm", onClick: onClose, icon: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: children })] })] }));
57
+ return (_jsxs(Div, { className: "hidden lg:block", children: [_jsx(Div, { surface: "overlay-xs", className: "fixed inset-0 z-40 backdrop-blur-sm", onClick: onClose, "aria-hidden": "true" }), _jsxs(Stack, { border: "default", shadow: "2xl", role: "dialog", "aria-modal": "true", "aria-label": title, className: `fixed top-0 ${edgeClass} z-50 h-full w-64 ${borderClass}`, surface: "default", children: [_jsxs(Row, { border: "bottom-subtle", paddingY: "y-sm-tall", className: "shrink-0", padding: "x-md", align: "center", justify: "between", children: [_jsx(Span, { size: "xs", weight: "semibold", transform: "uppercase", color: "muted", children: title }), _jsx(IconButton, { "aria-label": "Close", variant: "ghost", size: "sm", onClick: onClose, icon: _jsx("svg", { className: "w-4 h-4 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }), _jsx(Div, { className: `flex-1 ${__O.yAuto}`, children: children })] })] }));
58
58
  }
59
59
  export function AdminSidebar({ renderNavItems, renderHeader, renderFooter, activePath = "", groups, mobileOpen = false, desktopOpen = false, variant = "overlay", onCloseMobile, onToggle, }) {
60
60
  const close = onCloseMobile ?? (() => { });
@@ -619,6 +619,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
619
619
  };
620
620
  }
621
621
  const saveAllMutation = useApiMutation({
622
+ errorMessage: "Failed to save site settings.",
622
623
  mutationFn: async () => {
623
624
  await apiClient.put(ADMIN_ENDPOINTS.ADMIN_SITE, buildFullPayload());
624
625
  },
@@ -629,7 +630,6 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
629
630
  // shared useSiteSettings() hook under a different key — must invalidate both.
630
631
  queryClient.invalidateQueries({ queryKey: ["site-settings"] });
631
632
  },
632
- onError: (err) => showToast(err?.message ?? "Failed to save site settings.", "error"),
633
633
  });
634
634
  const BG_TYPE_OPTIONS = [
635
635
  { label: "Solid color", value: "color" },
@@ -80,6 +80,7 @@ export function AdminStoreEditorView({ open, onClose, storeId, storeName, curren
80
80
  }
81
81
  }, [open, currentStatus, currentIsVerified, currentIsFeatured, currentCapabilities]);
82
82
  const saveMutation = useApiMutation({
83
+ errorMessage: "Failed to update store.",
83
84
  mutationFn: async () => {
84
85
  await apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), {
85
86
  storeStatus,
@@ -95,9 +96,6 @@ export function AdminStoreEditorView({ open, onClose, storeId, storeName, curren
95
96
  queryClient.invalidateQueries({ queryKey: ["admin", "stores"] });
96
97
  onClose();
97
98
  },
98
- onError: (err) => {
99
- showToast(err?.message ?? "Failed to update store.", "error");
100
- },
101
99
  });
102
100
  const renderStatusSection = () => (_jsx(Select, { label: "Store status", options: STATUS_OPTIONS, value: storeStatus, onValueChange: setStoreStatus }));
103
101
  const renderNotesSection = () => (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { children: "Admin notes (optional)" }), _jsx(Textarea, { value: adminNotes, onChange: (e) => setAdminNotes(e.target.value), rows: 3, placeholder: "e.g. Reason for suspension, approval notes\u2026" })] }));
@@ -22,24 +22,20 @@ export function AdminStoresView({ children, ...props }) {
22
22
  const queryClient = useQueryClient();
23
23
  const router = useRouter();
24
24
  const verifyStore = useApiMutation({
25
+ errorMessage: "Failed to verify store.",
25
26
  mutationFn: (storeId) => apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), { isVerified: true }),
26
27
  onSuccess: () => {
27
28
  toast.showToast("Store verified.", "success");
28
29
  void queryClient.invalidateQueries({ queryKey: ["admin", "stores", "listing"] });
29
30
  },
30
- onError: () => {
31
- toast.showToast("Failed to verify store.", "error");
32
- },
33
31
  });
34
32
  const suspendStore = useApiMutation({
33
+ errorMessage: "Failed to suspend store.",
35
34
  mutationFn: (storeId) => apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), { storeStatus: "suspended" }),
36
35
  onSuccess: () => {
37
36
  toast.showToast("Store suspended.", "success");
38
37
  void queryClient.invalidateQueries({ queryKey: ["admin", "stores", "listing"] });
39
38
  },
40
- onError: () => {
41
- toast.showToast("Failed to suspend store.", "error");
42
- },
43
39
  });
44
40
  if (React.Children.count(children) > 0) {
45
41
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
@@ -42,6 +42,7 @@ export function AdminSublistingCategoryEditorView({ categoryId, onSaved, onDelet
42
42
  setCoverImage(cat.coverImage ?? "");
43
43
  }, [categoryQuery.data]);
44
44
  const saveMutation = useApiMutation({
45
+ errorMessage: "Failed to save category.",
45
46
  mutationFn: async () => {
46
47
  const payload = {
47
48
  name,
@@ -60,20 +61,15 @@ export function AdminSublistingCategoryEditorView({ categoryId, onSaved, onDelet
60
61
  if (onSaved && id)
61
62
  onSaved(String(id));
62
63
  },
63
- onError: (err) => {
64
- showToast(err?.message ?? "Failed to save category.", "error");
65
- },
66
64
  });
67
65
  const deleteMutation = useApiMutation({
66
+ errorMessage: "Failed to delete category.",
68
67
  mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.SUBLISTING_CATEGORY_BY_ID(categoryId)),
69
68
  onSuccess: () => {
70
69
  showToast("Category deleted. All linked listings were unlinked.", "success");
71
70
  if (onDeleted)
72
71
  onDeleted();
73
72
  },
74
- onError: (err) => {
75
- showToast(err?.message ?? "Failed to delete category.", "error");
76
- },
77
73
  });
78
74
  const { upload } = useMediaUpload();
79
75
  const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;