@mohasinac/appkit 4.29.1 → 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 (157) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
  2. package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
  3. package/dist/_internal/shared/features/blog/schema.js +2 -1
  4. package/dist/_internal/shared/features/brands/schema.d.ts +2 -2
  5. package/dist/_internal/shared/features/brands/schema.js +3 -1
  6. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +3 -3
  7. package/dist/_internal/shared/features/categories/bundle-schemas.js +2 -1
  8. package/dist/_internal/shared/features/events/schema.d.ts +2 -2
  9. package/dist/_internal/shared/features/events/schema.js +2 -1
  10. package/dist/_internal/shared/features/products/schema.d.ts +8 -8
  11. package/dist/_internal/shared/features/products/schema.js +3 -2
  12. package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
  13. package/dist/_internal/shared/features/reviews/schema.js +2 -1
  14. package/dist/_internal/shared/types/action-result.d.ts +0 -2
  15. package/dist/client/api/surface-error.d.ts +9 -0
  16. package/dist/client/api/surface-error.js +29 -3
  17. package/dist/client/api/useApiMutation.d.ts +10 -0
  18. package/dist/client/api/useApiMutation.js +2 -1
  19. package/dist/core/server-action.d.ts +3 -14
  20. package/dist/core/server-action.js +11 -2
  21. package/dist/errors/error-envelope.d.ts +73 -0
  22. package/dist/errors/error-envelope.js +96 -0
  23. package/dist/errors/error-handler.js +14 -7
  24. package/dist/features/account/components/NotificationBell.js +1 -1
  25. package/dist/features/account/components/UserSidebar.js +2 -2
  26. package/dist/features/account/components/UserSupportView.js +2 -6
  27. package/dist/features/account/hooks/useProfile.d.ts +6 -0
  28. package/dist/features/account/hooks/useProfile.js +1 -0
  29. package/dist/features/admin/components/AdminAdEditorView.js +1 -3
  30. package/dist/features/admin/components/AdminAddressClustersView.js +1 -3
  31. package/dist/features/admin/components/AdminAddressEditorView.js +2 -4
  32. package/dist/features/admin/components/AdminAddressesView.js +1 -3
  33. package/dist/features/admin/components/AdminAllEventEntriesView.js +1 -3
  34. package/dist/features/admin/components/AdminBidsView.js +1 -3
  35. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  36. package/dist/features/admin/components/AdminBrandEditorView.js +4 -5
  37. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +1 -3
  38. package/dist/features/admin/components/AdminCategoryEditorView.js +2 -4
  39. package/dist/features/admin/components/AdminContactEditorView.js +1 -3
  40. package/dist/features/admin/components/AdminContactView.js +2 -6
  41. package/dist/features/admin/components/AdminCouponEditorView.js +2 -4
  42. package/dist/features/admin/components/AdminEmployeeEditorView.js +3 -9
  43. package/dist/features/admin/components/AdminFaqEditorView.js +2 -4
  44. package/dist/features/admin/components/AdminFeatureEditorView.js +0 -9
  45. package/dist/features/admin/components/AdminFeatureFlagsView.js +2 -4
  46. package/dist/features/admin/components/AdminNavEditorView.js +1 -3
  47. package/dist/features/admin/components/AdminNavigationView.js +3 -3
  48. package/dist/features/admin/components/AdminNewsletterView.js +1 -3
  49. package/dist/features/admin/components/AdminNotificationsView.js +2 -6
  50. package/dist/features/admin/components/AdminOffersView.js +1 -3
  51. package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
  52. package/dist/features/admin/components/AdminPaymentClustersView.js +1 -3
  53. package/dist/features/admin/components/AdminPaymentMethodsView.js +1 -3
  54. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +2 -4
  55. package/dist/features/admin/components/AdminProductEditorView.js +2 -4
  56. package/dist/features/admin/components/AdminReturnRequestsView.js +2 -6
  57. package/dist/features/admin/components/AdminReviewsView.js +2 -6
  58. package/dist/features/admin/components/AdminScammerEditorView.js +2 -6
  59. package/dist/features/admin/components/AdminSectionsView.js +2 -6
  60. package/dist/features/admin/components/AdminSessionsView.js +1 -3
  61. package/dist/features/admin/components/AdminSidebar.js +2 -2
  62. package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
  63. package/dist/features/admin/components/AdminStoreEditorView.js +1 -3
  64. package/dist/features/admin/components/AdminStoresView.js +2 -6
  65. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -6
  66. package/dist/features/admin/components/AdminSupportTicketDetailView.js +7 -21
  67. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -4
  68. package/dist/features/admin/components/AdminUserEditorView.js +6 -18
  69. package/dist/features/admin/components/AdminUsersView.js +2 -6
  70. package/dist/features/admin/components/BrandQuickCreateForm.js +2 -4
  71. package/dist/features/admin/components/CategoryQuickCreateForm.js +2 -4
  72. package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
  73. package/dist/features/auth/components/ForgotPasswordView.js +1 -1
  74. package/dist/features/auth/components/LoginForm.js +1 -1
  75. package/dist/features/auth/components/RegisterForm.js +1 -1
  76. package/dist/features/auth/components/ResetPasswordView.js +1 -1
  77. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  78. package/dist/features/auth/repository/session.repository.js +13 -1
  79. package/dist/features/blog/actions/blog-actions.d.ts +14 -14
  80. package/dist/features/blog/actions/blog-actions.js +6 -8
  81. package/dist/features/blog/schemas/index.d.ts +1 -1
  82. package/dist/features/blog/schemas/index.js +2 -4
  83. package/dist/features/categories/api/[id]/route.js +2 -1
  84. package/dist/features/categories/api/route.js +2 -1
  85. package/dist/features/events/schemas/index.d.ts +1 -1
  86. package/dist/features/events/schemas/index.js +2 -4
  87. package/dist/features/faq/components/ContactCTA.js +1 -1
  88. package/dist/features/filters/AsyncFacetSection.js +1 -1
  89. package/dist/features/filters/FilterFacetSection.js +1 -1
  90. package/dist/features/filters/RangeFilter.js +1 -1
  91. package/dist/features/filters/SwitchFilter.js +1 -1
  92. package/dist/features/homepage/api/carousel/[id]/route.js +4 -3
  93. package/dist/features/homepage/api/carousel/route.js +4 -3
  94. package/dist/features/homepage/api/route.js +3 -2
  95. package/dist/features/homepage/components/GoogleReviewsSection.js +1 -1
  96. package/dist/features/homepage/components/HeroBanner.js +1 -1
  97. package/dist/features/homepage/components/HeroCarousel.js +1 -1
  98. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  99. package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
  100. package/dist/features/layout/AppLayoutShell.js +4 -4
  101. package/dist/features/layout/BottomNavbar.js +2 -2
  102. package/dist/features/layout/MainNavbar.js +1 -1
  103. package/dist/features/layout/TitleBarLayout.js +24 -12
  104. package/dist/features/media/AvatarUpload.js +28 -10
  105. package/dist/features/media/modals/ImageCropModal.js +1 -1
  106. package/dist/features/media/modals/ImageEditor.js +1 -1
  107. package/dist/features/products/components/MakeOfferButton.js +1 -1
  108. package/dist/features/reviews/api/[id]/route.js +3 -2
  109. package/dist/features/reviews/api/route.js +3 -2
  110. package/dist/features/search/components/Search.js +2 -2
  111. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +3 -3
  112. package/dist/features/seller/components/SellerSidebar.js +2 -2
  113. package/dist/features/store-extensions/components/ReviewDecisionModal.js +1 -1
  114. package/dist/features/stores/repository/store.repository.d.ts +21 -0
  115. package/dist/features/stores/repository/store.repository.js +32 -0
  116. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -2
  117. package/dist/index.d.ts +3 -2
  118. package/dist/index.js +4 -1
  119. package/dist/next/api/api-response.d.ts +29 -12
  120. package/dist/next/api/api-response.js +9 -8
  121. package/dist/next/api/apiHandler.d.ts +6 -1
  122. package/dist/next/api/apiHandler.js +13 -1
  123. package/dist/next/api/routeHandler.js +6 -6
  124. package/dist/next/middleware/pii-scrubber.js +5 -2
  125. package/dist/providers/db-firebase/base.repository.d.ts +14 -0
  126. package/dist/providers/db-firebase/base.repository.js +22 -0
  127. package/dist/providers/db-firebase/sieve.d.ts +14 -1
  128. package/dist/providers/db-firebase/sieve.js +3 -1
  129. package/dist/security/pii-encrypt.d.ts +0 -4
  130. package/dist/security/pii-encrypt.js +79 -13
  131. package/dist/security/pii-mask.d.ts +39 -1
  132. package/dist/security/pii-mask.js +33 -4
  133. package/dist/security/pii-redact.d.ts +0 -13
  134. package/dist/security/pii-redact.js +3 -1
  135. package/dist/security/pii-schemas.d.ts +0 -2
  136. package/dist/security/pii-schemas.js +16 -2
  137. package/dist/security/settings-encryption.js +31 -3
  138. package/dist/seed/test-utils.js +6 -2
  139. package/dist/ui/DataTable.js +2 -2
  140. package/dist/ui/components/CollapsibleSection.js +1 -1
  141. package/dist/ui/components/ImageGallery.js +1 -1
  142. package/dist/ui/components/ListingLayout.js +1 -1
  143. package/dist/ui/forms/FormShell.d.ts +0 -19
  144. package/dist/ui/forms/FormShell.js +4 -23
  145. package/dist/ui/forms/apply-zod-issues.d.ts +34 -0
  146. package/dist/ui/forms/apply-zod-issues.js +38 -0
  147. package/dist/ui/forms/index.d.ts +2 -1
  148. package/dist/ui/forms/index.js +2 -1
  149. package/dist/ui/icons/icon-registry.d.ts +8 -0
  150. package/dist/ui/icons/icon-registry.js +13 -1
  151. package/dist/utils/action-response.d.ts +1 -1
  152. package/dist/utils/media-url.d.ts +49 -0
  153. package/dist/utils/media-url.js +72 -1
  154. package/dist/validation/schemas.d.ts +15 -2
  155. package/dist/validation/schemas.js +18 -22
  156. package/package.json +1 -1
  157. package/scripts/seed-cli.mjs +10 -2
