@mohasinac/appkit 4.1.0 → 4.1.3
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/maintenance/index.d.ts +2 -0
- package/dist/_internal/client/features/maintenance/index.js +1 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
- package/dist/_internal/server/features/auctions/index.d.ts +1 -1
- package/dist/_internal/server/features/auctions/index.js +1 -1
- package/dist/_internal/server/features/auctions/service.js +2 -2
- package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
- package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
- package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
- package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
- package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
- package/dist/_internal/shared/features/auctions/config.js +1 -1
- package/dist/_internal/shared/features/auctions/schema.js +1 -1
- package/dist/_internal/shared/features/cart/schema.js +2 -2
- package/dist/_internal/shared/features/events/schema.js +1 -1
- package/dist/_internal/shared/features/orders/schema.js +1 -1
- package/dist/_internal/shared/features/products/schema.js +3 -3
- package/dist/_internal/shared/features/promotions/schema.js +3 -3
- package/dist/_internal/shared/fees/calculator.d.ts +6 -7
- package/dist/client.d.ts +4 -4
- package/dist/client.js +4 -2
- package/dist/constants/api-endpoints.d.ts +9 -6
- package/dist/constants/api-endpoints.js +3 -2
- package/dist/contracts/client-payment-gateway.d.ts +1 -2
- package/dist/features/about/components/AboutView.d.ts +6 -16
- package/dist/features/about/components/AboutView.js +3 -3
- package/dist/features/about/index.d.ts +2 -1
- package/dist/features/about/schemas/firestore.d.ts +47 -0
- package/dist/features/about/schemas/firestore.js +6 -0
- package/dist/features/about/schemas/index.d.ts +210 -0
- package/dist/features/about/schemas/index.js +45 -0
- package/dist/features/account/hooks/useProfile.js +3 -0
- package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
- package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
- package/dist/features/admin/components/AdminCartsView.js +16 -3
- package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
- package/dist/features/admin/components/AdminCouponsView.js +14 -4
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
- package/dist/features/admin/components/AdminMediaView.js +1 -1
- package/dist/features/admin/components/AdminNotificationsView.js +31 -19
- package/dist/features/admin/components/AdminOrdersView.js +7 -0
- package/dist/features/admin/components/AdminPayoutsView.js +18 -3
- package/dist/features/admin/components/AdminProductsView.js +17 -2
- package/dist/features/admin/components/AdminReviewsView.js +16 -2
- package/dist/features/admin/components/AdminScammersView.js +7 -0
- package/dist/features/admin/components/AdminSectionsView.js +4 -0
- package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +44 -2
- package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
- package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
- package/dist/features/admin/components/AdminStoresView.js +1 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
- package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
- package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
- package/dist/features/admin/constants/filter-tabs.js +55 -27
- package/dist/features/admin/schemas/firestore.d.ts +5 -0
- package/dist/features/auctions/actions/bid-actions.js +1 -1
- package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
- package/dist/features/auctions/components/AuctionsListView.js +8 -1
- package/dist/features/auth/components/LoginForm.js +2 -2
- package/dist/features/auth/components/RegisterForm.js +2 -2
- package/dist/features/auth/permissions/constants.d.ts +16 -0
- package/dist/features/auth/permissions/constants.js +53 -0
- package/dist/features/auth/schemas/firestore.js +1 -0
- package/dist/features/blog/components/BlogFilters.js +1 -1
- package/dist/features/cart/schemas/firestore.d.ts +1 -1
- package/dist/features/categories/schemas/firestore.d.ts +1 -1
- package/dist/features/consultation/components/ConsultationForm.js +1 -1
- package/dist/features/events/components/EventFilters.js +2 -0
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/layout/TitleBarLayout.d.ts +2 -2
- package/dist/features/layout/TitleBarLayout.js +4 -4
- package/dist/features/media/AvatarUpload.js +7 -3
- package/dist/features/media/MediaVideo.js +1 -1
- package/dist/features/media/finalize.d.ts +9 -13
- package/dist/features/media/finalize.js +23 -74
- package/dist/features/media/server.d.ts +1 -1
- package/dist/features/media/server.js +1 -1
- package/dist/features/orders/hooks/useOrders.js +16 -4
- package/dist/features/orders/schemas/firestore.d.ts +5 -5
- package/dist/features/payments/schemas/firestore.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
- package/dist/features/products/components/ArtStickersListView.js +5 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
- package/dist/features/products/components/ProductForm.js +14 -1
- package/dist/features/products/components/ProductsIndexPageView.js +7 -0
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/schemas/firestore.d.ts +1 -1
- package/dist/features/products/schemas/product-templates.d.ts +1 -1
- package/dist/features/products/types/index.d.ts +4 -0
- package/dist/features/scams/schemas/index.js +2 -2
- package/dist/features/search/components/Search.js +3 -3
- package/dist/features/seller/components/SellerAuctionsView.js +27 -2
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutsView.js +24 -10
- package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
- package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +1 -1
- package/dist/features/seller/components/SellerProductsView.js +8 -6
- package/dist/features/seller/components/SellerReviewsView.js +27 -6
- package/dist/features/seller/components/index.d.ts +0 -2
- package/dist/features/seller/components/index.js +0 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreProductsView.js +1 -1
- package/dist/features/stores/schemas/firestore.d.ts +11 -0
- package/dist/features/stores/schemas/firestore.js +5 -0
- package/dist/features/stores/schemas/index.d.ts +2 -2
- package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
- package/dist/features/tester/actions/checklist-item-actions.js +2 -0
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
- package/dist/features/tester/components/TesterHubView.js +34 -21
- package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
- package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
- package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
- package/dist/features/tester/schemas/firestore.d.ts +8 -3
- package/dist/features/tester/schemas/firestore.js +6 -0
- package/dist/features/tester/seed-data/bids-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/bids-tester-seed-data.js +37 -0
- package/dist/features/tester/seed-data/coupons-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/coupons-tester-seed-data.js +76 -0
- package/dist/features/tester/seed-data/events-tester-seed-data.js +50 -2
- package/dist/features/tester/seed-data/index.d.ts +3 -0
- package/dist/features/tester/seed-data/index.js +3 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.d.ts +2 -0
- package/dist/features/tester/seed-data/orders-tester-seed-data.js +265 -0
- package/dist/features/tester/seed-data/products-tester-seed-data.js +213 -8
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +294 -12
- package/dist/features/tester/utils/phases.d.ts +31 -0
- package/dist/features/tester/utils/phases.js +46 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
- package/dist/features/whatsapp-bot/types/index.d.ts +1 -1
- package/dist/features/wishlist/types/index.d.ts +3 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.js +15 -7
- package/dist/next/api/routeHandler.js +6 -10
- package/dist/next/components/NotFoundView.js +2 -1
- package/dist/next/routing/route-map.d.ts +4 -12
- package/dist/next/routing/route-map.js +2 -4
- package/dist/providers/db-firebase/admin.d.ts +1 -0
- package/dist/providers/db-firebase/admin.js +1 -1
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seed/addresses-seed-data.js +18 -18
- package/dist/seed/bids-seed-data.js +4 -4
- package/dist/seed/blog-posts-seed-data.js +82 -82
- package/dist/seed/carousel-slides-seed-data.js +14 -10
- package/dist/seed/carousels-seed-data.js +3 -3
- package/dist/seed/cart-seed-data.js +92 -92
- package/dist/seed/claimed-coupons-seed-data.js +10 -10
- package/dist/seed/conversations-seed-data.js +75 -75
- package/dist/seed/coupon-usage-seed-data.js +12 -12
- package/dist/seed/coupons-seed-data.js +41 -41
- package/dist/seed/events-seed-data.js +105 -105
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
- package/dist/seed/grouped-listings-seed-data.js +79 -143
- package/dist/seed/history-seed-data.js +38 -38
- package/dist/seed/homepage-sections-seed-data.js +9 -9
- package/dist/seed/index.d.ts +1 -1
- package/dist/seed/index.js +1 -1
- package/dist/seed/manifest.js +4 -4
- package/dist/seed/notifications-seed-data.js +56 -56
- package/dist/seed/offers-seed-data.js +91 -91
- package/dist/seed/orders-seed-data.js +125 -106
- package/dist/seed/payouts-seed-data.js +42 -42
- package/dist/seed/products-auctions-seed-data.js +10 -2
- package/dist/seed/products-classifieds-seed-data.js +48 -5
- package/dist/seed/products-digital-codes-seed-data.js +48 -5
- package/dist/seed/products-live-items-seed-data.js +6 -1
- package/dist/seed/products-prize-draws-seed-data.js +58 -5
- package/dist/seed/products-standard-seed-data.js +40 -8
- package/dist/seed/reviews-seed-data.js +42 -23
- package/dist/seed/scammers-seed-data.js +22 -22
- package/dist/seed/sessions-seed-data.js +7 -7
- package/dist/seed/shipments-seed-data.js +10 -10
- package/dist/seed/site-settings-seed-data.js +82 -0
- package/dist/seed/store-addresses-seed-data.js +2 -2
- package/dist/seed/store-extensions-seed-data.js +93 -93
- package/dist/seed/stores-seed-data.js +2 -0
- package/dist/seed/support-tickets-seed-data.js +47 -47
- package/dist/seed/users-seed-data.js +55 -89
- package/dist/seed/wishlists-seed-data.js +22 -22
- package/dist/server.d.ts +2 -1
- package/dist/server.js +5 -5
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PageLoader.js +8 -3
- package/dist/ui/components/Select.d.ts +8 -1
- package/dist/ui/components/Select.js +2 -2
- package/dist/ui/components/SiteMark.d.ts +23 -0
- package/dist/ui/components/SiteMark.js +76 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/validation/schemas.d.ts +0 -2
- package/dist/validation/schemas.js +0 -2
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +27 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
4
|
import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
|
|
5
5
|
import { sortBy } from "@mohasinac/appkit";
|
|
@@ -54,7 +54,7 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
|
|
|
54
54
|
title: "Coupons",
|
|
55
55
|
searchPlaceholder: "Search codes, campaigns, or seller scopes",
|
|
56
56
|
emptyLabel: "No coupons found",
|
|
57
|
-
filterKeys: ["type"],
|
|
57
|
+
filterKeys: ["type", "showExpired"],
|
|
58
58
|
defaultSort: sortBy("createdAt", "DESC"),
|
|
59
59
|
queryKey: ["admin", "coupons", "listing"],
|
|
60
60
|
endpoint: ADMIN_ENDPOINTS.COUPONS,
|
|
@@ -69,7 +69,14 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
|
|
|
69
69
|
raw: item,
|
|
70
70
|
})),
|
|
71
71
|
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
72
|
-
buildFilters: (state) =>
|
|
72
|
+
buildFilters: (state) => {
|
|
73
|
+
const parts = [];
|
|
74
|
+
if (state.type && state.type !== "All")
|
|
75
|
+
parts.push(sieveFilter("type", SIEVE_OP.EQ, state.type));
|
|
76
|
+
if (state.showExpired !== "true")
|
|
77
|
+
parts.push(sieveFilter("validity.isActive", SIEVE_OP.EQ, true));
|
|
78
|
+
return parts.join(",") || undefined;
|
|
79
|
+
},
|
|
73
80
|
primaryAction: {
|
|
74
81
|
label: "Add Coupon",
|
|
75
82
|
onClick: ({ openCreatePanel }) => openCreatePanel(),
|
|
@@ -95,7 +102,10 @@ export function AdminCouponsView({ children, onBulkArchive, onBulkDelete, ...pro
|
|
|
95
102
|
onClick: handlers[id],
|
|
96
103
|
}));
|
|
97
104
|
},
|
|
98
|
-
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_COUPON_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) })
|
|
105
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_COUPON_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }), _jsx(FilterChipGroup, { label: "Expired coupons", tabs: [
|
|
106
|
+
{ id: "", label: "Hide expired" },
|
|
107
|
+
{ id: "true", label: "Show all" },
|
|
108
|
+
], value: pendingFilters.showExpired ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showExpired: id })), allId: "" })] })),
|
|
99
109
|
renderCards: (rows, _view, selectionCtx, isLoading) => {
|
|
100
110
|
rowsRef.current = rows;
|
|
101
111
|
return isLoading ? (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Stack, { border: "skeleton", className: "animate-pulse", gap: "3", rounded: "xl", padding: "md", children: [_jsx(Div, { className: "h-6 w-2/3", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-4 w-full", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" })] }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "text-center", padding: "y-4xl", children: _jsx(Text, { color: "faint", children: "No coupons found" }) })) : (_jsx(Div, { gap: "3", className: "fluid-grid-card", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: selectionCtx.openEditPanel, onToggleActive: handleToggle, onDelete: handleDelete }, row.id))) }));
|
|
@@ -6,7 +6,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
6
6
|
import { Button, Checkbox, ConfirmDeleteModal, Details, Div, Form, FormActions, Input, Label, Select, SideDrawer, Span, Stack, Summary, Text, useToast } from "../../../ui";
|
|
7
7
|
import { apiClient } from "../../../http";
|
|
8
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
9
|
-
import { PERMISSION_GROUPS, } from "../../auth/permissions/constants";
|
|
9
|
+
import { PERMISSION_GROUPS, PERMISSION_DOMAINS, getPermissionsForDomain, formatPermLabel, } from "../../auth/permissions/constants";
|
|
10
10
|
const __O = {
|
|
11
11
|
yAuto: "overflow-y-auto",
|
|
12
12
|
};
|
|
@@ -32,47 +32,6 @@ const GROUP_OPTIONS = [
|
|
|
32
32
|
{ label: "Scam Moderator", value: "scam_moderator" },
|
|
33
33
|
{ label: "Custom", value: "custom" },
|
|
34
34
|
];
|
|
35
|
-
const PERMISSION_DOMAINS = [
|
|
36
|
-
{ label: "Dashboard", prefix: "admin:dashboard:" },
|
|
37
|
-
{ label: "Users & Bans", prefix: "admin:users:|admin:user-bans:" },
|
|
38
|
-
{ label: "Products", prefix: "admin:products:" },
|
|
39
|
-
{ label: "Orders & Returns", prefix: "admin:orders:|admin:returns:" },
|
|
40
|
-
{ label: "Stores", prefix: "admin:stores:|admin:store-addresses:" },
|
|
41
|
-
{ label: "Finance", prefix: "admin:analytics:|admin:payouts:" },
|
|
42
|
-
{ label: "Catalog", prefix: "admin:categories:|admin:brands:|admin:coupons:|admin:deals:|admin:featured:" },
|
|
43
|
-
{
|
|
44
|
-
label: "Content",
|
|
45
|
-
prefix: "admin:reviews:|admin:blog:|admin:bids:|admin:media:",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
label: "Site / CMS",
|
|
49
|
-
prefix: "admin:site:|admin:navigation:|admin:sections:|admin:carousel:|admin:ads:|admin:faqs:|admin:newsletter:|admin:contact:",
|
|
50
|
-
},
|
|
51
|
-
{ label: "Events", prefix: "admin:events:|admin:event-entries:" },
|
|
52
|
-
{ label: "Support & Safety", prefix: "admin:support-tickets:|admin:scammers:" },
|
|
53
|
-
{ label: "System", prefix: "admin:sessions:|admin:notifications:|admin:carts:|admin:wishlists:|admin:feature-flags:|admin:copilot:|admin:team:" },
|
|
54
|
-
];
|
|
55
|
-
function matchesDomain(perm, prefix) {
|
|
56
|
-
return prefix.split("|").some((p) => perm.startsWith(p));
|
|
57
|
-
}
|
|
58
|
-
function getPermissionsForDomain(prefix) {
|
|
59
|
-
const allPerms = Object.values(PERMISSION_GROUPS).flat();
|
|
60
|
-
const seen = new Set();
|
|
61
|
-
const result = [];
|
|
62
|
-
for (const p of allPerms) {
|
|
63
|
-
if (matchesDomain(p, prefix) && !seen.has(p)) {
|
|
64
|
-
seen.add(p);
|
|
65
|
-
result.push(p);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
function formatPermLabel(perm) {
|
|
71
|
-
const parts = perm.split(":");
|
|
72
|
-
return parts[parts.length - 1]
|
|
73
|
-
.replace(/-/g, " ")
|
|
74
|
-
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
75
|
-
}
|
|
76
35
|
function PermissionDomainsPanel({ permissions, togglePerm }) {
|
|
77
36
|
return (_jsxs(Stack, { gap: "sm", children: [_jsxs(Span, { size: "sm", weight: "medium", color: "muted", children: ["Permissions", _jsxs(Span, { size: "xs", weight: "normal", className: "ml-2", color: "muted", children: ["(", permissions.size, " selected)"] })] }), _jsx(Div, { className: `divide-y divide-zinc-100 divide-[var(--appkit-color-border)] max-h-[42vh] ${__O.yAuto}`, rounded: "xl", border: "default", children: PERMISSION_DOMAINS.map((domain) => {
|
|
78
37
|
const domainPerms = getPermissionsForDomain(domain.prefix);
|
|
@@ -64,7 +64,7 @@ function MediaBrowser({ onCopy }) {
|
|
|
64
64
|
return files;
|
|
65
65
|
return files.filter((f) => f.name.toLowerCase().includes(q));
|
|
66
66
|
}, [files, search]);
|
|
67
|
-
return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter",
|
|
67
|
+
return (_jsxs(Stack, { className: `${__P.p4}`, gap: "3", rounded: "xl", surface: "default", border: "default", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Browse existing media" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Select, { value: prefix, onChange: (e) => setPrefix(e.target.value), options: PREFIX_OPTIONS, "aria-label": "Prefix filter", wrapperClassName: "min-w-[140px]" }), _jsx(Input, { type: "text", placeholder: "Search filename\u2026", value: search, onChange: (e) => setSearch(e.target.value), className: "flex-1 min-w-[160px]" })] }), error && (_jsx(Alert, { variant: "error", title: "Load failed", children: error })), _jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6", children: [filtered.map((f) => {
|
|
68
68
|
const isImage = (f.contentType ?? "").startsWith("image/");
|
|
69
69
|
return (_jsxs(Div, { className: "group overflow-hidden", surface: "muted", rounded: "lg", border: "default", children: [_jsx(Row, { color: "muted", textSize: "xs", className: "relative aspect-square", surface: "subtle", align: "center", justify: "center", children: isImage ? (_jsx(MediaImage, { src: f.downloadURL, alt: f.name, size: "thumbnail" })) : (_jsx(Text, { align: "center", paddingX: "x-xs", className: "break-all", children: f.contentType ?? "file" })) }), _jsxs(Div, { className: "p-[0.375rem]", children: [_jsx(Text, { className: "truncate text-[10px] font-mono", color: "muted", children: f.name.split("/").pop() }), _jsx(Button, { type: "button", variant: "ghost", textSize: "xs", className: "mt-1 w-full", onClick: () => onCopy(f.downloadURL), children: ACTIONS.MEDIA["copy-url"].label })] })] }, f.name));
|
|
70
70
|
}), filtered.length === 0 && !isLoading && (_jsx(Text, { paddingY: "lg", className: "col-span-full", color: "muted", size: "sm", align: "center", children: "No files found." }))] }), _jsxs(Row, { align: "center", justify: "between", children: [_jsxs(Text, { size: "xs", color: "muted", children: [filtered.length, " file(s)", nextPageToken ? " · more available" : ""] }), nextPageToken && (_jsx(Button, { type: "button", variant: "outline", onClick: () => void load(nextPageToken), disabled: isLoading, children: isLoading ? "Loading…" : "Load more" }))] })] }));
|
|
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
8
8
|
import { ConfirmDeleteModal, FilterChipGroup, ListingLayout, RowActionMenu, useToast } from "../../../ui";
|
|
9
9
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
10
10
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
11
|
-
import { ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
|
|
11
|
+
import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
|
|
12
12
|
import { useBulkAction } from "../../../react";
|
|
13
13
|
import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
|
|
14
14
|
import { DataListingView } from "./DataListingView";
|
|
@@ -64,7 +64,7 @@ export function AdminNotificationsView({ children, ...props }) {
|
|
|
64
64
|
title: "Notifications",
|
|
65
65
|
searchPlaceholder: "Search by title or user ID",
|
|
66
66
|
emptyLabel: "No notifications found",
|
|
67
|
-
filterKeys: ["type"],
|
|
67
|
+
filterKeys: ["type", "readState"],
|
|
68
68
|
defaultSort: sortBy("createdAt", "DESC"),
|
|
69
69
|
queryKey: ["admin", "notifications", "listing"],
|
|
70
70
|
endpoint: ADMIN_ENDPOINTS.ADMIN_NOTIFICATIONS,
|
|
@@ -83,25 +83,33 @@ export function AdminNotificationsView({ children, ...props }) {
|
|
|
83
83
|
type: toStringValue(item.type, ""),
|
|
84
84
|
})),
|
|
85
85
|
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
86
|
-
buildFilters: (state) =>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
buildFilters: (state) => {
|
|
87
|
+
const parts = [];
|
|
88
|
+
if (state.type && state.type !== "All")
|
|
89
|
+
parts.push(sieveFilter("type", SIEVE_OP.EQ, state.type));
|
|
90
|
+
if (state.readState === "unread")
|
|
91
|
+
parts.push(sieveFilter("isRead", SIEVE_OP.EQ, false));
|
|
92
|
+
else if (state.readState === "read")
|
|
93
|
+
parts.push(sieveFilter("isRead", SIEVE_OP.EQ, true));
|
|
94
|
+
return parts.join(",") || undefined;
|
|
95
|
+
},
|
|
96
|
+
// Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
|
|
97
|
+
buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.notifications.map((id) => ({
|
|
98
|
+
id,
|
|
99
|
+
label: ROW_ACTION_META[id].label,
|
|
100
|
+
destructive: ROW_ACTION_META[id].destructive,
|
|
101
|
+
variant: id === ROW_ACTION_ID.MARK_READ ? "primary" : "secondary",
|
|
102
|
+
loading: id === ROW_ACTION_ID.MARK_READ ? bulkNotifs.isLoading : undefined,
|
|
103
|
+
onClick: () => {
|
|
104
|
+
if (id === ROW_ACTION_ID.MARK_READ) {
|
|
94
105
|
void bulkNotifs.execute({ action: "mark_read", ids: selection.selectedIds });
|
|
95
106
|
selection.clearSelection();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
label: ACTIONS.ADMIN["delete-notification"].label,
|
|
101
|
-
variant: "secondary",
|
|
102
|
-
onClick: () => setBulkDeleteIds(selection.selectedIds),
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
setBulkDeleteIds(selection.selectedIds);
|
|
110
|
+
}
|
|
103
111
|
},
|
|
104
|
-
|
|
112
|
+
})),
|
|
105
113
|
renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
|
|
106
114
|
{
|
|
107
115
|
label: ACTIONS.ADMIN["resend-notification"].label,
|
|
@@ -113,7 +121,11 @@ export function AdminNotificationsView({ children, ...props }) {
|
|
|
113
121
|
onClick: () => setDeleteTarget(row),
|
|
114
122
|
},
|
|
115
123
|
] })),
|
|
116
|
-
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Type", tabs: NOTIF_TYPES.map((opt) => ({ id: opt, label: opt })), value: pendingFilters.type || "All", onChange: (v) => setPendingFilters((p) => ({ ...p, type: v })) })
|
|
124
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Type", tabs: NOTIF_TYPES.map((opt) => ({ id: opt, label: opt })), value: pendingFilters.type || "All", onChange: (v) => setPendingFilters((p) => ({ ...p, type: v })) }), _jsx(FilterChipGroup, { label: "Read state", tabs: [
|
|
125
|
+
{ id: "", label: "All" },
|
|
126
|
+
{ id: "unread", label: "Unread" },
|
|
127
|
+
{ id: "read", label: "Read" },
|
|
128
|
+
], value: pendingFilters.readState ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, readState: id })), allId: "" })] })),
|
|
117
129
|
};
|
|
118
130
|
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(ConfirmDeleteModal, { isOpen: Boolean(deleteTarget) || Boolean(bulkDeleteIds), onClose: () => {
|
|
119
131
|
setDeleteTarget(null);
|
|
@@ -3,10 +3,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
|
|
4
4
|
import { sortBy } from "@mohasinac/appkit";
|
|
5
5
|
import React, { useState, useCallback } from "react";
|
|
6
|
+
import { useRouter } from "next/navigation";
|
|
6
7
|
import { FilterChipGroup, ListingLayout, useToast } from "../../../ui";
|
|
7
8
|
import { apiClient } from "../../../http";
|
|
8
9
|
import { QuickEditMenu } from "./QuickEditMenu";
|
|
9
10
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
11
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
10
12
|
import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
|
|
11
13
|
import { ADMIN_ORDER_STATUS_TABS } from "../constants/filter-tabs";
|
|
12
14
|
import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
|
|
@@ -14,6 +16,7 @@ import { DataListingView } from "./DataListingView";
|
|
|
14
16
|
import { AdminOrderEditorView } from "./AdminOrderEditorView";
|
|
15
17
|
export function AdminOrdersView({ children, ...props }) {
|
|
16
18
|
const { showToast } = useToast();
|
|
19
|
+
const router = useRouter();
|
|
17
20
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
18
21
|
const [selectedRow, setSelectedRow] = useState(null);
|
|
19
22
|
const handleQuickStatus = useCallback(async (id, status) => {
|
|
@@ -94,6 +97,10 @@ export function AdminOrdersView({ children, ...props }) {
|
|
|
94
97
|
setDrawerOpen(true);
|
|
95
98
|
},
|
|
96
99
|
},
|
|
100
|
+
{
|
|
101
|
+
label: "Open full page",
|
|
102
|
+
onClick: () => router.push(String(ROUTES.ADMIN.ORDER_DETAIL(row.id))),
|
|
103
|
+
},
|
|
97
104
|
{
|
|
98
105
|
label: "Update status",
|
|
99
106
|
separator: true,
|
|
@@ -104,7 +104,7 @@ export function AdminPayoutsView({ children, ...props }) {
|
|
|
104
104
|
title: "Payouts",
|
|
105
105
|
searchPlaceholder: "Search stores, payout IDs, or order groups",
|
|
106
106
|
emptyLabel: "No payouts found",
|
|
107
|
-
filterKeys: ["status"],
|
|
107
|
+
filterKeys: ["status", "showAllPayouts"],
|
|
108
108
|
defaultSort: sortBy("createdAt", "DESC"),
|
|
109
109
|
queryKey: ["admin", "payouts", "listing"],
|
|
110
110
|
endpoint: ADMIN_ENDPOINTS.PAYOUTS,
|
|
@@ -121,7 +121,19 @@ export function AdminPayoutsView({ children, ...props }) {
|
|
|
121
121
|
updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
|
|
122
122
|
})),
|
|
123
123
|
getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
|
|
124
|
-
buildFilters: (state) =>
|
|
124
|
+
buildFilters: (state) => {
|
|
125
|
+
// An explicit status chip always wins. Otherwise, hide the noise
|
|
126
|
+
// (already-paid payouts) by default — mirrors the isSold-hide-by-
|
|
127
|
+
// default precedent — with an escape hatch to see everything.
|
|
128
|
+
// Pipe-joined EQ, not NEQ — the Firestore rule requiring orderBy to
|
|
129
|
+
// match the inequality field does not apply to IN queries, so this
|
|
130
|
+
// stays compatible with the default createdAt sort.
|
|
131
|
+
if (state.status && state.status !== "All")
|
|
132
|
+
return sieveFilter("status", SIEVE_OP.EQ, state.status);
|
|
133
|
+
return state.showAllPayouts !== "true"
|
|
134
|
+
? sieveFilter("status", SIEVE_OP.EQ, "pending|processing|failed|cancelled")
|
|
135
|
+
: undefined;
|
|
136
|
+
},
|
|
125
137
|
toolbarExtra: (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", action: ACTIONS.ADMIN["calculate-payouts"], size: "sm", isLoading: calculating, disabled: calculating, onClick: handleCalculatePayouts }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: handleExportCsv, children: ACTIONS.ADMIN["export-csv"].label })] })),
|
|
126
138
|
// Rule #7: bulk-action array sourced from the ADMIN_BULK_ACTIONS preset.
|
|
127
139
|
buildBulkActions: (selection) => ADMIN_BULK_ACTIONS.payouts.map((id) => ({
|
|
@@ -140,7 +152,10 @@ export function AdminPayoutsView({ children, ...props }) {
|
|
|
140
152
|
disabled: row.status.toLowerCase() === "paid" || row.status.toLowerCase() === "cancelled",
|
|
141
153
|
},
|
|
142
154
|
] })),
|
|
143
|
-
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })
|
|
155
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PAYOUT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Paid payouts", tabs: [
|
|
156
|
+
{ id: "", label: "Hide paid" },
|
|
157
|
+
{ id: "true", label: "Show all" },
|
|
158
|
+
], value: pendingFilters.showAllPayouts ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showAllPayouts: id })), allId: "" })] })),
|
|
144
159
|
};
|
|
145
160
|
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(Modal, { isOpen: markPaidOpen, onClose: closePaidModal, title: "Mark payout as paid", children: _jsxs(Form, { onSubmit: (e) => {
|
|
146
161
|
e.preventDefault();
|
|
@@ -108,7 +108,7 @@ export function AdminProductsView({ children, ...props }) {
|
|
|
108
108
|
title: "Products",
|
|
109
109
|
searchPlaceholder: "Search products, SKUs, or seller names",
|
|
110
110
|
emptyLabel: "No products found",
|
|
111
|
-
filterKeys: ["status", "type", "showSold"],
|
|
111
|
+
filterKeys: ["status", "type", "showSold", "showEnded", "showClosed"],
|
|
112
112
|
defaultSort: sortBy("createdAt", "DESC"),
|
|
113
113
|
queryKey: ["admin", "products", "listing"],
|
|
114
114
|
endpoint: ADMIN_ENDPOINTS.PRODUCTS,
|
|
@@ -149,6 +149,15 @@ export function AdminProductsView({ children, ...props }) {
|
|
|
149
149
|
const typeFilter = TYPE_FILTER_MAP[state.type];
|
|
150
150
|
if (typeFilter)
|
|
151
151
|
parts.push(typeFilter);
|
|
152
|
+
// Ended/closed only apply to auctions/prize-draws — auctionEndDate
|
|
153
|
+
// and prizeRevealWindowEnd do not exist on other listing types, so
|
|
154
|
+
// these are no-ops unless the matching Type tab is also selected.
|
|
155
|
+
if (typeFilter === "listingType==auction" && state.showEnded !== "true") {
|
|
156
|
+
parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, new Date().toISOString()));
|
|
157
|
+
}
|
|
158
|
+
if (typeFilter === "listingType==prize-draw" && state.showClosed !== "true") {
|
|
159
|
+
parts.push(sieveFilter("prizeRevealWindowEnd", SIEVE_OP.GTE, new Date().toISOString()));
|
|
160
|
+
}
|
|
152
161
|
return parts.join(",") || undefined;
|
|
153
162
|
},
|
|
154
163
|
primaryAction: {
|
|
@@ -222,7 +231,13 @@ export function AdminProductsView({ children, ...props }) {
|
|
|
222
231
|
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PRODUCT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: ADMIN_PRODUCT_LISTING_TYPE_TABS, value: pendingFilters.type ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, type: id })) }), _jsx(FilterChipGroup, { label: "Sold", tabs: [
|
|
223
232
|
{ id: "", label: "Hide sold" },
|
|
224
233
|
{ id: "true", label: "Show sold" },
|
|
225
|
-
], value: pendingFilters.showSold ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showSold: id })), allId: "" })
|
|
234
|
+
], value: pendingFilters.showSold ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showSold: id })), allId: "" }), _jsx(FilterChipGroup, { label: "Ended", tabs: [
|
|
235
|
+
{ id: "", label: "Hide ended" },
|
|
236
|
+
{ id: "true", label: "Show ended" },
|
|
237
|
+
], value: pendingFilters.showEnded ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showEnded: id })), allId: "" }), _jsx(FilterChipGroup, { label: "Closed", tabs: [
|
|
238
|
+
{ id: "", label: "Hide closed" },
|
|
239
|
+
{ id: "true", label: "Show closed" },
|
|
240
|
+
], value: pendingFilters.showClosed ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, showClosed: id })), allId: "" })] })),
|
|
226
241
|
renderEditor: ({ editId, closePanel }) => (_jsx(AdminProductEditorView, { productId: editId ?? undefined, initialData: editId ? rawByIdRef.current[editId] : undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })),
|
|
227
242
|
resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Product" : "Edit Product"),
|
|
228
243
|
};
|
|
@@ -99,8 +99,22 @@ export function AdminReviewsView({ children, ...props }) {
|
|
|
99
99
|
getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
|
|
100
100
|
buildFilters: (f) => {
|
|
101
101
|
const parts = [];
|
|
102
|
-
|
|
102
|
+
// Default (f.status === "") to pending-only — hides the moderation
|
|
103
|
+
// noise (already-approved/rejected reviews) without a redundant
|
|
104
|
+
// separate toggle, since Status is already a 1-click 3-value chip
|
|
105
|
+
// group. Explicit "All" is reachable via renderFilterPanel allId
|
|
106
|
+
// override below, which makes the FilterChipGroup write the literal
|
|
107
|
+
// string "All" (not "") when clicked — otherwise "never touched" and
|
|
108
|
+
// "explicitly chose All" are indistinguishable.
|
|
109
|
+
if (f.status === "All") {
|
|
110
|
+
// explicit "show everything" — no status filter
|
|
111
|
+
}
|
|
112
|
+
else if (f.status) {
|
|
103
113
|
parts.push(sieveFilter("status", SIEVE_OP.EQ, f.status));
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
parts.push(sieveFilter("status", SIEVE_OP.EQ, "pending"));
|
|
117
|
+
}
|
|
104
118
|
if (f.rating && f.rating !== "All")
|
|
105
119
|
parts.push(sieveFilter("rating", SIEVE_OP.EQ, f.rating));
|
|
106
120
|
return parts.join(",") || undefined;
|
|
@@ -145,7 +159,7 @@ export function AdminReviewsView({ children, ...props }) {
|
|
|
145
159
|
onClick: () => handleViewReview(row),
|
|
146
160
|
},
|
|
147
161
|
] })),
|
|
148
|
-
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_REVIEW_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Rating", tabs: ADMIN_REVIEW_RATING_TABS, value: pendingFilters.rating ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, rating: id })) })] })),
|
|
162
|
+
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status (defaults to Pending)", tabs: ADMIN_REVIEW_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })), allId: "__all__" }), _jsx(FilterChipGroup, { label: "Rating", tabs: ADMIN_REVIEW_RATING_TABS, value: pendingFilters.rating ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, rating: id })) })] })),
|
|
149
163
|
};
|
|
150
164
|
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(ViewReviewModal, { review: viewReview, isOpen: Boolean(viewReview), onClose: () => setViewReview(null) }), _jsx(Modal, { isOpen: replyOpen, onClose: () => {
|
|
151
165
|
setReplyOpen(false);
|
|
@@ -3,9 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit";
|
|
4
4
|
import { sortBy } from "@mohasinac/appkit";
|
|
5
5
|
import React, { useState } from "react";
|
|
6
|
+
import { useRouter } from "next/navigation";
|
|
6
7
|
import { FilterChipGroup, ListingLayout, RowActionMenu, Span, Stack, Text } from "../../../ui";
|
|
7
8
|
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
9
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
10
|
+
import { ROUTES } from "../../../next/routing/route-map";
|
|
9
11
|
import { ADMIN_SCAMMER_STATUS_TABS } from "../constants/filter-tabs";
|
|
10
12
|
import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
|
|
11
13
|
import { DataListingView } from "./DataListingView";
|
|
@@ -36,6 +38,7 @@ const SCAMMER_COLUMNS = [
|
|
|
36
38
|
},
|
|
37
39
|
];
|
|
38
40
|
export function AdminScammersView({ children, ...props }) {
|
|
41
|
+
const router = useRouter();
|
|
39
42
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
40
43
|
const [selectedRow, setSelectedRow] = useState(null);
|
|
41
44
|
if (React.Children.count(children) > 0) {
|
|
@@ -91,6 +94,10 @@ export function AdminScammersView({ children, ...props }) {
|
|
|
91
94
|
setDrawerOpen(true);
|
|
92
95
|
},
|
|
93
96
|
},
|
|
97
|
+
{
|
|
98
|
+
label: "Open full page",
|
|
99
|
+
onClick: () => router.push(String(ROUTES.ADMIN.SCAMMER_BY_ID(row.id))),
|
|
100
|
+
},
|
|
94
101
|
] })),
|
|
95
102
|
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_SCAMMER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
|
|
96
103
|
};
|
|
@@ -200,6 +200,8 @@ export function AdminSectionsView({ children }) {
|
|
|
200
200
|
},
|
|
201
201
|
onSuccess: async () => {
|
|
202
202
|
await queryClient.invalidateQueries({ queryKey: ["admin", "sections", "listing"] });
|
|
203
|
+
// Public-facing useHomepageSections() reads under a separate key.
|
|
204
|
+
await queryClient.invalidateQueries({ queryKey: ["homepage-sections"] });
|
|
203
205
|
toast.showToast(mode === "create" ? "Section created." : "Section updated.", "success");
|
|
204
206
|
setIsModalOpen(false);
|
|
205
207
|
},
|
|
@@ -215,6 +217,8 @@ export function AdminSectionsView({ children }) {
|
|
|
215
217
|
},
|
|
216
218
|
onSuccess: async () => {
|
|
217
219
|
await queryClient.invalidateQueries({ queryKey: ["admin", "sections", "listing"] });
|
|
220
|
+
// Public-facing useHomepageSections() reads under a separate key.
|
|
221
|
+
await queryClient.invalidateQueries({ queryKey: ["homepage-sections"] });
|
|
218
222
|
setReorderServerSnapshot(cloneReorderItems(reorderDraft));
|
|
219
223
|
setReorderUndoStack([]);
|
|
220
224
|
toast.showToast("Section order updated.", "success");
|
|
@@ -13,7 +13,7 @@ export function AdminSiteConfigGuideView() {
|
|
|
13
13
|
{ name: "watermark", desc: "Watermark text and opacity applied to all product images via the /api/media proxy." },
|
|
14
14
|
{ name: "fees", desc: "platformFee (%), Razorpay gateway fee pass-through toggle, GST rate on platform fees." },
|
|
15
15
|
{ name: "integrations", desc: "API keys: Razorpay (live/test, disabled by default — manual payment is the default), Resend, WhatsApp Cloud API. Keys are encrypted at rest — never log or export." },
|
|
16
|
-
{ name: "shipping", desc: "Default shipping carriers, free shipping threshold (
|
|
16
|
+
{ name: "shipping", desc: "Default shipping carriers, free shipping threshold (₹), COD availability toggle." },
|
|
17
17
|
{ name: "auctionConfig", desc: "Global bid increment default, auto-extend window (minutes), minimum auction duration." },
|
|
18
18
|
{ name: "platformLimits", desc: "Cart item cap (50), wishlist cap (20), history cap (50), support ticket limits (2 general + 1 per order)." },
|
|
19
19
|
{ name: "legalPages", desc: "Body HTML for Terms of Service, Privacy Policy, Refund Policy, and Shipping Policy. Rendered on their respective static pages." },
|
|
@@ -189,7 +189,18 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
189
189
|
const [aboutSubtitle, setAboutSubtitle] = React.useState("");
|
|
190
190
|
const [aboutMissionTitle, setAboutMissionTitle] = React.useState("");
|
|
191
191
|
const [aboutMissionText, setAboutMissionText] = React.useState("");
|
|
192
|
+
const [aboutHowItWorksTitle, setAboutHowItWorksTitle] = React.useState("");
|
|
193
|
+
const [aboutHowItems, setAboutHowItems] = React.useState([]);
|
|
194
|
+
const [aboutValuesTitle, setAboutValuesTitle] = React.useState("");
|
|
195
|
+
const [aboutValueItems, setAboutValueItems] = React.useState([]);
|
|
196
|
+
const [aboutMilestonesTitle, setAboutMilestonesTitle] = React.useState("");
|
|
197
|
+
const [aboutMilestones, setAboutMilestones] = React.useState([]);
|
|
198
|
+
const [aboutTeamTitle, setAboutTeamTitle] = React.useState("");
|
|
199
|
+
const [aboutTeamSubtitle, setAboutTeamSubtitle] = React.useState("");
|
|
200
|
+
const [aboutTeamMembers, setAboutTeamMembers] = React.useState([]);
|
|
192
201
|
const [aboutCtaTitle, setAboutCtaTitle] = React.useState("");
|
|
202
|
+
const [aboutCtaSell, setAboutCtaSell] = React.useState("");
|
|
203
|
+
const [aboutCtaShop, setAboutCtaShop] = React.useState("");
|
|
193
204
|
// ⑭ Notification channels
|
|
194
205
|
const [notifEmailEnabled, setNotifEmailEnabled] = React.useState(false);
|
|
195
206
|
const [notifEmailMinPriority, setNotifEmailMinPriority] = React.useState("normal");
|
|
@@ -326,7 +337,18 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
326
337
|
setAboutSubtitle(s.aboutContent?.subtitle ?? "");
|
|
327
338
|
setAboutMissionTitle(s.aboutContent?.missionTitle ?? "");
|
|
328
339
|
setAboutMissionText(s.aboutContent?.missionText ?? "");
|
|
340
|
+
setAboutHowItWorksTitle(s.aboutContent?.howItWorksTitle ?? "");
|
|
341
|
+
setAboutHowItems(s.aboutContent?.howItems ?? []);
|
|
342
|
+
setAboutValuesTitle(s.aboutContent?.valuesTitle ?? "");
|
|
343
|
+
setAboutValueItems(s.aboutContent?.valueItems ?? []);
|
|
344
|
+
setAboutMilestonesTitle(s.aboutContent?.milestonesTitle ?? "");
|
|
345
|
+
setAboutMilestones(s.aboutContent?.milestones ?? []);
|
|
346
|
+
setAboutTeamTitle(s.aboutContent?.teamTitle ?? "");
|
|
347
|
+
setAboutTeamSubtitle(s.aboutContent?.teamSubtitle ?? "");
|
|
348
|
+
setAboutTeamMembers(s.aboutContent?.teamMembers ?? []);
|
|
329
349
|
setAboutCtaTitle(s.aboutContent?.ctaTitle ?? "");
|
|
350
|
+
setAboutCtaSell(s.aboutContent?.ctaSell ?? "");
|
|
351
|
+
setAboutCtaShop(s.aboutContent?.ctaShop ?? "");
|
|
330
352
|
setWaPhoneNumberId(s.credentialsMasked?.whatsappPhoneNumberId ?? "");
|
|
331
353
|
setWaCloudApiToken(s.credentialsMasked?.whatsappCloudApiToken ?? "");
|
|
332
354
|
setWaAdminNotifyNumbers(s.credentialsMasked?.whatsappAdminNotifyNumbers ?? "");
|
|
@@ -362,7 +384,24 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
362
384
|
// save action for the whole form, not 19 per-tab mutations.
|
|
363
385
|
function buildFullPayload() {
|
|
364
386
|
return {
|
|
365
|
-
aboutContent: {
|
|
387
|
+
aboutContent: {
|
|
388
|
+
title: aboutTitle,
|
|
389
|
+
subtitle: aboutSubtitle,
|
|
390
|
+
missionTitle: aboutMissionTitle,
|
|
391
|
+
missionText: aboutMissionText,
|
|
392
|
+
howItWorksTitle: aboutHowItWorksTitle,
|
|
393
|
+
howItems: aboutHowItems,
|
|
394
|
+
valuesTitle: aboutValuesTitle,
|
|
395
|
+
valueItems: aboutValueItems,
|
|
396
|
+
milestonesTitle: aboutMilestonesTitle,
|
|
397
|
+
milestones: aboutMilestones,
|
|
398
|
+
teamTitle: aboutTeamTitle,
|
|
399
|
+
teamSubtitle: aboutTeamSubtitle,
|
|
400
|
+
teamMembers: aboutTeamMembers,
|
|
401
|
+
ctaTitle: aboutCtaTitle,
|
|
402
|
+
ctaSell: aboutCtaSell,
|
|
403
|
+
ctaShop: aboutCtaShop,
|
|
404
|
+
},
|
|
366
405
|
siteName, tagline, logo: logoUrl, favicon: faviconUrl,
|
|
367
406
|
maintenance: { enabled: maintenanceMode, message: maintenanceMessage },
|
|
368
407
|
background: {
|
|
@@ -452,6 +491,9 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
452
491
|
onSuccess: () => {
|
|
453
492
|
showToast("Site settings saved.", "success");
|
|
454
493
|
queryClient.invalidateQueries({ queryKey: ["admin", "site-settings"] });
|
|
494
|
+
// Public-facing consumers (Navbar, checkout, watermark, etc.) read via the
|
|
495
|
+
// shared useSiteSettings() hook under a different key — must invalidate both.
|
|
496
|
+
queryClient.invalidateQueries({ queryKey: ["site-settings"] });
|
|
455
497
|
},
|
|
456
498
|
onError: (err) => showToast(err?.message ?? "Failed to save site settings.", "error"),
|
|
457
499
|
});
|
|
@@ -491,7 +533,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
491
533
|
["procurement", "⑮ Procurement"],
|
|
492
534
|
["emi", "⑯ EMI"],
|
|
493
535
|
["gst", "⑰ GST"],
|
|
494
|
-
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Override the About page hero and mission text. Leave blank to use the platform defaults." }), _jsx(Input, { label: "Hero title", value: aboutTitle, onChange: (e) => setAboutTitle(e.target.value), placeholder: "About LetItRip" }), _jsx(Input, { label: "Hero subtitle", value: aboutSubtitle, onChange: (e) => setAboutSubtitle(e.target.value), placeholder: "Connecting buyers, sellers, and bidders in one vibrant marketplace" }), _jsx(Input, { label: "Mission section title", value: aboutMissionTitle, onChange: (e) => setAboutMissionTitle(e.target.value), placeholder: "Our Mission" }), _jsxs(_Fragment, { children: [_jsx(Text, { className: "mb-1", color: "muted", size: "sm", weight: "medium", children: "Mission text" }), _jsx(Textarea, { value: aboutMissionText, onChange: (e) => setAboutMissionText(e.target.value), placeholder: "LetItRip was built to democratise commerce\u2026", rows: 4 })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Site name", value: siteName, onChange: (e) => setSiteName(e.target.value), placeholder: "LetItRip" }), _jsx(Input, { label: "Tagline", value: tagline, onChange: (e) => setTagline(e.target.value), placeholder: "India's Largest Collectibles Marketplace" }), _jsx(ImageUpload, { label: "Logo", currentImage: logoUrl, onUpload: (file) => upload(file, "store"), onChange: setLogoUrl }), _jsx(ImageUpload, { label: "Favicon", currentImage: faviconUrl, onUpload: (file) => upload(file, "store"), onChange: setFaviconUrl }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Maintenance mode", checked: maintenanceMode, onChange: setMaintenanceMode }), _jsx(Input, { label: "Maintenance message", value: maintenanceMessage, onChange: (e) => setMaintenanceMessage(e.target.value), placeholder: "We're back soon.", disabled: !maintenanceMode })] })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Sets the background behind the public shell, dashboard sidebars, and any Section/Card that opts into it. Leave the value blank for a plain surface." }), _jsxs(Grid, { cols: 2, gap: "lg", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Light mode" }), _jsx(Select, { label: "Type", options: BG_TYPE_OPTIONS, value: lightBgType, onValueChange: (v) => setLightBgType(v) }), lightBgType === "color" ? (_jsx(Input, { type: "color", label: "Color", value: lightBgValue || "#ffffff", onChange: (e) => setLightBgValue(e.target.value), className: "h-10 w-full cursor-pointer", bare: true }) /* audit-hex-tokens-ok: native color picker requires literal hex string fallback */) : lightBgType === "gradient" ? (_jsx(Textarea, { label: "CSS gradient expression", rows: 2, value: lightBgValue, onChange: (e) => setLightBgValue(e.target.value), placeholder: "linear-gradient(...)" })) : (_jsx(MediaUploadField, { label: lightBgType === "video" ? "Background video" : "Background image", kind: lightBgType === "video" ? "video" : "image", value: lightBgValue, onChange: setLightBgValue, onUpload: (file) => upload(file, "store") })), _jsx(Toggle, { label: "Dim overlay", checked: lightBgOverlayEnabled, onChange: setLightBgOverlayEnabled }), lightBgOverlayEnabled && (_jsxs(_Fragment, { children: [_jsx(Input, { type: "color", label: "Overlay color", value: lightBgOverlayColor, onChange: (e) => setLightBgOverlayColor(e.target.value), className: "h-10 w-full cursor-pointer", bare: true }), " ", _jsx(Slider, { label: "Overlay opacity", min: 0, max: 100, value: lightBgOverlayOpacity, onChange: setLightBgOverlayOpacity })] })), _jsx(Text, { size: "xs", color: "muted", children: "Preview" }), _jsx(Div, { className: "relative h-24 w-full", overflow: "hidden", rounded: "md", border: "default", children: _jsx(BackgroundRenderer, { mode: "light", lightMode: {
|
|
536
|
+
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Override the About page content. Leave blank to use the platform defaults." }), _jsx(Input, { label: "Hero title", value: aboutTitle, onChange: (e) => setAboutTitle(e.target.value), placeholder: "About LetItRip" }), _jsx(Input, { label: "Hero subtitle", value: aboutSubtitle, onChange: (e) => setAboutSubtitle(e.target.value), placeholder: "Connecting buyers, sellers, and bidders in one vibrant marketplace" }), _jsx(Input, { label: "Mission section title", value: aboutMissionTitle, onChange: (e) => setAboutMissionTitle(e.target.value), placeholder: "Our Mission" }), _jsxs(_Fragment, { children: [_jsx(Text, { className: "mb-1", color: "muted", size: "sm", weight: "medium", children: "Mission text" }), _jsx(Textarea, { value: aboutMissionText, onChange: (e) => setAboutMissionText(e.target.value), placeholder: "LetItRip was built to democratise commerce\u2026", rows: 4 })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: '"How it works" section title', value: aboutHowItWorksTitle, onChange: (e) => setAboutHowItWorksTitle(e.target.value), placeholder: "How It Works" }), aboutHowItems.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Step ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutHowItems(aboutHowItems.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Icon (emoji)", value: item.icon, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, icon: e.target.value }; setAboutHowItems(next); }, placeholder: "\uD83D\uDED2" }), _jsx(Input, { label: "Title", value: item.title, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, title: e.target.value }; setAboutHowItems(next); } }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutHowItems]; next[i] = { ...item, text: e.target.value }; setAboutHowItems(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutHowItems([...aboutHowItems, { title: "", text: "", icon: "✨", tone: "indigo" }]), children: "+ Add step" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Values section title", value: aboutValuesTitle, onChange: (e) => setAboutValuesTitle(e.target.value), placeholder: "Our Values" }), aboutValueItems.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Value ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutValueItems(aboutValueItems.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Icon (emoji)", value: item.icon, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, icon: e.target.value }; setAboutValueItems(next); }, placeholder: "\uD83D\uDEE1\uFE0F" }), _jsx(Input, { label: "Title", value: item.title, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, title: e.target.value }; setAboutValueItems(next); } }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutValueItems]; next[i] = { ...item, text: e.target.value }; setAboutValueItems(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutValueItems([...aboutValueItems, { title: "", text: "", icon: "✨" }]), children: "+ Add value" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Milestones section title", value: aboutMilestonesTitle, onChange: (e) => setAboutMilestonesTitle(e.target.value), placeholder: "Our Journey" }), aboutMilestones.map((item, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Milestone ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutMilestones(aboutMilestones.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(Input, { label: "Year", value: item.year, onChange: (e) => { const next = [...aboutMilestones]; next[i] = { ...item, year: e.target.value }; setAboutMilestones(next); }, placeholder: "2024" }), _jsx(Textarea, { value: item.text, onChange: (e) => { const next = [...aboutMilestones]; next[i] = { ...item, text: e.target.value }; setAboutMilestones(next); }, rows: 2 })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutMilestones([...aboutMilestones, { year: "", text: "" }]), children: "+ Add milestone" })] }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Input, { label: "Team section title", value: aboutTeamTitle, onChange: (e) => setAboutTeamTitle(e.target.value), placeholder: "Meet the Team" }), _jsx(Input, { label: "Team section subtitle", value: aboutTeamSubtitle, onChange: (e) => setAboutTeamSubtitle(e.target.value), placeholder: "The people building LetItRip" }), aboutTeamMembers.map((member, i) => (_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", weight: "medium", color: "muted", children: ["Team member ", i + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setAboutTeamMembers(aboutTeamMembers.filter((_, idx) => idx !== i)), children: "\u2715" })] }), _jsx(ImageUpload, { label: "Photo", currentImage: member.photoUrl, onUpload: (file) => upload(file, "store"), onChange: (url) => { const next = [...aboutTeamMembers]; next[i] = { ...member, photoUrl: url }; setAboutTeamMembers(next); } }), _jsx(Input, { label: "Name", value: member.name, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, name: e.target.value }; setAboutTeamMembers(next); } }), _jsx(Input, { label: "Role", value: member.role, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, role: e.target.value }; setAboutTeamMembers(next); }, placeholder: "Founder & Developer" }), _jsx(Textarea, { value: member.bio, onChange: (e) => { const next = [...aboutTeamMembers]; next[i] = { ...member, bio: e.target.value }; setAboutTeamMembers(next); }, rows: 3 }), _jsxs(Row, { gap: "md", children: [_jsx(Toggle, { label: "Founder", checked: !!member.isFounder, onChange: (v) => { const next = [...aboutTeamMembers]; next[i] = { ...member, isFounder: v }; setAboutTeamMembers(next); } }), _jsx(Toggle, { label: "Developer", checked: !!member.isDeveloper, onChange: (v) => { const next = [...aboutTeamMembers]; next[i] = { ...member, isDeveloper: v }; setAboutTeamMembers(next); } })] })] }, i))), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setAboutTeamMembers([...aboutTeamMembers, { name: "", role: "", bio: "" }]), children: "+ Add team member" })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(Input, { label: "Sell CTA copy", value: aboutCtaSell, onChange: (e) => setAboutCtaSell(e.target.value), placeholder: "Start selling in minutes" }), _jsx(Input, { label: "Shop CTA copy", value: aboutCtaShop, onChange: (e) => setAboutCtaShop(e.target.value), placeholder: "Browse the marketplace" })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Site name", value: siteName, onChange: (e) => setSiteName(e.target.value), placeholder: "LetItRip" }), _jsx(Input, { label: "Tagline", value: tagline, onChange: (e) => setTagline(e.target.value), placeholder: "India's Largest Collectibles Marketplace" }), _jsx(ImageUpload, { label: "Logo", currentImage: logoUrl, onUpload: (file) => upload(file, "store"), onChange: setLogoUrl }), _jsx(ImageUpload, { label: "Favicon", currentImage: faviconUrl, onUpload: (file) => upload(file, "store"), onChange: setFaviconUrl }), _jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Maintenance mode", checked: maintenanceMode, onChange: setMaintenanceMode }), _jsx(Input, { label: "Maintenance message", value: maintenanceMessage, onChange: (e) => setMaintenanceMessage(e.target.value), placeholder: "We're back soon.", disabled: !maintenanceMode })] })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Sets the background behind the public shell, dashboard sidebars, and any Section/Card that opts into it. Leave the value blank for a plain surface." }), _jsxs(Grid, { cols: 2, gap: "lg", children: [_jsxs(Stack, { gap: "sm", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Light mode" }), _jsx(Select, { label: "Type", options: BG_TYPE_OPTIONS, value: lightBgType, onValueChange: (v) => setLightBgType(v) }), lightBgType === "color" ? (_jsx(Input, { type: "color", label: "Color", value: lightBgValue || "#ffffff", onChange: (e) => setLightBgValue(e.target.value), className: "h-10 w-full cursor-pointer", bare: true }) /* audit-hex-tokens-ok: native color picker requires literal hex string fallback */) : lightBgType === "gradient" ? (_jsx(Textarea, { label: "CSS gradient expression", rows: 2, value: lightBgValue, onChange: (e) => setLightBgValue(e.target.value), placeholder: "linear-gradient(...)" })) : (_jsx(MediaUploadField, { label: lightBgType === "video" ? "Background video" : "Background image", kind: lightBgType === "video" ? "video" : "image", value: lightBgValue, onChange: setLightBgValue, onUpload: (file) => upload(file, "store") })), _jsx(Toggle, { label: "Dim overlay", checked: lightBgOverlayEnabled, onChange: setLightBgOverlayEnabled }), lightBgOverlayEnabled && (_jsxs(_Fragment, { children: [_jsx(Input, { type: "color", label: "Overlay color", value: lightBgOverlayColor, onChange: (e) => setLightBgOverlayColor(e.target.value), className: "h-10 w-full cursor-pointer", bare: true }), " ", _jsx(Slider, { label: "Overlay opacity", min: 0, max: 100, value: lightBgOverlayOpacity, onChange: setLightBgOverlayOpacity })] })), _jsx(Text, { size: "xs", color: "muted", children: "Preview" }), _jsx(Div, { className: "relative h-24 w-full", overflow: "hidden", rounded: "md", border: "default", children: _jsx(BackgroundRenderer, { mode: "light", lightMode: {
|
|
495
537
|
type: lightBgType,
|
|
496
538
|
value: lightBgValue,
|
|
497
539
|
overlay: { enabled: lightBgOverlayEnabled, color: lightBgOverlayColor, opacity: lightBgOverlayOpacity / 100 },
|
|
@@ -6,6 +6,7 @@ export interface AdminStoreEditorViewProps {
|
|
|
6
6
|
storeName?: string;
|
|
7
7
|
currentStatus?: string;
|
|
8
8
|
currentIsVerified?: boolean;
|
|
9
|
+
currentIsFeatured?: boolean;
|
|
9
10
|
currentCapabilities?: string[];
|
|
10
11
|
}
|
|
11
|
-
export declare function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentCapabilities, }: AdminStoreEditorViewProps): React.JSX.Element;
|
|
12
|
+
export declare function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentIsFeatured, currentCapabilities, }: AdminStoreEditorViewProps): React.JSX.Element;
|
|
@@ -48,12 +48,12 @@ const STATUS_OPTIONS = [
|
|
|
48
48
|
{ label: "Rejected", value: "rejected" },
|
|
49
49
|
];
|
|
50
50
|
// --- Component ---------------------------------------------------------------
|
|
51
|
-
export function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentCapabilities, }) {
|
|
51
|
+
export function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentIsFeatured, currentCapabilities, }) {
|
|
52
52
|
const queryClient = useQueryClient();
|
|
53
53
|
const { showToast } = useToast();
|
|
54
54
|
const [storeStatus, setStoreStatus] = React.useState(currentStatus ?? "pending");
|
|
55
55
|
const [adminNotes, setAdminNotes] = React.useState("");
|
|
56
|
-
const [isFeatured, setIsFeatured] = React.useState(false);
|
|
56
|
+
const [isFeatured, setIsFeatured] = React.useState(currentIsFeatured ?? false);
|
|
57
57
|
const [isVerified, setIsVerified] = React.useState(currentIsVerified ?? false);
|
|
58
58
|
const [suspensionReason, setSuspensionReason] = React.useState("");
|
|
59
59
|
const [capabilities, setCapabilities] = React.useState(new Set(currentCapabilities ?? ["suggest_brands", "create_coupons"]));
|
|
@@ -71,12 +71,12 @@ export function AdminStoreEditorView({ open, onClose, storeId, storeName, curren
|
|
|
71
71
|
if (open) {
|
|
72
72
|
setStoreStatus(currentStatus ?? "pending");
|
|
73
73
|
setAdminNotes("");
|
|
74
|
-
setIsFeatured(false);
|
|
74
|
+
setIsFeatured(currentIsFeatured ?? false);
|
|
75
75
|
setIsVerified(currentIsVerified ?? false);
|
|
76
76
|
setSuspensionReason("");
|
|
77
77
|
setCapabilities(new Set(currentCapabilities ?? ["suggest_brands", "create_coupons"]));
|
|
78
78
|
}
|
|
79
|
-
}, [open, currentStatus, currentIsVerified, currentCapabilities]);
|
|
79
|
+
}, [open, currentStatus, currentIsVerified, currentIsFeatured, currentCapabilities]);
|
|
80
80
|
const saveMutation = useApiMutation({
|
|
81
81
|
mutationFn: async () => {
|
|
82
82
|
await apiClient.patch(ADMIN_ENDPOINTS.STORE_BY_ID(storeId), {
|
|
@@ -109,7 +109,7 @@ export function AdminStoresView({ children, ...props }) {
|
|
|
109
109
|
},
|
|
110
110
|
renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_STORE_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
|
|
111
111
|
};
|
|
112
|
-
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminStoreEditorView, { open: Boolean(editorRow), onClose: () => setEditorRow(null), storeId: editorRow?.id, storeName: editorRow?.primary, currentStatus: editorRow?.status?.toLowerCase(), currentIsVerified: Boolean(editorRow?._raw?.isVerified), currentCapabilities: Array.isArray(editorRow?._raw?.capabilities)
|
|
112
|
+
return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminStoreEditorView, { open: Boolean(editorRow), onClose: () => setEditorRow(null), storeId: editorRow?.id, storeName: editorRow?.primary, currentStatus: editorRow?.status?.toLowerCase(), currentIsVerified: Boolean(editorRow?._raw?.isVerified), currentIsFeatured: Boolean(editorRow?._raw?.isFeatured), currentCapabilities: Array.isArray(editorRow?._raw?.capabilities)
|
|
113
113
|
? editorRow._raw.capabilities
|
|
114
114
|
: undefined })] }));
|
|
115
115
|
}
|