@mohasinac/appkit 4.29.0 → 4.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
- package/dist/_internal/server/jobs/handlers/_helpers.d.ts +22 -0
- package/dist/_internal/server/jobs/handlers/_helpers.js +25 -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/stores-seed-data.js +18 -0
- 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
|
@@ -7,7 +7,6 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
7
7
|
import { Button, Checkbox, ConfirmDeleteModal, Div, Form, Grid, Input, Label, Select, Stack, StackedViewShell, Text, Toggle, useToast, } from "../../../ui";
|
|
8
8
|
import { apiClient } from "../../../http";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
|
-
import { ERROR_MESSAGES } from "../../../errors/messages";
|
|
11
10
|
import { FormErrorSummary, applyZodIssues } from "../../../ui/forms";
|
|
12
11
|
import { productFeatureFormSchema } from "../../products/schemas/product-features.validators";
|
|
13
12
|
import { PRODUCT_FEATURE_CATEGORY_OPTIONS, PRODUCT_FEATURE_DEFAULT_DISPLAY_ORDER, PRODUCT_FEATURE_ICON_COLOR_OPTIONS, PRODUCT_FEATURE_PRODUCT_TYPE_OPTIONS, PRODUCT_FEATURE_SCOPE_OPTIONS, } from "../../products/constants/product-features.constants";
|
|
@@ -116,12 +115,6 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
|
|
|
116
115
|
if (onSaved && id)
|
|
117
116
|
onSaved(String(id));
|
|
118
117
|
},
|
|
119
|
-
onError: (err) => {
|
|
120
|
-
showToast(err?.message ??
|
|
121
|
-
(isEdit
|
|
122
|
-
? ERROR_MESSAGES.PRODUCT_FEATURES.UPDATE_FAILED
|
|
123
|
-
: ERROR_MESSAGES.PRODUCT_FEATURES.CREATE_FAILED), "error");
|
|
124
|
-
},
|
|
125
118
|
});
|
|
126
119
|
const deleteMutation = useApiMutation({
|
|
127
120
|
mutationFn: () => apiClient.delete(byIdEndpoint(featureId)),
|
|
@@ -130,8 +123,6 @@ export function AdminFeatureEditorView({ featureId, fixedScope, fixedStoreId, em
|
|
|
130
123
|
if (onDeleted)
|
|
131
124
|
onDeleted();
|
|
132
125
|
},
|
|
133
|
-
onError: (err) => showToast(err?.message ??
|
|
134
|
-
ERROR_MESSAGES.PRODUCT_FEATURES.DELETE_FAILED, "error"),
|
|
135
126
|
});
|
|
136
127
|
const isSubmitting = saveMutation.isPending || featureQuery.isLoading;
|
|
137
128
|
const storeOptions = React.useMemo(() => [
|
|
@@ -45,7 +45,7 @@ const CATEGORY_TYPE_FLAGS = [
|
|
|
45
45
|
];
|
|
46
46
|
function AccordionSection({ title, defaultOpen = true, children }) {
|
|
47
47
|
const [open, setOpen] = React.useState(defaultOpen);
|
|
48
|
-
return (_jsxs(Div, { className: `${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => setOpen((v) => !v), paddingX: "md", paddingY: "md", className: "w-full text-left bg-[var(--appkit-color-surface-elevated)]/60", "aria-expanded": open, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: title }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform ${open ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }) }), open && (_jsx(Div, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", children: children }))] }));
|
|
48
|
+
return (_jsxs(Div, { className: `${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => setOpen((v) => !v), paddingX: "md", paddingY: "md", className: "w-full text-left bg-[var(--appkit-color-surface-elevated)]/60", "aria-expanded": open, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: title }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform ${open ? "rotate-180" : ""} shrink-0`, fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }) }), open && (_jsx(Div, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", children: children }))] }));
|
|
49
49
|
}
|
|
50
50
|
function FlagRow({ label, desc, checked, onChange, rollout, onRolloutChange, showRollout }) {
|
|
51
51
|
return (_jsxs(Row, { surface: "default", padding: "inline", align: "center", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Toggle, { checked: checked, onChange: onChange, label: label }), _jsx(Text, { className: "mt-0.5 ml-10 truncate", color: "faint", size: "xs", children: desc })] }), showRollout && (_jsxs(Stack, { className: "w-28 shrink-0", gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Rollout %" }), _jsx(Input, { type: "number", min: 0, max: 100, value: rollout ?? 100, onChange: (e) => onRolloutChange?.(Math.min(100, Math.max(0, Number(e.target.value)))), disabled: !checked, className: "w-full" })] }))] }));
|
|
@@ -87,6 +87,7 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
|
|
|
87
87
|
categoryTypes: { ...(prev.categoryTypes ?? {}), [key]: val },
|
|
88
88
|
}));
|
|
89
89
|
const saveFlags = useApiMutation({
|
|
90
|
+
errorMessage: "Failed to save feature flags.",
|
|
90
91
|
mutationFn: async () => {
|
|
91
92
|
await apiClient.put(ADMIN_ENDPOINTS.FEATURE_FLAGS, { flags, rollouts });
|
|
92
93
|
},
|
|
@@ -94,9 +95,6 @@ export function AdminFeatureFlagsView({ labels = {}, renderFlags, ...rest }) {
|
|
|
94
95
|
await queryClient.invalidateQueries({ queryKey: ["admin", "feature-flags"] });
|
|
95
96
|
showToast("Feature flags saved.", "success");
|
|
96
97
|
},
|
|
97
|
-
onError: () => {
|
|
98
|
-
showToast("Failed to save feature flags.", "error");
|
|
99
|
-
},
|
|
100
98
|
});
|
|
101
99
|
const defaultFlags = () => (_jsxs(Form, { schema: featureFlagsUpdateSchema, onSubmit: (event) => {
|
|
102
100
|
event.preventDefault();
|
|
@@ -52,6 +52,7 @@ export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions
|
|
|
52
52
|
}, [open, item]);
|
|
53
53
|
const { showToast } = useToast();
|
|
54
54
|
const saveMutation = useApiMutation({
|
|
55
|
+
errorMessage: "Failed to save nav item.",
|
|
55
56
|
mutationFn: async () => {
|
|
56
57
|
const payload = {
|
|
57
58
|
label,
|
|
@@ -71,9 +72,6 @@ export function AdminNavEditorView({ open, onClose, onSaved, item, parentOptions
|
|
|
71
72
|
onSaved();
|
|
72
73
|
onClose();
|
|
73
74
|
},
|
|
74
|
-
onError: (err) => {
|
|
75
|
-
showToast(err?.message ?? "Failed to save nav item.", "error");
|
|
76
|
-
},
|
|
77
75
|
});
|
|
78
76
|
const canSave = Boolean(label.trim()) && Boolean(href.trim());
|
|
79
77
|
const parentSelectOptions = [
|
|
@@ -34,24 +34,25 @@ export function AdminNavigationView({ labels = {}, ...rest }) {
|
|
|
34
34
|
const items = data?.items ?? [];
|
|
35
35
|
const sorted = [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
36
36
|
const visibilityMutation = useApiMutation({
|
|
37
|
+
errorMessage: "Failed to update visibility.",
|
|
37
38
|
mutationFn: async ({ id, isVisible }) => {
|
|
38
39
|
await apiClient.patch(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(id), { isVisible });
|
|
39
40
|
},
|
|
40
41
|
onSuccess: () => {
|
|
41
42
|
queryClient.invalidateQueries({ queryKey: ["admin", "navigation"] });
|
|
42
43
|
},
|
|
43
|
-
onError: () => showToast("Failed to update visibility.", "error"),
|
|
44
44
|
});
|
|
45
45
|
const reorderMutation = useApiMutation({
|
|
46
|
+
errorMessage: "Failed to reorder.",
|
|
46
47
|
mutationFn: async ({ id, newOrder }) => {
|
|
47
48
|
await apiClient.patch(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(id), { order: newOrder });
|
|
48
49
|
},
|
|
49
50
|
onSuccess: () => {
|
|
50
51
|
queryClient.invalidateQueries({ queryKey: ["admin", "navigation"] });
|
|
51
52
|
},
|
|
52
|
-
onError: () => showToast("Failed to reorder.", "error"),
|
|
53
53
|
});
|
|
54
54
|
const deleteMutation = useApiMutation({
|
|
55
|
+
errorMessage: "Failed to delete nav item.",
|
|
55
56
|
mutationFn: async (id) => {
|
|
56
57
|
await apiClient.delete(ADMIN_ENDPOINTS.NAVIGATION_BY_ID(id));
|
|
57
58
|
},
|
|
@@ -60,7 +61,6 @@ export function AdminNavigationView({ labels = {}, ...rest }) {
|
|
|
60
61
|
queryClient.invalidateQueries({ queryKey: ["admin", "navigation"] });
|
|
61
62
|
setDeleteTarget(null);
|
|
62
63
|
},
|
|
63
|
-
onError: () => showToast("Failed to delete nav item.", "error"),
|
|
64
64
|
});
|
|
65
65
|
const handleMoveUp = (idx) => {
|
|
66
66
|
if (idx === 0)
|
|
@@ -21,6 +21,7 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
|
|
|
21
21
|
const [unsubscribeOpen, setUnsubscribeOpen] = useState(false);
|
|
22
22
|
const [selectedRow, setSelectedRow] = useState(null);
|
|
23
23
|
const unsubscribeMutation = useApiMutation({
|
|
24
|
+
errorMessage: "Failed to unsubscribe.",
|
|
24
25
|
mutationFn: async (id) => {
|
|
25
26
|
await apiClient.delete(ADMIN_ENDPOINTS.NEWSLETTER_BY_ID(id));
|
|
26
27
|
},
|
|
@@ -30,9 +31,6 @@ export function AdminNewsletterView({ children, onBulkUnsubscribe, ...props }) {
|
|
|
30
31
|
setUnsubscribeOpen(false);
|
|
31
32
|
setSelectedRow(null);
|
|
32
33
|
},
|
|
33
|
-
onError: (err) => {
|
|
34
|
-
showToast(err?.message ?? "Failed to unsubscribe.", "error");
|
|
35
|
-
},
|
|
36
34
|
});
|
|
37
35
|
// Export runs as an async `newsletterExport` job (Async Job Primitive) —
|
|
38
36
|
// the route only enqueues; this subscribes to the same RTDB bulk-event
|
|
@@ -34,6 +34,7 @@ export function AdminNotificationsView({ children, ...props }) {
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
const deleteMutation = useApiMutation({
|
|
37
|
+
errorMessage: "Failed to delete notification.",
|
|
37
38
|
mutationFn: async (id) => {
|
|
38
39
|
await apiClient.delete(ADMIN_ENDPOINTS.ADMIN_NOTIFICATION_BY_ID(id));
|
|
39
40
|
},
|
|
@@ -42,11 +43,9 @@ export function AdminNotificationsView({ children, ...props }) {
|
|
|
42
43
|
queryClient.invalidateQueries({ queryKey: ["admin", "notifications"] });
|
|
43
44
|
setDeleteTarget(null);
|
|
44
45
|
},
|
|
45
|
-
onError: (err) => {
|
|
46
|
-
showToast(err?.message ?? "Failed to delete notification.", "error");
|
|
47
|
-
},
|
|
48
46
|
});
|
|
49
47
|
const resendMutation = useApiMutation({
|
|
48
|
+
errorMessage: "Failed to resend notification.",
|
|
50
49
|
mutationFn: async (id) => {
|
|
51
50
|
await apiClient.post(ADMIN_ENDPOINTS.ADMIN_NOTIFICATION_RESEND(id), {});
|
|
52
51
|
},
|
|
@@ -54,9 +53,6 @@ export function AdminNotificationsView({ children, ...props }) {
|
|
|
54
53
|
showToast("Notification resent.", "success");
|
|
55
54
|
queryClient.invalidateQueries({ queryKey: ["admin", "notifications"] });
|
|
56
55
|
},
|
|
57
|
-
onError: (err) => {
|
|
58
|
-
showToast(err?.message ?? "Failed to resend notification.", "error");
|
|
59
|
-
},
|
|
60
56
|
});
|
|
61
57
|
if (React.Children.count(children) > 0) {
|
|
62
58
|
return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
|
|
@@ -83,6 +83,7 @@ export function AdminOffersView({ children, ...props }) {
|
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
const cancelMutation = useApiMutation({
|
|
86
|
+
errorMessage: "Failed to cancel offer.",
|
|
86
87
|
mutationFn: async ({ offerId, reason }) => {
|
|
87
88
|
await apiClient.patch(ADMIN_ENDPOINTS.OFFER_BY_ID(offerId), {
|
|
88
89
|
status: "expired",
|
|
@@ -95,9 +96,6 @@ export function AdminOffersView({ children, ...props }) {
|
|
|
95
96
|
setCancelOpen(false);
|
|
96
97
|
setSelectedRow(null);
|
|
97
98
|
},
|
|
98
|
-
onError: (err) => {
|
|
99
|
-
showToast(err?.message ?? "Failed to cancel offer.", "error");
|
|
100
|
-
},
|
|
101
99
|
});
|
|
102
100
|
if (React.Children.count(children) > 0) {
|
|
103
101
|
return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
|
|
@@ -69,6 +69,7 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
|
|
|
69
69
|
}
|
|
70
70
|
}, [open, currentStatus]);
|
|
71
71
|
const saveMutation = useApiMutation({
|
|
72
|
+
errorMessage: "Failed to update order.",
|
|
72
73
|
mutationFn: async () => {
|
|
73
74
|
const payload = {
|
|
74
75
|
status,
|
|
@@ -90,9 +91,6 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
|
|
|
90
91
|
queryClient.invalidateQueries({ queryKey: ["admin", "orders"] });
|
|
91
92
|
onClose();
|
|
92
93
|
},
|
|
93
|
-
onError: (err) => {
|
|
94
|
-
showToast(err?.message ?? "Failed to update order.", "error");
|
|
95
|
-
},
|
|
96
94
|
});
|
|
97
95
|
const handleVerifyPayment = async () => {
|
|
98
96
|
if (!orderId)
|
|
@@ -21,6 +21,7 @@ export function AdminPaymentClustersView(_props) {
|
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
const flagMutation = useApiMutation({
|
|
24
|
+
errorMessage: "Failed to flag payment method.",
|
|
24
25
|
mutationFn: async (id) => {
|
|
25
26
|
await apiClient.patch(ADMIN_ENDPOINTS.PAYMENT_METHOD_BY_ID(id), { action: "flag_suspicious" });
|
|
26
27
|
},
|
|
@@ -28,9 +29,6 @@ export function AdminPaymentClustersView(_props) {
|
|
|
28
29
|
showToast("Payment method flagged as suspicious.", "success");
|
|
29
30
|
void queryClient.invalidateQueries({ queryKey: ["admin", "payment-clusters"] });
|
|
30
31
|
},
|
|
31
|
-
onError: (err) => {
|
|
32
|
-
showToast(err.message ?? "Failed to flag payment method.", "error");
|
|
33
|
-
},
|
|
34
32
|
});
|
|
35
33
|
const clusters = data?.clusters ?? [];
|
|
36
34
|
return (_jsxs(Stack, { gap: "lg", padding: "page", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", color: "primary", children: "Payment Method Clusters" }), _jsx(Text, { size: "sm", color: "muted", className: "mt-1", children: "Multiple accounts sharing the same payment identifier. Flagging is informational only \u2014 users are not blocked." })] }), isLoading && (_jsx(Row, { justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), error && (_jsx(Div, { surface: "danger-surface", color: "error", padding: "inline", rounded: "xl", textSize: "sm", children: "Failed to load payment clusters." })), !isLoading && clusters.length === 0 && (_jsx(Div, { surface: "muted", padding: "y-4xl", rounded: "xl", className: "text-center", children: _jsx(Text, { color: "muted", size: "sm", children: "No shared payment identifiers found." }) })), clusters.map((cluster) => (_jsxs(Stack, { surface: "card", padding: "md", gap: "md", rounded: "xl", border: "default", children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", color: "muted", weight: "medium", children: "Identifier Hash" }), _jsx(Span, { size: "xs", color: "muted", className: "font-mono truncate max-w-xs", children: cluster.identifierHash })] }), _jsxs(Badge, { variant: "secondary", size: "sm", children: [cluster.methods.length, " accounts"] })] }), _jsx(Stack, { gap: "xs", children: cluster.methods.map((m) => (_jsxs(Row, { justify: "between", align: "center", gap: "sm", surface: "muted", padding: "inlineSm", rounded: "lg", children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", color: "primary", className: "truncate", children: [m.type.toUpperCase(), " \u00B7 ", m.displayLabel] }), _jsxs(Text, { size: "xs", color: "muted", children: ["User: ", m.userId] })] }), _jsxs(Row, { gap: "xs", align: "center", children: [m.banStatus && (_jsx(Badge, { variant: BAN_BADGE[m.banStatus] ?? "secondary", size: "sm", children: m.banStatus.replace(/_/g, " ") })), m.banStatus !== "suspicious" && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => flagMutation.mutate(m.id), disabled: flagMutation.isPending, children: "Flag Suspicious" }))] })] }, m.id))) })] }, cluster.identifierHash)))] }));
|
|
@@ -127,6 +127,7 @@ export function AdminPaymentMethodsView(_props) {
|
|
|
127
127
|
const queryClient = useQueryClient();
|
|
128
128
|
const { showToast } = useToast();
|
|
129
129
|
const actionMutation = useApiMutation({
|
|
130
|
+
errorMessage: "Failed to update payment method.",
|
|
130
131
|
mutationFn: async ({ id, action }) => {
|
|
131
132
|
await apiClient.patch(ADMIN_ENDPOINTS.PAYMENT_METHOD_BY_ID(id), { action });
|
|
132
133
|
},
|
|
@@ -134,9 +135,6 @@ export function AdminPaymentMethodsView(_props) {
|
|
|
134
135
|
showToast("Payment method updated.", "success");
|
|
135
136
|
void queryClient.invalidateQueries({ queryKey: ["admin", "payment-methods"] });
|
|
136
137
|
},
|
|
137
|
-
onError: (err) => {
|
|
138
|
-
showToast(err.message ?? "Failed to update payment method.", "error");
|
|
139
|
-
},
|
|
140
138
|
});
|
|
141
139
|
const handleAction = (id, action) => {
|
|
142
140
|
actionMutation.mutate({ id, action });
|
|
@@ -15,12 +15,13 @@ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
|
15
15
|
import { payoutMarkPaidSchema } from "../schemas/small-forms";
|
|
16
16
|
import { FieldInput } from "../../../ui/forms/FieldInput";
|
|
17
17
|
import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
|
|
18
|
-
import { applyZodIssues } from "../../../ui/forms/
|
|
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;
|