@@ -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;
@@ -73,6 +73,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
73
73
  return out;
74
74
  }, [parties]);
75
75
  const updateMutation = useApiMutation({
76
+ errorMessage: "Failed to update ticket.",
76
77
  mutationFn: async () => {
77
78
  await apiClient.patch(ADMIN_ENDPOINTS.SUPPORT_TICKET_BY_ID(ticketId), {
78
79
  status,
@@ -86,11 +87,9 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
86
87
  invalidate();
87
88
  onClose();
88
89
  },
89
- onError: (err) => {
90
- showToast(err?.message ?? "Failed to update ticket.", "error");
91
- },
92
90
  });
93
91
  const replyMutation = useApiMutation({
92
+ errorMessage: "Failed to send reply.",
94
93
  mutationFn: async () => {
95
94
  await apiClient.post(SUPPORT_ENDPOINTS.TICKET_MESSAGES(ticketId), { body: replyBody, newStatus: status });
96
95
  },
@@ -100,9 +99,6 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
100
99
  invalidate();
101
100
  onClose();
102
101
  },
103
- onError: (err) => {
104
- showToast(err?.message ?? "Failed to send reply.", "error");
105
- },
106
102
  });
107
103
  // ST-4 — Apply Store Change action (only relevant for store_change_request)
108
104
  const [storeStatus, setStoreStatus] = React.useState("active");
@@ -129,6 +125,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
129
125
  },
