@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
|
@@ -73,13 +73,13 @@ function DrawerContent({ groups, items, activeHref, onItemClick, }) {
|
|
|
73
73
|
const hasActive = !!findActiveNavItem(group.items, activeHref);
|
|
74
74
|
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
|
|
75
75
|
? "text-primary-600 dark:text-primary-400"
|
|
76
|
-
: "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));
|
|
76
|
+
: "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));
|
|
77
77
|
})] }));
|
|
78
78
|
}
|
|
79
79
|
function DrawerPanel({ title, onClose, children, }) {
|
|
80
80
|
const { hand } = useHandMode();
|
|
81
81
|
const { edgeClass, borderClass } = getSidebarOverlayClasses(hand);
|
|
82
|
-
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 })] })] }));
|
|
82
|
+
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 })] })] }));
|
|
83
83
|
}
|
|
84
84
|
export function UserSidebar({ items, groups, mobileOpen = false, onCloseMobile, desktopOpen = false, onToggle, variant = "overlay", renderFooter }) {
|
|
85
85
|
const pathname = usePathname();
|
|
@@ -69,6 +69,7 @@ export function UserSupportView(_props) {
|
|
|
69
69
|
const tickets = data?.tickets ?? [];
|
|
70
70
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["user", "support-tickets"] });
|
|
71
71
|
const createMutation = useApiMutation({
|
|
72
|
+
errorMessage: "Failed to create ticket.",
|
|
72
73
|
mutationFn: async () => {
|
|
73
74
|
/*
|
|
74
75
|
* This drawer validated NOTHING. Its sibling page
|
|
@@ -106,11 +107,9 @@ export function UserSupportView(_props) {
|
|
|
106
107
|
setNewOrderId("");
|
|
107
108
|
invalidate();
|
|
108
109
|
},
|
|
109
|
-
onError: (err) => {
|
|
110
|
-
showToast(err?.message ?? "Failed to create ticket.", "error");
|
|
111
|
-
},
|
|
112
110
|
});
|
|
113
111
|
const replyMutation = useApiMutation({
|
|
112
|
+
errorMessage: "Failed to send reply.",
|
|
114
113
|
mutationFn: async () => {
|
|
115
114
|
await apiClient.post(SUPPORT_ENDPOINTS.TICKET_MESSAGES(selectedTicket.id), { body: replyBody.trim() });
|
|
116
115
|
},
|
|
@@ -119,9 +118,6 @@ export function UserSupportView(_props) {
|
|
|
119
118
|
setReplyBody("");
|
|
120
119
|
invalidate();
|
|
121
120
|
},
|
|
122
|
-
onError: (err) => {
|
|
123
|
-
showToast(err?.message ?? "Failed to send reply.", "error");
|
|
124
|
-
},
|
|
125
121
|
});
|
|
126
122
|
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "mx-auto max-w-2xl", paddingY: "y-lg", paddingX: "x-md", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", children: [_jsx(Text, { size: "xl", weight: "semibold", color: "primary", children: "Support Tickets" }), _jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: () => setNewTicketOpen(true), children: "New ticket" })] }), renderTicketListArea({ isLoading, error, tickets, setSelectedTicket, setDetailOpen })] }), renderNewTicketDrawer({ newTicketOpen, setNewTicketOpen, newCategory, setNewCategory, newSubject, setNewSubject, newOrderId, setNewOrderId, newDescription, setNewDescription, createMutation }), renderTicketDetailDrawer({ detailOpen, setDetailOpen, selectedTicket, replyBody, setReplyBody, replyMutation })] }));
|
|
127
123
|
}
|
|
@@ -45,4 +45,10 @@ export declare function useUpdateCurrentProfile(options?: {
|
|
|
45
45
|
onSuccess?: (data: JsonValue) => void;
|
|
46
46
|
onError?: (error: Error) => void;
|
|
47
47
|
endpoint?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Curated failure copy, forwarded to the ONE error surface. A wrapper that
|
|
50
|
+
* omits this forces its callers to add a second toast of their own, which
|
|
51
|
+
* is exactly how one failed avatar save produced three stacked banners.
|
|
52
|
+
*/
|
|
53
|
+
errorMessage?: string;
|
|
48
54
|
}): import("@tanstack/react-query").UseMutationResult<JsonValue, Error, UpdateCurrentProfileInput, unknown>;
|
|
@@ -14,6 +14,7 @@ export function useUpdateCurrentProfile(options) {
|
|
|
14
14
|
const queryClient = useQueryClient();
|
|
15
15
|
const endpoint = options?.endpoint ?? ACCOUNT_ENDPOINTS.PROFILE;
|
|
16
16
|
return useApiMutation({
|
|
17
|
+
errorMessage: options?.errorMessage,
|
|
17
18
|
mutationFn: (data) => apiClient.patch(endpoint, data),
|
|
18
19
|
onSuccess: async (data) => {
|
|
19
20
|
await queryClient.invalidateQueries({ queryKey: ["profile"] });
|
|
@@ -83,6 +83,7 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
|
|
|
83
83
|
setThirdPartyUrl(item.creative?.thirdPartyUrl || "");
|
|
84
84
|
}, [adQuery.data]);
|
|
85
85
|
const saveMutation = useApiMutation({
|
|
86
|
+
errorMessage: "Save failed",
|
|
86
87
|
mutationFn: async () => {
|
|
87
88
|
const payload = {
|
|
88
89
|
name,
|
|
@@ -116,9 +117,6 @@ export function AdminAdEditorView({ adId, endpointBase = ADMIN_ENDPOINTS.ADS, la
|
|
|
116
117
|
onSaved?.(savedId);
|
|
117
118
|
}
|
|
118
119
|
},
|
|
119
|
-
onError: (error) => {
|
|
120
|
-
showToast(error instanceof Error ? error.message : "Save failed", "error");
|
|
121
|
-
},
|
|
122
120
|
});
|
|
123
121
|
const placements = adQuery.data?.placements ?? metaQuery.data?.placements ?? [];
|
|
124
122
|
const providerCredentialStatus = metaQuery.data?.providerCredentialStatus;
|
|
@@ -22,6 +22,7 @@ export function AdminAddressClustersView(_props) {
|
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
const flagMutation = useApiMutation({
|
|
25
|
+
errorMessage: "Failed to flag address.",
|
|
25
26
|
mutationFn: async (id) => {
|
|
26
27
|
await apiClient.patch(ADMIN_ENDPOINTS.ADDRESS_BY_ID(id), { action: "flag_suspicious" });
|
|
27
28
|
},
|
|
@@ -29,9 +30,6 @@ export function AdminAddressClustersView(_props) {
|
|
|
29
30
|
showToast("Address flagged as suspicious.", "success");
|
|
30
31
|
void queryClient.invalidateQueries({ queryKey: ["admin", "address-clusters"] });
|
|
31
32
|
},
|
|
32
|
-
onError: (err) => {
|
|
33
|
-
showToast(err.message ?? "Failed to flag address.", "error");
|
|
34
|
-
},
|
|
35
33
|
});
|
|
36
34
|
const clusters = data?.clusters ?? [];
|
|
37
35
|
return (_jsxs(Stack, { gap: "lg", padding: "page", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", color: "primary", children: "Address Clusters" }), _jsx(Text, { size: "sm", color: "muted", className: "mt-1", children: "Users sharing the same physical address. 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 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 addresses 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: "Hash" }), _jsx(Span, { size: "xs", color: "muted", className: "font-mono truncate max-w-xs", children: cluster.addressHash })] }), _jsxs(Badge, { variant: "secondary", size: "sm", children: [cluster.addresses.length, " accounts"] })] }), _jsx(Stack, { gap: "xs", children: cluster.addresses.map((addr) => (_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: [addr.ownerType === "user" ? "User" : "Store", ": ", addr.ownerId] }), addr.city && (_jsx(Text, { size: "xs", color: "muted", children: [addr.city, addr.state, addr.postalCode].filter(Boolean).join(", ") }))] }), _jsxs(Row, { gap: "xs", align: "center", children: [addr.banStatus && (_jsx(Badge, { variant: BAN_BADGE[addr.banStatus] ?? "secondary", size: "sm", children: addr.banStatus.replace(/_/g, " ") })), addr.banStatus !== "suspicious" && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => flagMutation.mutate(addr.id), disabled: flagMutation.isPending, children: "Flag Suspicious" }))] })] }, addr.id))) })] }, cluster.addressHash)))] }));
|
|
@@ -64,6 +64,7 @@ export function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest
|
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
66
|
const saveMutation = useApiMutation({
|
|
67
|
+
errorMessage: "Failed to save address.",
|
|
67
68
|
mutationFn: async () => {
|
|
68
69
|
const payload = {
|
|
69
70
|
...form,
|
|
@@ -81,11 +82,9 @@ export function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest
|
|
|
81
82
|
if (onSaved && id)
|
|
82
83
|
onSaved(String(id));
|
|
83
84
|
},
|
|
84
|
-
onError: (err) => {
|
|
85
|
-
showToast(err?.message ?? "Failed to save address.", "error");
|
|
86
|
-
},
|
|
87
85
|
});
|
|
88
86
|
const deleteMutation = useApiMutation({
|
|
87
|
+
errorMessage: "Failed to delete address.",
|
|
89
88
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.ADDRESS_BY_ID(addressId)),
|
|
90
89
|
onSuccess: () => {
|
|
91
90
|
showToast("Address deleted.", "success");
|
|
@@ -93,7 +92,6 @@ export function AdminAddressEditorView({ addressId, onSaved, onDeleted, ...rest
|
|
|
93
92
|
if (onDeleted)
|
|
94
93
|
onDeleted();
|
|
95
94
|
},
|
|
96
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete address.", "error"),
|
|
97
95
|
});
|
|
98
96
|
const isSubmitting = saveMutation.isPending || isLoading;
|
|
99
97
|
const canSave = form.ownerId &&
|
|
@@ -133,6 +133,7 @@ export function AdminAddressesView(_props) {
|
|
|
133
133
|
const queryClient = useQueryClient();
|
|
134
134
|
const { showToast } = useToast();
|
|
135
135
|
const actionMutation = useApiMutation({
|
|
136
|
+
errorMessage: "Failed to update address.",
|
|
136
137
|
mutationFn: async ({ id, action }) => {
|
|
137
138
|
await apiClient.patch(ADMIN_ENDPOINTS.ADDRESS_BY_ID(id), { action });
|
|
138
139
|
},
|
|
@@ -140,9 +141,6 @@ export function AdminAddressesView(_props) {
|
|
|
140
141
|
showToast("Address updated.", "success");
|
|
141
142
|
void queryClient.invalidateQueries({ queryKey: ["admin", "addresses"] });
|
|
142
143
|
},
|
|
143
|
-
onError: (err) => {
|
|
144
|
-
showToast(err.message ?? "Failed to update address.", "error");
|
|
145
|
-
},
|
|
146
144
|
});
|
|
147
145
|
const handleAction = (id, action) => {
|
|
148
146
|
actionMutation.mutate({ id, action });
|
|
@@ -17,6 +17,7 @@ export function AdminAllEventEntriesView({ children, ...props }) {
|
|
|
17
17
|
const { showToast } = useToast();
|
|
18
18
|
const [selected, setSelected] = React.useState(null);
|
|
19
19
|
const updateMutation = useApiMutation({
|
|
20
|
+
errorMessage: "Failed to update entry.",
|
|
20
21
|
mutationFn: async ({ id, status }) => {
|
|
21
22
|
await apiClient.patch(ADMIN_ENDPOINTS.ADMIN_EVENT_ENTRY_BY_ID(id), { status });
|
|
22
23
|
},
|
|
@@ -24,9 +25,6 @@ export function AdminAllEventEntriesView({ children, ...props }) {
|
|
|
24
25
|
showToast("Entry updated.", "success");
|
|
25
26
|
queryClient.invalidateQueries({ queryKey: ["admin", "event-entries"] });
|
|
26
27
|
},
|
|
27
|
-
onError: (err) => {
|
|
28
|
-
showToast(err?.message ?? "Failed to update entry.", "error");
|
|
29
|
-
},
|
|
30
28
|
});
|
|
31
29
|
if (React.Children.count(children) > 0) {
|
|
32
30
|
return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
|
|
@@ -24,6 +24,7 @@ export function AdminBidsView({ children, ...props }) {
|
|
|
24
24
|
const [selectedRow, setSelectedRow] = useState(null);
|
|
25
25
|
const [detail, setDetail] = useState(null);
|
|
26
26
|
const cancelMutation = useApiMutation({
|
|
27
|
+
errorMessage: "Failed to cancel bid.",
|
|
27
28
|
mutationFn: async (bidId) => {
|
|
28
29
|
await apiClient.patch(ADMIN_ENDPOINTS.BID_BY_ID(bidId), { status: "cancelled" });
|
|
29
30
|
},
|
|
@@ -33,9 +34,6 @@ export function AdminBidsView({ children, ...props }) {
|
|
|
33
34
|
setCancelOpen(false);
|
|
34
35
|
setSelectedRow(null);
|
|
35
36
|
},
|
|
36
|
-
onError: (err) => {
|
|
37
|
-
showToast(err?.message ?? "Failed to cancel bid.", "error");
|
|
38
|
-
},
|
|
39
37
|
});
|
|
40
38
|
if (React.Children.count(children) > 0) {
|
|
41
39
|
return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
|
|
@@ -114,6 +114,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
114
114
|
}, [postQuery.data]);
|
|
115
115
|
// --- save ---
|
|
116
116
|
const saveMutation = useApiMutation({
|
|
117
|
+
errorMessage: "Failed to save post.",
|
|
117
118
|
mutationFn: async () => {
|
|
118
119
|
const payload = {
|
|
119
120
|
title: draft.title,
|
|
@@ -164,18 +165,17 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
|
|
|
164
165
|
if (issues && issues.length > 0) {
|
|
165
166
|
applyZodIssues(issues, setFieldError);
|
|
166
167
|
}
|
|
167
|
-
showToast(err?.message ?? "Failed to save post.", "error");
|
|
168
168
|
},
|
|
169
169
|
});
|
|
170
170
|
// --- delete ---
|
|
171
171
|
const deleteMutation = useApiMutation({
|
|
172
|
+
errorMessage: "Failed to delete post.",
|
|
172
173
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.BLOG_BY_ID(postId)),
|
|
173
174
|
onSuccess: () => {
|
|
174
175
|
showToast("Post deleted.", "success");
|
|
175
176
|
if (onDeleted)
|
|
176
177
|
onDeleted();
|
|
177
178
|
},
|
|
178
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete post.", "error"),
|
|
179
179
|
});
|
|
180
180
|
const isLoading = saveMutation.isPending || postQuery.isLoading;
|
|
181
181
|
/**
|
|
@@ -5,6 +5,7 @@ import { useApiMutation } from "@mohasinac/appkit/client";
|
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { useQuery } from "@tanstack/react-query";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { mediaUrlSchema } from "../../../validation/schemas";
|
|
8
9
|
import { Button, ConfirmDeleteModal, Div, Form, Input, StackedViewShell, Toggle, useToast, } from "../../../ui";
|
|
9
10
|
import { FieldInput, FormErrorSummary } from "../../../ui/forms";
|
|
10
11
|
import { ImageUpload } from "../../media/upload/ImageUpload";
|
|
@@ -15,7 +16,7 @@ const brandFormSchema = z.object({
|
|
|
15
16
|
name: z.string().min(1, "Brand name is required").max(120),
|
|
16
17
|
slug: z.string().regex(/^[a-z0-9-]+$/i).optional().or(z.literal("")),
|
|
17
18
|
description: z.string().max(2000).optional().or(z.literal("")),
|
|
18
|
-
logoURL:
|
|
19
|
+
logoURL: mediaUrlSchema.optional().or(z.literal("")),
|
|
19
20
|
website: z.string().url().optional().or(z.literal("")),
|
|
20
21
|
country: z.string().max(80).optional().or(z.literal("")),
|
|
21
22
|
founded: z.number().int().min(1800).max(new Date().getFullYear()).optional(),
|
|
@@ -75,6 +76,7 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
|
|
|
75
76
|
setSlug(toBrandSlug(value));
|
|
76
77
|
};
|
|
77
78
|
const saveMutation = useApiMutation({
|
|
79
|
+
errorMessage: "Failed to save brand.",
|
|
78
80
|
mutationFn: async () => {
|
|
79
81
|
const payload = {
|
|
80
82
|
name,
|
|
@@ -98,18 +100,15 @@ export function AdminBrandEditorView({ brandId, onSaved, onDeleted, embedded, ..
|
|
|
98
100
|
if (onSaved && id)
|
|
99
101
|
onSaved(id);
|
|
100
102
|
},
|
|
101
|
-
onError: (err) => {
|
|
102
|
-
showToast(err?.message ?? "Failed to save brand.", "error");
|
|
103
|
-
},
|
|
104
103
|
});
|
|
105
104
|
const deleteMutation = useApiMutation({
|
|
105
|
+
errorMessage: "Failed to delete brand.",
|
|
106
106
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.BRAND_BY_ID(brandId)),
|
|
107
107
|
onSuccess: () => {
|
|
108
108
|
showToast("Brand deleted.", "success");
|
|
109
109
|
if (onDeleted)
|
|
110
110
|
onDeleted();
|
|
111
111
|
},
|
|
112
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete brand.", "error"),
|
|
113
112
|
});
|
|
114
113
|
const { upload } = useMediaUpload();
|
|
115
114
|
const isSubmitting = saveMutation.isPending || brandQuery.isLoading;
|
|
@@ -36,6 +36,7 @@ export function AdminCarouselGroupEditorView({ onCreated, carouselId, onSaved, .
|
|
|
36
36
|
setStatus(doc.status === "active" ? "active" : "draft");
|
|
37
37
|
}, [existing.data]);
|
|
38
38
|
const createMutation = useApiMutation({
|
|
39
|
+
errorMessage: "create",
|
|
39
40
|
/*
|
|
40
41
|
* PATCH on the edit path, not PUT. `/api/admin/carousels/[id]` exports
|
|
41
42
|
* GET/PATCH/DELETE — copying `AdminFeatureEditorView`'s `apiClient.put`
|
|
@@ -62,9 +63,6 @@ export function AdminCarouselGroupEditorView({ onCreated, carouselId, onSaved, .
|
|
|
62
63
|
else
|
|
63
64
|
router.push(String(ROUTES.ADMIN.CAROUSEL_DETAIL(id)));
|
|
64
65
|
},
|
|
65
|
-
onError: (err) => {
|
|
66
|
-
showToast(err?.message ?? `Failed to ${isEdit ? "update" : "create"} carousel.`, "error");
|
|
67
|
-
},
|
|
68
66
|
});
|
|
69
67
|
return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Carousel" : "New Named Carousel", sections: [
|
|
70
68
|
_jsx(Form, { schema: carouselGroupFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "name", label: "Carousel name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Homepage Hero" }), _jsx(FieldSelect, { name: "status", label: "Status", value: status, onChange: (v) => setStatus(v), options: [
|
|
@@ -82,6 +82,7 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
82
82
|
setSlug(toCategorySlug(value));
|
|
83
83
|
};
|
|
84
84
|
const saveMutation = useApiMutation({
|
|
85
|
+
errorMessage: "Failed to save category.",
|
|
85
86
|
mutationFn: async () => {
|
|
86
87
|
const payload = {
|
|
87
88
|
name,
|
|
@@ -103,18 +104,15 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
|
|
|
103
104
|
if (onSaved && id)
|
|
104
105
|
onSaved(id);
|
|
105
106
|
},
|
|
106
|
-
onError: (err) => {
|
|
107
|
-
showToast(err?.message ?? "Failed to save category.", "error");
|
|
108
|
-
},
|
|
109
107
|
});
|
|
110
108
|
const deleteMutation = useApiMutation({
|
|
109
|
+
errorMessage: "Failed to delete category.",
|
|
111
110
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.CATEGORY_BY_ID(categoryId)),
|
|
112
111
|
onSuccess: () => {
|
|
113
112
|
showToast("Category deleted.", "success");
|
|
114
113
|
if (onDeleted)
|
|
115
114
|
onDeleted();
|
|
116
115
|
},
|
|
117
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete category.", "error"),
|
|
118
116
|
});
|
|
119
117
|
const isSubmitting = saveMutation.isPending || categoryQuery.isLoading;
|
|
120
118
|
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 ? (isActive ? "Active" : "Inactive") : "New" }), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "submit", form: "category-editor-form", className: "w-full", isLoading: isSubmitting, disabled: !name || isSubmitting, children: isEdit ? "Save changes" : "Create category" }), isEdit && (_jsx(Button, { type: "button", variant: "danger", className: "w-full", isLoading: deleteMutation.isPending, onClick: () => setDeleteOpen(true), children: "Delete category" }))] }) }));
|
|
@@ -16,6 +16,7 @@ export function AdminContactEditorView({ open, onClose, submissionId, subject, n
|
|
|
16
16
|
const queryClient = useQueryClient();
|
|
17
17
|
const { showToast } = useToast();
|
|
18
18
|
const actionMutation = useApiMutation({
|
|
19
|
+
errorMessage: "Action failed.",
|
|
19
20
|
mutationFn: async (action) => {
|
|
20
21
|
await apiClient.patch(ADMIN_ENDPOINTS.CONTACT_SUBMISSION_BY_ID(submissionId), { action });
|
|
21
22
|
},
|
|
@@ -25,9 +26,6 @@ export function AdminContactEditorView({ open, onClose, submissionId, subject, n
|
|
|
25
26
|
queryClient.invalidateQueries({ queryKey: ["admin", "contact"] });
|
|
26
27
|
onClose();
|
|
27
28
|
},
|
|
28
|
-
onError: (err) => {
|
|
29
|
-
showToast(err?.message ?? "Action failed.", "error");
|
|
30
|
-
},
|
|
31
29
|
});
|
|
32
30
|
const statusColor = {
|
|
33
31
|
new: "bg-info-surface text-info",
|
|
@@ -22,6 +22,7 @@ export function AdminContactView({ children, onBulkMarkRead, onBulkArchive, onBu
|
|
|
22
22
|
const [selectedRow, setSelectedRow] = useState(null);
|
|
23
23
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
24
24
|
const actionMutation = useApiMutation({
|
|
25
|
+
errorMessage: "Action failed.",
|
|
25
26
|
mutationFn: async ({ id, action }) => {
|
|
26
27
|
await apiClient.patch(ADMIN_ENDPOINTS.CONTACT_SUBMISSION_BY_ID(id), { action });
|
|
27
28
|
},
|
|
@@ -29,11 +30,9 @@ export function AdminContactView({ children, onBulkMarkRead, onBulkArchive, onBu
|
|
|
29
30
|
showToast(`${action === "read" ? "Marked as read" : "Archived"}.`, "success");
|
|
30
31
|
queryClient.invalidateQueries({ queryKey: ["admin", "contact"] });
|
|
31
32
|
},
|
|
32
|
-
onError: (err) => {
|
|
33
|
-
showToast(err?.message ?? "Action failed.", "error");
|
|
34
|
-
},
|
|
35
33
|
});
|
|
36
34
|
const deleteMutation = useApiMutation({
|
|
35
|
+
errorMessage: "Failed to delete.",
|
|
37
36
|
mutationFn: async (id) => {
|
|
38
37
|
await apiClient.delete(ADMIN_ENDPOINTS.CONTACT_SUBMISSION_BY_ID(id));
|
|
39
38
|
},
|
|
@@ -43,9 +42,6 @@ export function AdminContactView({ children, onBulkMarkRead, onBulkArchive, onBu
|
|
|
43
42
|
setDeleteOpen(false);
|
|
44
43
|
setSelectedRow(null);
|
|
45
44
|
},
|
|
46
|
-
onError: (err) => {
|
|
47
|
-
showToast(err?.message ?? "Failed to delete.", "error");
|
|
48
|
-
},
|
|
49
45
|
});
|
|
50
46
|
if (React.Children.count(children) > 0) {
|
|
51
47
|
return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
|
|
@@ -132,6 +132,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
132
132
|
};
|
|
133
133
|
// --- save ---
|
|
134
134
|
const saveMutation = useApiMutation({
|
|
135
|
+
errorMessage: "Failed to save coupon.",
|
|
135
136
|
mutationFn: async () => {
|
|
136
137
|
const payload = {
|
|
137
138
|
name,
|
|
@@ -180,19 +181,16 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
|
|
|
180
181
|
if (onSaved && id)
|
|
181
182
|
onSaved(id);
|
|
182
183
|
},
|
|
183
|
-
onError: (err) => {
|
|
184
|
-
showToast(err?.message ?? "Failed to save coupon.", "error");
|
|
185
|
-
},
|
|
186
184
|
});
|
|
187
185
|
// --- delete ---
|
|
188
186
|
const deleteMutation = useApiMutation({
|
|
187
|
+
errorMessage: "Failed to delete coupon.",
|
|
189
188
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.COUPON_BY_ID(couponId)),
|
|
190
189
|
onSuccess: () => {
|
|
191
190
|
showToast("Coupon deleted.", "success");
|
|
192
191
|
if (onDeleted)
|
|
193
192
|
onDeleted();
|
|
194
193
|
},
|
|
195
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete coupon.", "error"),
|
|
196
194
|
});
|
|
197
195
|
const isSubmitting = saveMutation.isPending || couponQuery.isLoading;
|
|
198
196
|
const canSave = Boolean(name && (isEdit ? true : code) && discountValue !== "" && startDate);
|
|
@@ -76,6 +76,7 @@ export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayNa
|
|
|
76
76
|
});
|
|
77
77
|
};
|
|
78
78
|
const inviteMutation = useApiMutation({
|
|
79
|
+
errorMessage: "Failed to invite employee",
|
|
79
80
|
mutationFn: async () => {
|
|
80
81
|
await apiClient.post(ADMIN_ENDPOINTS.TEAM, {
|
|
81
82
|
email: email.trim(),
|
|
@@ -88,11 +89,9 @@ export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayNa
|
|
|
88
89
|
queryClient.invalidateQueries({ queryKey: ["admin", "team"] });
|
|
89
90
|
onClose();
|
|
90
91
|
},
|
|
91
|
-
onError: (err) => {
|
|
92
|
-
showToast(err.message ?? "Failed to invite employee", "error");
|
|
93
|
-
},
|
|
94
92
|
});
|
|
95
93
|
const updateMutation = useApiMutation({
|
|
94
|
+
errorMessage: "Failed to update permissions",
|
|
96
95
|
mutationFn: async () => {
|
|
97
96
|
await apiClient.put(ADMIN_ENDPOINTS.TEAM_MEMBER(userId), {
|
|
98
97
|
permissionGroup: group,
|
|
@@ -104,11 +103,9 @@ export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayNa
|
|
|
104
103
|
queryClient.invalidateQueries({ queryKey: ["admin", "team"] });
|
|
105
104
|
onClose();
|
|
106
105
|
},
|
|
107
|
-
onError: (err) => {
|
|
108
|
-
showToast(err.message ?? "Failed to update permissions", "error");
|
|
109
|
-
},
|
|
110
106
|
});
|
|
111
107
|
const revokeMutation = useApiMutation({
|
|
108
|
+
errorMessage: "Failed to revoke access",
|
|
112
109
|
mutationFn: async () => {
|
|
113
110
|
await apiClient.delete(ADMIN_ENDPOINTS.TEAM_MEMBER(userId));
|
|
114
111
|
},
|
|
@@ -118,9 +115,6 @@ export function AdminEmployeeEditorView({ open, onClose, mode, userId, displayNa
|
|
|
118
115
|
setRevokeOpen(false);
|
|
119
116
|
onClose();
|
|
120
117
|
},
|
|
121
|
-
onError: (err) => {
|
|
122
|
-
showToast(err.message ?? "Failed to revoke access", "error");
|
|
123
|
-
},
|
|
124
118
|
});
|
|
125
119
|
const isBusy = inviteMutation.isPending ||
|
|
126
120
|
updateMutation.isPending ||
|
|
@@ -91,6 +91,7 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
|
|
|
91
91
|
};
|
|
92
92
|
// --- save ---
|
|
93
93
|
const saveMutation = useApiMutation({
|
|
94
|
+
errorMessage: "Failed to save FAQ.",
|
|
94
95
|
mutationFn: async () => {
|
|
95
96
|
const payload = {
|
|
96
97
|
question,
|
|
@@ -116,19 +117,16 @@ export function AdminFaqEditorView({ faqId, onSaved, onDeleted, embedded, ...res
|
|
|
116
117
|
if (onSaved && id)
|
|
117
118
|
onSaved(id);
|
|
118
119
|
},
|
|
119
|
-
onError: (err) => {
|
|
120
|
-
showToast(err?.message ?? "Failed to save FAQ.", "error");
|
|
121
|
-
},
|
|
122
120
|
});
|
|
123
121
|
// --- delete ---
|
|
124
122
|
const deleteMutation = useApiMutation({
|
|
123
|
+
errorMessage: "Failed to delete FAQ.",
|
|
125
124
|
mutationFn: () => apiClient.delete(ADMIN_ENDPOINTS.FAQ_BY_ID(faqId)),
|
|
126
125
|
onSuccess: () => {
|
|
127
126
|
showToast("FAQ deleted.", "success");
|
|
128
127
|
if (onDeleted)
|
|
129
128
|
onDeleted();
|
|
130
129
|
},
|
|
131
|
-
onError: (err) => showToast(err?.message ?? "Failed to delete FAQ.", "error"),
|
|
132
130
|
});
|
|
133
131
|
const isSubmitting = saveMutation.isPending || faqQuery.isLoading;
|
|
134
132
|
const canSave = Boolean(question.trim()) && Boolean(answer.trim());
|
|
@@ -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
|