@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.
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
- package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
- package/dist/_internal/shared/features/blog/schema.js +2 -1
- package/dist/_internal/shared/features/brands/schema.d.ts +2 -2
- package/dist/_internal/shared/features/brands/schema.js +3 -1
- package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +3 -3
- package/dist/_internal/shared/features/categories/bundle-schemas.js +2 -1
- package/dist/_internal/shared/features/events/schema.d.ts +2 -2
- package/dist/_internal/shared/features/events/schema.js +2 -1
- package/dist/_internal/shared/features/products/schema.d.ts +8 -8
- package/dist/_internal/shared/features/products/schema.js +3 -2
- package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
- package/dist/_internal/shared/features/reviews/schema.js +2 -1
- package/dist/_internal/shared/types/action-result.d.ts +0 -2
- package/dist/client/api/surface-error.d.ts +9 -0
- package/dist/client/api/surface-error.js +29 -3
- package/dist/client/api/useApiMutation.d.ts +10 -0
- package/dist/client/api/useApiMutation.js +2 -1
- package/dist/core/server-action.d.ts +3 -14
- package/dist/core/server-action.js +11 -2
- package/dist/errors/error-envelope.d.ts +73 -0
- package/dist/errors/error-envelope.js +96 -0
- package/dist/errors/error-handler.js +14 -7
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/components/UserSidebar.js +2 -2
- package/dist/features/account/components/UserSupportView.js +2 -6
- package/dist/features/account/hooks/useProfile.d.ts +6 -0
- package/dist/features/account/hooks/useProfile.js +1 -0
- package/dist/features/admin/components/AdminAdEditorView.js +1 -3
- package/dist/features/admin/components/AdminAddressClustersView.js +1 -3
- package/dist/features/admin/components/AdminAddressEditorView.js +2 -4
- package/dist/features/admin/components/AdminAddressesView.js +1 -3
- package/dist/features/admin/components/AdminAllEventEntriesView.js +1 -3
- package/dist/features/admin/components/AdminBidsView.js +1 -3
- package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
- package/dist/features/admin/components/AdminBrandEditorView.js +4 -5
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +1 -3
- package/dist/features/admin/components/AdminCategoryEditorView.js +2 -4
- package/dist/features/admin/components/AdminContactEditorView.js +1 -3
- package/dist/features/admin/components/AdminContactView.js +2 -6
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -4
- package/dist/features/admin/components/AdminEmployeeEditorView.js +3 -9
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -4
- package/dist/features/admin/components/AdminFeatureEditorView.js +0 -9
- package/dist/features/admin/components/AdminFeatureFlagsView.js +2 -4
- package/dist/features/admin/components/AdminNavEditorView.js +1 -3
- package/dist/features/admin/components/AdminNavigationView.js +3 -3
- package/dist/features/admin/components/AdminNewsletterView.js +1 -3
- package/dist/features/admin/components/AdminNotificationsView.js +2 -6
- package/dist/features/admin/components/AdminOffersView.js +1 -3
- package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
- package/dist/features/admin/components/AdminPaymentClustersView.js +1 -3
- package/dist/features/admin/components/AdminPaymentMethodsView.js +1 -3
- package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +2 -4
- package/dist/features/admin/components/AdminProductEditorView.js +2 -4
- package/dist/features/admin/components/AdminReturnRequestsView.js +2 -6
- package/dist/features/admin/components/AdminReviewsView.js +2 -6
- package/dist/features/admin/components/AdminScammerEditorView.js +2 -6
- package/dist/features/admin/components/AdminSectionsView.js +2 -6
- package/dist/features/admin/components/AdminSessionsView.js +1 -3
- package/dist/features/admin/components/AdminSidebar.js +2 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +1 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +1 -3
- package/dist/features/admin/components/AdminStoresView.js +2 -6
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -6
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +7 -21
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -4
- package/dist/features/admin/components/AdminUserEditorView.js +6 -18
- package/dist/features/admin/components/AdminUsersView.js +2 -6
- package/dist/features/admin/components/BrandQuickCreateForm.js +2 -4
- package/dist/features/admin/components/CategoryQuickCreateForm.js +2 -4
- package/dist/features/auctions/components/PlaceBidFormClient.js +1 -1
- package/dist/features/auth/components/ForgotPasswordView.js +1 -1
- package/dist/features/auth/components/LoginForm.js +1 -1
- package/dist/features/auth/components/RegisterForm.js +1 -1
- package/dist/features/auth/components/ResetPasswordView.js +1 -1
- package/dist/features/auth/components/SocialAuthButtons.js +1 -1
- package/dist/features/auth/repository/session.repository.js +13 -1
- package/dist/features/blog/actions/blog-actions.d.ts +14 -14
- package/dist/features/blog/actions/blog-actions.js +6 -8
- package/dist/features/blog/schemas/index.d.ts +1 -1
- package/dist/features/blog/schemas/index.js +2 -4
- package/dist/features/categories/api/[id]/route.js +2 -1
- package/dist/features/categories/api/route.js +2 -1
- package/dist/features/events/schemas/index.d.ts +1 -1
- package/dist/features/events/schemas/index.js +2 -4
- package/dist/features/faq/components/ContactCTA.js +1 -1
- package/dist/features/filters/AsyncFacetSection.js +1 -1
- package/dist/features/filters/FilterFacetSection.js +1 -1
- package/dist/features/filters/RangeFilter.js +1 -1
- package/dist/features/filters/SwitchFilter.js +1 -1
- package/dist/features/homepage/api/carousel/[id]/route.js +4 -3
- package/dist/features/homepage/api/carousel/route.js +4 -3
- package/dist/features/homepage/api/route.js +3 -2
- package/dist/features/homepage/components/GoogleReviewsSection.js +1 -1
- package/dist/features/homepage/components/HeroBanner.js +1 -1
- package/dist/features/homepage/components/HeroCarousel.js +1 -1
- package/dist/features/homepage/components/SocialPostCard.js +1 -1
- package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
- package/dist/features/layout/AppLayoutShell.js +4 -4
- package/dist/features/layout/BottomNavbar.js +2 -2
- package/dist/features/layout/MainNavbar.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +24 -12
- package/dist/features/media/AvatarUpload.js +28 -10
- package/dist/features/media/modals/ImageCropModal.js +1 -1
- package/dist/features/media/modals/ImageEditor.js +1 -1
- package/dist/features/products/components/MakeOfferButton.js +1 -1
- package/dist/features/reviews/api/[id]/route.js +3 -2
- package/dist/features/reviews/api/route.js +3 -2
- package/dist/features/search/components/Search.js +2 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +3 -3
- package/dist/features/seller/components/SellerSidebar.js +2 -2
- package/dist/features/store-extensions/components/ReviewDecisionModal.js +1 -1
- package/dist/features/stores/repository/store.repository.d.ts +21 -0
- package/dist/features/stores/repository/store.repository.js +32 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -1
- package/dist/next/api/api-response.d.ts +29 -12
- package/dist/next/api/api-response.js +9 -8
- package/dist/next/api/apiHandler.d.ts +6 -1
- package/dist/next/api/apiHandler.js +13 -1
- package/dist/next/api/routeHandler.js +6 -6
- package/dist/next/middleware/pii-scrubber.js +5 -2
- package/dist/providers/db-firebase/base.repository.d.ts +14 -0
- package/dist/providers/db-firebase/base.repository.js +22 -0
- package/dist/providers/db-firebase/sieve.d.ts +14 -1
- package/dist/providers/db-firebase/sieve.js +3 -1
- package/dist/security/pii-encrypt.d.ts +0 -4
- package/dist/security/pii-encrypt.js +79 -13
- package/dist/security/pii-mask.d.ts +39 -1
- package/dist/security/pii-mask.js +33 -4
- package/dist/security/pii-redact.d.ts +0 -13
- package/dist/security/pii-redact.js +3 -1
- package/dist/security/pii-schemas.d.ts +0 -2
- package/dist/security/pii-schemas.js +16 -2
- package/dist/security/settings-encryption.js +31 -3
- package/dist/seed/test-utils.js +6 -2
- package/dist/ui/DataTable.js +2 -2
- package/dist/ui/components/CollapsibleSection.js +1 -1
- package/dist/ui/components/ImageGallery.js +1 -1
- package/dist/ui/components/ListingLayout.js +1 -1
- package/dist/ui/forms/FormShell.d.ts +0 -19
- package/dist/ui/forms/FormShell.js +4 -23
- package/dist/ui/forms/apply-zod-issues.d.ts +34 -0
- package/dist/ui/forms/apply-zod-issues.js +38 -0
- package/dist/ui/forms/index.d.ts +2 -1
- package/dist/ui/forms/index.js +2 -1
- package/dist/ui/icons/icon-registry.d.ts +8 -0
- package/dist/ui/icons/icon-registry.js +13 -1
- package/dist/utils/action-response.d.ts +1 -1
- package/dist/utils/media-url.d.ts +49 -0
- package/dist/utils/media-url.js +72 -1
- package/dist/validation/schemas.d.ts +15 -2
- package/dist/validation/schemas.js +18 -22
- package/package.json +1 -1
- 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/
|
|
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());
|