130
126
  });
131
127
  const applyStoreChange = useApiMutation({
128
+ errorMessage: "Failed to apply store change.",
132
129
  mutationFn: async () => {
133
130
  if (!linkedStoreId)
134
131
  throw new Error("No linked store on this ticket.");
@@ -149,9 +146,6 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
149
146
  .catch(console.error);
150
147
  invalidate();
151
148
  },
152
- onError: (err) => {
153
- showToast(err?.message ?? "Failed to apply store change.", "error");
154
- },
155
149
  });
156
150
  // ST-3 — Modify Order Items panel (fetches current items on demand, lets
157
151
  // admin edit quantity per line + remove lines, then PATCHes the order).
@@ -160,6 +154,7 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
160
154
  const linkedOrderId = parties.orderId?.trim() || orderId;
161
155
  const isOrderModificationRequest = category === "order_modification_request" || category === "order_issue";
162
156
  const loadOrderItems = useApiMutation({
157
+ errorMessage: "Failed to load order.",
163
158
  mutationFn: async () => {
164
159
  if (!linkedOrderId)
165
160
  throw new Error("No linked order on this ticket.");
@@ -174,14 +169,12 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
174
169
  })));
175
170
  setOrderItemsOpen(true);
176
171
  },
177
- onError: (err) => {
178
- showToast(err?.message ?? "Failed to load order.", "error");
179
- },
180
172
  });
181
173
  // ST-5 — Lift Ban actions for unban_request tickets
182
174
  const linkedUserId = parties.userId?.trim();
183
175
  const isUnbanRequest = category === "unban_request";
184
176
  const liftHardBan = useApiMutation({
177
+ errorMessage: "Failed to lift hard ban.",
185
178
  mutationFn: async () => {
186
179
  if (!linkedUserId)
187
180
  throw new Error("No linked user on this ticket.");
@@ -198,11 +191,9 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
198
191
  .catch(console.error);
199
192
  invalidate();
200
193
  },
201
- onError: (err) => {
202
- showToast(err?.message ?? "Failed to lift hard ban.", "error");
203
- },
204
194
  });
205
195
  const liftSoftBanTickets = useApiMutation({
196
+ errorMessage: "Failed to lift soft ban.",
206
197
  mutationFn: async () => {
207
198
  if (!linkedUserId)
208
199
  throw new Error("No linked user on this ticket.");
@@ -219,11 +210,9 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
219
210
  .catch(console.error);
220
211
  invalidate();
221
212
  },
222
- onError: (err) => {
223
- showToast(err?.message ?? "Failed to lift soft ban.", "error");
224
- },
225
213
  });
226
214
  const applyOrderItems = useApiMutation({
215
+ errorMessage: "Failed to update order items.",
227
216
  mutationFn: async () => {
228
217
  if (!linkedOrderId)
229
218
  throw new Error("No linked order on this ticket.");
@@ -256,9 +245,6 @@ export function AdminSupportTicketDetailView({ open, onClose, ticketId, subject,
256
245
  invalidate();
257
246
  setOrderItemsOpen(false);
258
247
  },
259
- onError: (err) => {
260
- showToast(err?.message ?? "Failed to update order items.", "error");
261
- },
262
248
  });
263
249
  return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: subject ?? "Support Ticket", children: _jsxs(Stack, { className: `${__P.p4}`, gap: "md", children: [_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-flex ${STATUS_COLOR[currentStatus ?? "open"] ?? STATUS_COLOR.open}`, rounded: "full", padding: "pill-sm-tall", children: (currentStatus ?? "open").replace(/_/g, " ") }), _jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: category }), orderId && (_jsxs(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: ["Order: ", orderId] }))] }), _jsxs(Div, { textSize: "sm", color: "muted", children: ["From: ", _jsx(Span, { weight: "medium", color: "primary", children: userDisplayName })] }), description && (_jsxs(Div, { className: `${__P.p3}`, rounded: "lg", surface: "muted", border: "default", children: [_jsx(Text, { className: "mb-1 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Description" }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", size: "sm", children: description })] })), messages.length > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: ["Messages (", messages.length, ")"] }), _jsx(Stack, { className: `max-h-64 ${__O.yAuto}`, gap: "sm", children: messages.map((msg, i) => (_jsxs(Div, { textSize: "sm", surface: msg.authorRole === "user" ? "default" : "info-surface", className: msg.authorRole === "user" ? "border border-[var(--appkit-color-border)]" : "border border-[var(--appkit-color-info)]", rounded: "lg", padding: "sm", children: [_jsxs(Row, { color: "muted", textSize: "xs", className: "mb-1", align: "center", gap: "sm", children: [_jsx(Span, { weight: "medium", color: "muted", children: ROLE_LABEL[msg.authorRole ?? "user"] ?? msg.authorRole }), msg.createdAt && (_jsx(Span, { children: new Date(msg.createdAt).toLocaleString() }))] }), _jsx(Text, { className: "whitespace-pre-wrap", color: "primary", children: msg.body })] }, msg.id ?? i))) })] })), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { className: "uppercase tracking-wide", color: "muted", size: "xs", weight: "semibold", children: "Reply to user" }), _jsx(Textarea, { value: replyBody, onChange: (e) => setReplyBody(e.target.value), rows: 3, placeholder: "Type a reply\u2026" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", isLoading: replyMutation.isPending, disabled: !replyBody.trim() || !ticketId || replyMutation.isPending, onClick: () => replyMutation.mutate(), children: "Send reply" })] }), _jsx(HorizontalRule, { tone: "subtle", spacing: "comfortable" }), _jsx(RecordStatusTimeline, { entries: statusHistory, truncatedCount: statusHistoryTruncated }), _jsx(HorizontalRule, { tone: "subtle", spacing: "comfortable" }), _jsx(Select, { label: "Status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Select, { label: "Priority", options: PRIORITY_OPTIONS, value: priority, onValueChange: setPriority }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { className: "uppercase tracking-wide", color: "muted", size: "xs", weight: "semibold", children: "Internal notes (staff only)" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Notes visible only to admins and employees\u2026" })] }), _jsxs(Stack, { padding: "sm", gap: "sm", rounded: "lg", border: "default", children: [_jsx(Text, { className: "tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Linked parties" }), _jsx(Text, { size: "xs", color: "muted", children: "Tag the buyer / store / order / product / bid this ticket concerns." }), [
264
250
  ["userId", "User slug"],
@@ -79,6 +79,7 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
79
79
  setPageKey(slugifyKey(value));
80
80
  };
81
81
  const saveMutation = useApiMutation({
82
+ errorMessage: "Failed to save checklist item.",
82
83
  mutationFn: async () => {
83
84
  const payload = {
84
85
  groupKey: groupKey || slugifyKey(groupLabel),
@@ -104,18 +105,15 @@ export function AdminTesterChecklistItemEditorView({ itemId, onSaved, onDeleted,
104
105
  if (onSaved && id)
105
106
  onSaved(id);
106
107
  },
107
- onError: (err) => {
108
- showToast(err?.message ?? "Failed to save checklist item.", "error");
109
- },
110
108
  });
111
109
  const deleteMutation = useApiMutation({
110
+ errorMessage: "Failed to delete checklist item.",
112
111
  mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEM_BY_ID(itemId)),
113
112
  onSuccess: () => {
114
113
  showToast("Checklist item deleted.", "success");
115
114
  if (onDeleted)
116
115
  onDeleted();
117
116
  },
118
- onError: (err) => showToast(err?.message ?? "Failed to delete checklist item.", "error"),
119
117
  });
120
118
  const isSubmitting = saveMutation.isPending || itemQuery.isLoading;
121
119
  const canSave = Boolean(groupLabel.trim()) && Boolean(pageLabel.trim()) && Boolean(label.trim());