@mohasinac/appkit 3.2.5 → 3.2.7
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/client/features/lottery/LotteryAdminEditView.js +3 -3
- package/dist/_internal/server/features/products/data.d.ts +2 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +2 -1
- package/dist/_internal/shared/actions/action-registry.js +62 -0
- package/dist/_internal/shared/features/products/types.d.ts +3 -2
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +4 -0
- package/dist/constants/field-names.d.ts +2 -0
- package/dist/constants/field-names.js +2 -0
- package/dist/features/about/components/PublicProfileView.js +8 -5
- package/dist/features/account/components/UserOffersPanel.js +0 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +14 -1
- package/dist/features/addresses/repository/addresses.repository.js +122 -1
- package/dist/features/addresses/schemas/firestore.d.ts +22 -3
- package/dist/features/addresses/schemas/firestore.js +25 -0
- package/dist/features/addresses/schemas/index.d.ts +2 -2
- package/dist/features/admin/components/AdminAddressClustersView.d.ts +5 -0
- package/dist/features/admin/components/AdminAddressClustersView.js +38 -0
- package/dist/features/admin/components/AdminAddressesView.d.ts +5 -0
- package/dist/features/admin/components/AdminAddressesView.js +137 -0
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -4
- package/dist/features/admin/components/AdminFulfillmentView.js +2 -3
- package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
- package/dist/features/admin/components/AdminPaymentClustersView.d.ts +5 -0
- package/dist/features/admin/components/AdminPaymentClustersView.js +37 -0
- package/dist/features/admin/components/AdminPaymentMethodsView.d.ts +5 -0
- package/dist/features/admin/components/AdminPaymentMethodsView.js +130 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +2 -2
- package/dist/features/admin/components/index.d.ts +8 -0
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/admin/schemas/firestore.d.ts +3 -5
- package/dist/features/admin/types/product.types.d.ts +2 -1
- package/dist/features/auth/actions/profile-actions.d.ts +3 -1
- package/dist/features/auth/actions/profile-actions.js +29 -2
- package/dist/features/auth/schemas/firestore.d.ts +8 -10
- package/dist/features/cart/schemas/index.d.ts +6 -6
- package/dist/features/categories/components/CategoryBundlesListing.js +1 -3
- package/dist/features/checkout/schemas/firestore.d.ts +4 -4
- package/dist/features/copilot/components/AdminCopilotView.js +4 -4
- package/dist/features/events/schemas/firestore.d.ts +2 -2
- package/dist/features/grouped/schemas/firestore.d.ts +2 -4
- package/dist/features/history/hooks/useHistory.js +3 -2
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +6 -6
- package/dist/features/homepage/actions/homepage-section-actions.js +2 -2
- package/dist/features/homepage/components/PromoGrid.js +1 -1
- package/dist/features/homepage/schemas/firestore.d.ts +15 -14
- package/dist/features/layout/AppLayoutShell.js +3 -18
- package/dist/features/layout/background-seed-defaults.d.ts +19 -0
- package/dist/features/layout/background-seed-defaults.js +14 -0
- package/dist/features/lottery/schemas/firestore.d.ts +2 -2
- package/dist/features/messages/hooks/useConversation.js +5 -13
- package/dist/features/messages/hooks/useConversations.js +0 -1
- package/dist/features/messages/schemas/firestore.d.ts +2 -2
- package/dist/features/orders/components/RefundHistoryTable.js +1 -3
- package/dist/features/orders/schemas/firestore.d.ts +2 -1
- package/dist/features/orders/schemas/index.d.ts +11 -7
- package/dist/features/orders/schemas/index.js +1 -1
- package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +48 -0
- package/dist/features/payments/repository/saved-payment-methods.repository.js +236 -0
- package/dist/features/payments/schemas/index.d.ts +1 -0
- package/dist/features/payments/schemas/index.js +1 -0
- package/dist/features/payments/schemas/saved-methods-firestore.d.ts +63 -0
- package/dist/features/payments/schemas/saved-methods-firestore.js +60 -0
- package/dist/features/payments/server.d.ts +2 -0
- package/dist/features/payments/server.js +2 -0
- package/dist/features/products/actions/product-actions.d.ts +2 -1
- package/dist/features/products/api/[id]/route.js +1 -1
- package/dist/features/products/api/route.js +1 -1
- package/dist/features/products/components/CompareOverlay.d.ts +18 -1
- package/dist/features/products/components/CompareOverlay.js +0 -16
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/products/components/ShowGroupSection.js +1 -3
- package/dist/features/products/repository/product-features.repository.js +1 -1
- package/dist/features/products/schemas/firestore.d.ts +3 -6
- package/dist/features/promotions/actions/coupon-actions.d.ts +2 -1
- package/dist/features/promotions/hooks/useCouponValidate.d.ts +2 -1
- package/dist/features/promotions/repository/coupons.repository.d.ts +2 -1
- package/dist/features/promotions/schemas/firestore.d.ts +4 -4
- package/dist/features/promotions/schemas/index.d.ts +6 -6
- package/dist/features/reviews/repository/reviews.repository.d.ts +12 -0
- package/dist/features/reviews/repository/reviews.repository.js +24 -0
- package/dist/features/reviews/schemas/firestore.d.ts +5 -1
- package/dist/features/reviews/schemas/firestore.js +2 -0
- package/dist/features/reviews/schemas/index.d.ts +6 -6
- package/dist/features/scams/components/ScamRegistryView.js +2 -2
- package/dist/features/scams/schemas/firestore.d.ts +6 -6
- package/dist/features/search/types/index.d.ts +3 -2
- package/dist/features/search/types/index.js +0 -1
- package/dist/features/seller/components/FulfillmentView.js +2 -3
- package/dist/features/seller/components/PrintCenterView.js +1 -3
- package/dist/features/seller/components/SellerAddressesView.js +7 -4
- package/dist/features/seller/components/SellerOffersPanel.js +0 -1
- package/dist/features/seller/components/SellerReviewsView.js +0 -1
- package/dist/features/seller/schemas/firestore.d.ts +2 -4
- package/dist/features/server-errors/schemas/firestore.d.ts +2 -2
- package/dist/features/store-extensions/schemas/firestore.d.ts +2 -2
- package/dist/features/stores/components/StoreScopedSearch.js +2 -4
- package/dist/features/stores/repository/store.repository.js +3 -1
- package/dist/features/stores/types/index.d.ts +2 -1
- package/dist/features/stores/types/index.js +0 -1
- package/dist/features/support/schemas/firestore.d.ts +2 -2
- package/dist/features/support/schemas/index.d.ts +8 -8
- package/dist/features/wishlist/schemas/index.d.ts +2 -2
- package/dist/features/wishlist/types/index.d.ts +2 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/next/routing/route-map.d.ts +8 -0
- package/dist/next/routing/route-map.js +4 -0
- package/dist/providers/auth-firebase/provider.js +3 -1
- package/dist/providers/auth-firebase/session.js +3 -1
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +66 -66
- package/dist/schemas/webhooks/razorpay.d.ts +50 -50
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/pii-schemas.d.ts +2 -0
- package/dist/security/pii-schemas.js +2 -0
- package/dist/seo/json-ld.d.ts +2 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Form.d.ts +3 -1
- package/dist/ui/components/Form.js +2 -1
- package/dist/ui/components/Radio.d.ts +7 -0
- package/dist/ui/components/Radio.js +7 -0
- package/dist/ui/components/surface-tokens.d.ts +4 -0
- package/dist/ui/components/surface-tokens.js +24 -0
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +1 -1
- package/dist/ui/rich-text/RichText.js +0 -1
- package/package.json +5 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { Badge, Button, Div, Heading, Row, Span, Stack, Text, useToast } from "../../../ui";
|
|
6
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
|
+
import { apiClient } from "../../../http";
|
|
8
|
+
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
9
|
+
const BAN_BADGE = {
|
|
10
|
+
banned: "danger",
|
|
11
|
+
unban_requested: "warning",
|
|
12
|
+
suspicious: "secondary",
|
|
13
|
+
};
|
|
14
|
+
export function AdminAddressClustersView(_props) {
|
|
15
|
+
const queryClient = useQueryClient();
|
|
16
|
+
const { showToast } = useToast();
|
|
17
|
+
const { data, isLoading, error } = useQuery({
|
|
18
|
+
queryKey: ["admin", "address-clusters"],
|
|
19
|
+
queryFn: async () => {
|
|
20
|
+
const res = await apiClient.get(ADMIN_ENDPOINTS.ADDRESS_CLUSTERS);
|
|
21
|
+
return res;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const flagMutation = useApiMutation({
|
|
25
|
+
mutationFn: async (id) => {
|
|
26
|
+
await apiClient.patch(ADMIN_ENDPOINTS.ADDRESS_BY_ID(id), { action: "flag_suspicious" });
|
|
27
|
+
},
|
|
28
|
+
onSuccess: () => {
|
|
29
|
+
showToast("Address flagged as suspicious.", "success");
|
|
30
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "address-clusters"] });
|
|
31
|
+
},
|
|
32
|
+
onError: (err) => {
|
|
33
|
+
showToast(err.message ?? "Failed to flag address.", "error");
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const clusters = data?.clusters ?? [];
|
|
37
|
+
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)))] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { sortBy } from "@mohasinac/appkit";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { Badge, Div, FilterChipGroup, Span, Stack, Text, useToast } from "../../../ui";
|
|
7
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
|
+
import { toRecordArray, toRelativeDate, toStringValue } from "../hooks/useAdminListingData";
|
|
10
|
+
import { DataListingView } from "./DataListingView";
|
|
11
|
+
import { apiClient } from "../../../http";
|
|
12
|
+
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
13
|
+
import { QuickEditMenu } from "./QuickEditMenu";
|
|
14
|
+
const BAN_STATUS_TABS = [
|
|
15
|
+
{ id: "", label: "All" },
|
|
16
|
+
{ id: "banned", label: "Banned" },
|
|
17
|
+
{ id: "unban_requested", label: "Unban Requested" },
|
|
18
|
+
{ id: "suspicious", label: "Suspicious" },
|
|
19
|
+
];
|
|
20
|
+
const STATUS_BADGE = {
|
|
21
|
+
banned: "danger",
|
|
22
|
+
unban_requested: "warning",
|
|
23
|
+
suspicious: "secondary",
|
|
24
|
+
};
|
|
25
|
+
const ADDRESS_COLUMNS = [
|
|
26
|
+
{
|
|
27
|
+
key: "primary",
|
|
28
|
+
header: "Address",
|
|
29
|
+
render: (row) => (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { weight: "medium", color: "primary", children: row.primary }), row.secondary ? _jsx(Text, { size: "xs", color: "muted", children: row.secondary }) : null] })),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: "status",
|
|
33
|
+
header: "Status",
|
|
34
|
+
className: "w-36",
|
|
35
|
+
render: (row) => {
|
|
36
|
+
if (!row.status)
|
|
37
|
+
return _jsx(Span, { size: "xs", color: "muted", children: "\u2014" });
|
|
38
|
+
const variant = (STATUS_BADGE[row.status] ?? "secondary");
|
|
39
|
+
return _jsx(Badge, { variant: variant, size: "sm", children: row.status.replace(/_/g, " ") });
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "updatedAt",
|
|
44
|
+
header: "Flagged",
|
|
45
|
+
className: "w-32",
|
|
46
|
+
render: (row) => _jsx(Span, { size: "sm", color: "muted", children: row.updatedAt }),
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
function buildAddressConfig(banStatus, onAction) {
|
|
50
|
+
return {
|
|
51
|
+
portal: "admin",
|
|
52
|
+
title: "Address Management",
|
|
53
|
+
searchPlaceholder: "Search by city or owner",
|
|
54
|
+
emptyLabel: "No addresses found",
|
|
55
|
+
filterKeys: [],
|
|
56
|
+
defaultSort: sortBy("bannedAt", "DESC"),
|
|
57
|
+
queryKey: ["admin", "addresses", banStatus],
|
|
58
|
+
endpoint: banStatus
|
|
59
|
+
? `${ADMIN_ENDPOINTS.ADDRESSES}?banStatus=${encodeURIComponent(banStatus)}`
|
|
60
|
+
: ADMIN_ENDPOINTS.ADDRESSES,
|
|
61
|
+
sortOptions: [
|
|
62
|
+
{ value: "bannedAt", label: "Flagged Date" },
|
|
63
|
+
{ value: "city", label: "City" },
|
|
64
|
+
],
|
|
65
|
+
columns: ADDRESS_COLUMNS,
|
|
66
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
67
|
+
id: toStringValue(item.id, `addr-${index}`),
|
|
68
|
+
primary: [
|
|
69
|
+
toStringValue(item.addressLine1, ""),
|
|
70
|
+
toStringValue(item.city, ""),
|
|
71
|
+
toStringValue(item.state, ""),
|
|
72
|
+
toStringValue(item.postalCode, ""),
|
|
73
|
+
].filter(Boolean).join(", "),
|
|
74
|
+
secondary: `Owner: ${toStringValue(item.ownerId, "unknown")} (${toStringValue(item.ownerType, "")})${item.banReason ? ` · ${toStringValue(item.banReason, "")}` : ""}`,
|
|
75
|
+
status: toStringValue(item.banStatus, ""),
|
|
76
|
+
updatedAt: toRelativeDate(item.bannedAt ?? item.updatedAt),
|
|
77
|
+
_raw: item,
|
|
78
|
+
})),
|
|
79
|
+
getTotal: (response, rows) => typeof response.total === "number" ? response.total : rows.length,
|
|
80
|
+
buildFilters: () => undefined,
|
|
81
|
+
renderRowActions: (row) => {
|
|
82
|
+
const actions = [];
|
|
83
|
+
if (row.status !== "banned") {
|
|
84
|
+
actions.push({
|
|
85
|
+
label: ACTIONS.ADMIN["ban-address"].label,
|
|
86
|
+
destructive: true,
|
|
87
|
+
onClick: () => onAction(row.id, "ban"),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (row.status === "banned") {
|
|
91
|
+
actions.push({
|
|
92
|
+
label: ACTIONS.ADMIN["approve-unban"].label,
|
|
93
|
+
onClick: () => onAction(row.id, "approve_unban"),
|
|
94
|
+
});
|
|
95
|
+
actions.push({
|
|
96
|
+
label: ACTIONS.ADMIN["reject-unban"].label,
|
|
97
|
+
onClick: () => onAction(row.id, "reject_unban"),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (row.status !== "suspicious") {
|
|
101
|
+
actions.push({
|
|
102
|
+
label: "Flag Suspicious",
|
|
103
|
+
onClick: () => onAction(row.id, "flag_suspicious"),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (row.status) {
|
|
107
|
+
actions.push({
|
|
108
|
+
label: "Clear Flag",
|
|
109
|
+
onClick: () => onAction(row.id, "clear_ban"),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return _jsx(QuickEditMenu, { actions: actions });
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function AdminAddressesView(_props) {
|
|
117
|
+
const [banStatus, setBanStatus] = useState("");
|
|
118
|
+
const queryClient = useQueryClient();
|
|
119
|
+
const { showToast } = useToast();
|
|
120
|
+
const actionMutation = useApiMutation({
|
|
121
|
+
mutationFn: async ({ id, action }) => {
|
|
122
|
+
await apiClient.patch(ADMIN_ENDPOINTS.ADDRESS_BY_ID(id), { action });
|
|
123
|
+
},
|
|
124
|
+
onSuccess: () => {
|
|
125
|
+
showToast("Address updated.", "success");
|
|
126
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "addresses"] });
|
|
127
|
+
},
|
|
128
|
+
onError: (err) => {
|
|
129
|
+
showToast(err.message ?? "Failed to update address.", "error");
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const handleAction = (id, action) => {
|
|
133
|
+
actionMutation.mutate({ id, action });
|
|
134
|
+
};
|
|
135
|
+
const config = buildAddressConfig(banStatus, handleAction);
|
|
136
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(Div, { padding: "inline", border: "default", className: "border-b", children: _jsx(FilterChipGroup, { label: "Status", tabs: BAN_STATUS_TABS, value: banStatus, onChange: (v) => setBanStatus(v) }) }), _jsx(DataListingView, { config: config })] }));
|
|
137
|
+
}
|
|
@@ -26,7 +26,6 @@ function makeCard(zone = 1) {
|
|
|
26
26
|
id: `card-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
27
27
|
zone,
|
|
28
28
|
background: { type: "color", color: "var(--appkit-color-primary)" },
|
|
29
|
-
// audit-hex-tokens-ok: default white text on colored bg — written into Firestore card
|
|
30
29
|
content: { title: "", subtitle: "", textColor: "#ffffff", textAlign: "left" },
|
|
31
30
|
buttons: [],
|
|
32
31
|
hover: { effect: "scale" },
|
|
@@ -79,9 +78,7 @@ function ZonePicker({ selected, onChange, disabled, }) {
|
|
|
79
78
|
// ── Background Editor ────────────────────────────────────────────────────────
|
|
80
79
|
function BackgroundEditor({ value, onChange, prefix, }) {
|
|
81
80
|
const set = (patch) => onChange({ ...value, ...patch });
|
|
82
|
-
return (_jsxs(Stack, { gap: "3", children: [_jsx(Select, { label: "Background type", value: value.type, onChange: (e) => onChange({ type: e.target.value }), options: BG_TYPE_OPTIONS }), (value.type === "image" || value.type === "video") && (_jsxs(_Fragment, { children: [_jsx(Input, { label: `${value.type === "image" ? "Image" : "Video"} URL`, value: value.url ?? "", onChange: (e) => set({ url: e.target.value }), placeholder: "https://..." }), _jsx(Input, { label: "Mobile URL (optional)", value: value.mobileUrl ?? "", onChange: (e) => set({ mobileUrl: e.target.value || undefined }), placeholder: "https://... (portrait crop)" }), value.type === "video" && (_jsx(Input, { label: "Poster / thumbnail URL", value: value.thumbnail ?? "", onChange: (e) => set({ thumbnail: e.target.value || undefined }), placeholder: "https://..." })), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "lg", border: "default", children: [_jsx(Toggle, { label: "Dim overlay", checked: value.dimOverlay?.enabled ?? false, onChange: (v) => set({ dimOverlay: { enabled: v, opacity: value.dimOverlay?.opacity ?? 40 } }) }), value.dimOverlay?.enabled && (_jsxs(Div, { children: [_jsxs(Text, { className: "mb-1", size: "sm", weight: "medium", children: ["Opacity (", value.dimOverlay.opacity, "%)"] }), _jsx("input", { type: "range", min: 0, max: 90, step: 5, value: value.dimOverlay.opacity, onChange: (e) => set({ dimOverlay: { enabled: true, opacity: Number(e.target.value) } }), className: "w-full" })] }))] })] })), value.type === "color" && (_jsx(Input, { label: "Colour (CSS token or hex)", value: value.color ?? "", onChange: (e) => set({ color: e.target.value }),
|
|
83
|
-
// audit-hex-tokens-ok: placeholder text shown to admin as example
|
|
84
|
-
placeholder: "var(--appkit-color-primary) or #1a1a2e" })), value.type === "gradient" && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "From colour", value: value.gradientFrom ?? "", onChange: (e) => set({ gradientFrom: e.target.value }), placeholder: "var(--appkit-color-primary)" }), _jsx(Input, { label: "To colour", value: value.gradientTo ?? "", onChange: (e) => set({ gradientTo: e.target.value }), placeholder: "var(--appkit-color-secondary)" }), _jsx(Input, { label: "Angle (degrees)", type: "number", value: String(value.gradientAngle ?? 135), onChange: (e) => set({ gradientAngle: Number(e.target.value) }), min: 0, max: 360 })] }))] }));
|
|
81
|
+
return (_jsxs(Stack, { gap: "3", children: [_jsx(Select, { label: "Background type", value: value.type, onChange: (e) => onChange({ type: e.target.value }), options: BG_TYPE_OPTIONS }), (value.type === "image" || value.type === "video") && (_jsxs(_Fragment, { children: [_jsx(Input, { label: `${value.type === "image" ? "Image" : "Video"} URL`, value: value.url ?? "", onChange: (e) => set({ url: e.target.value }), placeholder: "https://..." }), _jsx(Input, { label: "Mobile URL (optional)", value: value.mobileUrl ?? "", onChange: (e) => set({ mobileUrl: e.target.value || undefined }), placeholder: "https://... (portrait crop)" }), value.type === "video" && (_jsx(Input, { label: "Poster / thumbnail URL", value: value.thumbnail ?? "", onChange: (e) => set({ thumbnail: e.target.value || undefined }), placeholder: "https://..." })), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", rounded: "lg", border: "default", children: [_jsx(Toggle, { label: "Dim overlay", checked: value.dimOverlay?.enabled ?? false, onChange: (v) => set({ dimOverlay: { enabled: v, opacity: value.dimOverlay?.opacity ?? 40 } }) }), value.dimOverlay?.enabled && (_jsxs(Div, { children: [_jsxs(Text, { className: "mb-1", size: "sm", weight: "medium", children: ["Opacity (", value.dimOverlay.opacity, "%)"] }), _jsx("input", { type: "range", min: 0, max: 90, step: 5, value: value.dimOverlay.opacity, onChange: (e) => set({ dimOverlay: { enabled: true, opacity: Number(e.target.value) } }), className: "w-full" })] }))] })] })), value.type === "color" && (_jsx(Input, { label: "Colour (CSS token or hex)", value: value.color ?? "", onChange: (e) => set({ color: e.target.value }), placeholder: "var(--appkit-color-primary) or #1a1a2e" })), value.type === "gradient" && (_jsxs(_Fragment, { children: [_jsx(Input, { label: "From colour", value: value.gradientFrom ?? "", onChange: (e) => set({ gradientFrom: e.target.value }), placeholder: "var(--appkit-color-primary)" }), _jsx(Input, { label: "To colour", value: value.gradientTo ?? "", onChange: (e) => set({ gradientTo: e.target.value }), placeholder: "var(--appkit-color-secondary)" }), _jsx(Input, { label: "Angle (degrees)", type: "number", value: String(value.gradientAngle ?? 135), onChange: (e) => set({ gradientAngle: Number(e.target.value) }), min: 0, max: 360 })] }))] }));
|
|
85
82
|
}
|
|
86
83
|
// ── Card Editor ──────────────────────────────────────────────────────────────
|
|
87
84
|
function CardEditor({ card, index, otherZones, onChange, onRemove, }) {
|
|
@@ -34,14 +34,13 @@ export function AdminFulfillmentView({ initialStoreId, initialStoreName, }) {
|
|
|
34
34
|
const [actionLoadingId, setActionLoadingId] = useState(null);
|
|
35
35
|
const { showToast } = useToast();
|
|
36
36
|
const fetchOrders = useCallback(async (sid) => {
|
|
37
|
-
// toast-intentionally-silent: background data loader — stale list is kept on error
|
|
38
37
|
setIsLoading(true);
|
|
39
38
|
try {
|
|
40
39
|
const res = await apiClient.get(ADMIN_ENDPOINTS.ADMIN_FULFILLMENT(sid));
|
|
41
40
|
setOrders(res.orders ?? []);
|
|
42
41
|
}
|
|
43
|
-
catch {
|
|
44
|
-
|
|
42
|
+
catch (err) {
|
|
43
|
+
void normalizeError(err);
|
|
45
44
|
}
|
|
46
45
|
finally {
|
|
47
46
|
setIsLoading(false);
|
|
@@ -95,7 +95,5 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
|
|
|
95
95
|
return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: orderLabel ? `Order: ${orderLabel}` : "Update Order", children: _jsxs(Form, { onSubmit: (e) => {
|
|
96
96
|
e.preventDefault();
|
|
97
97
|
saveMutation.mutate();
|
|
98
|
-
}, spacing: "md", padding: "md", children: [_jsx(Select, { label: "Order status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Input, { label: "Tracking number (optional)", value: trackingNumber, onChange: (e) => setTrackingNumber(e.target.value), placeholder: "e.g. DEL1234567890IN" }), _jsx(Select, { label: "Carrier (optional)", options: CARRIER_OPTIONS, value: carrier, onValueChange: setCarrier }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Internal note (optional)" }), _jsx("textarea", { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Reason for status change, escalation notes\u2026", className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), (status === "refunded" || status === "return_requested") && (_jsx(Input, { label: "Refund amount \u20B9 (optional)", type: "number", min: "0", step: "0.01", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "e.g. 499.00" })), isCashOrUpi && (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Payment Proof" }), paymentProofUrl ? (_jsxs(Stack, { gap: "xs", children: [_jsx(MediaImage, { src: paymentProofUrl, alt: "Payment screenshot", size: "card"
|
|
99
|
-
// audit-variant-ok: themed success border color not in BORDER_MAP
|
|
100
|
-
_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-success/20", surface: "success-surface", children: _jsx(Text, { size: "xs", className: "text-success", weight: "medium", children: "Payment verified" }) }))] })) : (_jsx(Text, { size: "xs", color: "faint", children: "No proof uploaded yet." }))] })), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !orderId || saveMutation.isPending, children: "Save changes" })] })] }) }));
|
|
98
|
+
}, spacing: "md", padding: "md", children: [_jsx(Select, { label: "Order status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Input, { label: "Tracking number (optional)", value: trackingNumber, onChange: (e) => setTrackingNumber(e.target.value), placeholder: "e.g. DEL1234567890IN" }), _jsx(Select, { label: "Carrier (optional)", options: CARRIER_OPTIONS, value: carrier, onValueChange: setCarrier }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Internal note (optional)" }), _jsx("textarea", { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Reason for status change, escalation notes\u2026", className: "w-full rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-primary-500" })] }), (status === "refunded" || status === "return_requested") && (_jsx(Input, { label: "Refund amount \u20B9 (optional)", type: "number", min: "0", step: "0.01", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "e.g. 499.00" })), isCashOrUpi && (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Payment Proof" }), paymentProofUrl ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { border: "default", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: paymentProofUrl, alt: "Payment screenshot", size: "card" }) }), paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", children: ["UTR: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: paymentTransactionId })] })), needsVerification && (_jsx(Button, { type: "button", action: ACTIONS.ADMIN["verify-payment"], onClick: handleVerifyPayment, isLoading: isVerifyingPayment, disabled: isVerifyingPayment, variant: "primary", className: "mt-1 w-full" })), !needsVerification && paymentStatus === "paid" && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-success/20", surface: "success-surface", children: _jsx(Text, { size: "xs", className: "text-success", weight: "medium", children: "Payment verified" }) }))] })) : (_jsx(Text, { size: "xs", color: "faint", children: "No proof uploaded yet." }))] })), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !orderId || saveMutation.isPending, children: "Save changes" })] })] }) }));
|
|
101
99
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
5
|
+
import { Badge, Button, Div, Heading, Row, Span, Stack, Text, useToast } from "../../../ui";
|
|
6
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
7
|
+
import { apiClient } from "../../../http";
|
|
8
|
+
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
9
|
+
const BAN_BADGE = {
|
|
10
|
+
banned: "danger",
|
|
11
|
+
suspicious: "secondary",
|
|
12
|
+
};
|
|
13
|
+
export function AdminPaymentClustersView(_props) {
|
|
14
|
+
const queryClient = useQueryClient();
|
|
15
|
+
const { showToast } = useToast();
|
|
16
|
+
const { data, isLoading, error } = useQuery({
|
|
17
|
+
queryKey: ["admin", "payment-clusters"],
|
|
18
|
+
queryFn: async () => {
|
|
19
|
+
const res = await apiClient.get(ADMIN_ENDPOINTS.PAYMENT_METHOD_CLUSTERS);
|
|
20
|
+
return res;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const flagMutation = useApiMutation({
|
|
24
|
+
mutationFn: async (id) => {
|
|
25
|
+
await apiClient.patch(ADMIN_ENDPOINTS.PAYMENT_METHOD_BY_ID(id), { action: "flag_suspicious" });
|
|
26
|
+
},
|
|
27
|
+
onSuccess: () => {
|
|
28
|
+
showToast("Payment method flagged as suspicious.", "success");
|
|
29
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "payment-clusters"] });
|
|
30
|
+
},
|
|
31
|
+
onError: (err) => {
|
|
32
|
+
showToast(err.message ?? "Failed to flag payment method.", "error");
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const clusters = data?.clusters ?? [];
|
|
36
|
+
return (_jsxs(Stack, { gap: "lg", padding: "page", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", color: "primary", children: "Payment Method Clusters" }), _jsx(Text, { size: "sm", color: "muted", className: "mt-1", children: "Multiple accounts sharing the same payment identifier. Flagging is informational only \u2014 users are not blocked." })] }), isLoading && (_jsx(Row, { justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), error && (_jsx(Div, { surface: "danger-surface", color: "error", padding: "inline", rounded: "xl", textSize: "sm", children: "Failed to load payment clusters." })), !isLoading && clusters.length === 0 && (_jsx(Div, { surface: "muted", padding: "y-4xl", rounded: "xl", className: "text-center", children: _jsx(Text, { color: "muted", size: "sm", children: "No shared payment identifiers found." }) })), clusters.map((cluster) => (_jsxs(Stack, { surface: "card", padding: "md", gap: "md", rounded: "xl", border: "default", children: [_jsxs(Row, { justify: "between", align: "start", gap: "sm", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", color: "muted", weight: "medium", children: "Identifier Hash" }), _jsx(Span, { size: "xs", color: "muted", className: "font-mono truncate max-w-xs", children: cluster.identifierHash })] }), _jsxs(Badge, { variant: "secondary", size: "sm", children: [cluster.methods.length, " accounts"] })] }), _jsx(Stack, { gap: "xs", children: cluster.methods.map((m) => (_jsxs(Row, { justify: "between", align: "center", gap: "sm", surface: "muted", padding: "inlineSm", rounded: "lg", children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", color: "primary", className: "truncate", children: [m.type.toUpperCase(), " \u00B7 ", m.displayLabel] }), _jsxs(Text, { size: "xs", color: "muted", children: ["User: ", m.userId] })] }), _jsxs(Row, { gap: "xs", align: "center", children: [m.banStatus && (_jsx(Badge, { variant: BAN_BADGE[m.banStatus] ?? "secondary", size: "sm", children: m.banStatus.replace(/_/g, " ") })), m.banStatus !== "suspicious" && (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => flagMutation.mutate(m.id), disabled: flagMutation.isPending, children: "Flag Suspicious" }))] })] }, m.id))) })] }, cluster.identifierHash)))] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { sortBy } from "@mohasinac/appkit";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { Badge, Div, FilterChipGroup, Span, Stack, Text, useToast } from "../../../ui";
|
|
7
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
8
|
+
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
9
|
+
import { toRecordArray, toRelativeDate, toStringValue } from "../hooks/useAdminListingData";
|
|
10
|
+
import { DataListingView } from "./DataListingView";
|
|
11
|
+
import { apiClient } from "../../../http";
|
|
12
|
+
import { useApiMutation } from "@mohasinac/appkit/client";
|
|
13
|
+
import { QuickEditMenu } from "./QuickEditMenu";
|
|
14
|
+
const BAN_STATUS_TABS = [
|
|
15
|
+
{ id: "", label: "All" },
|
|
16
|
+
{ id: "banned", label: "Banned" },
|
|
17
|
+
{ id: "suspicious", label: "Suspicious" },
|
|
18
|
+
];
|
|
19
|
+
const STATUS_BADGE = {
|
|
20
|
+
banned: "danger",
|
|
21
|
+
suspicious: "secondary",
|
|
22
|
+
};
|
|
23
|
+
const PM_COLUMNS = [
|
|
24
|
+
{
|
|
25
|
+
key: "primary",
|
|
26
|
+
header: "Payment Method",
|
|
27
|
+
render: (row) => (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { weight: "medium", color: "primary", children: row.primary }), row.secondary ? _jsx(Text, { size: "xs", color: "muted", children: row.secondary }) : null] })),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: "status",
|
|
31
|
+
header: "Status",
|
|
32
|
+
className: "w-36",
|
|
33
|
+
render: (row) => {
|
|
34
|
+
if (!row.status)
|
|
35
|
+
return _jsx(Span, { size: "xs", color: "muted", children: "\u2014" });
|
|
36
|
+
const variant = STATUS_BADGE[row.status] ?? "secondary";
|
|
37
|
+
return _jsx(Badge, { variant: variant, size: "sm", children: row.status.replace(/_/g, " ") });
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "updatedAt",
|
|
42
|
+
header: "Flagged",
|
|
43
|
+
className: "w-32",
|
|
44
|
+
render: (row) => _jsx(Span, { size: "sm", color: "muted", children: row.updatedAt }),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
function buildConfig(banStatus, onAction) {
|
|
48
|
+
return {
|
|
49
|
+
portal: "admin",
|
|
50
|
+
title: "Payment Methods",
|
|
51
|
+
searchPlaceholder: "Search by type or owner",
|
|
52
|
+
emptyLabel: "No payment methods found",
|
|
53
|
+
filterKeys: [],
|
|
54
|
+
defaultSort: sortBy("bannedAt", "DESC"),
|
|
55
|
+
queryKey: ["admin", "payment-methods", banStatus],
|
|
56
|
+
endpoint: banStatus
|
|
57
|
+
? `${ADMIN_ENDPOINTS.PAYMENT_METHODS}?banStatus=${encodeURIComponent(banStatus)}`
|
|
58
|
+
: ADMIN_ENDPOINTS.PAYMENT_METHODS,
|
|
59
|
+
sortOptions: [
|
|
60
|
+
{ value: "bannedAt", label: "Flagged Date" },
|
|
61
|
+
{ value: "type", label: "Type" },
|
|
62
|
+
],
|
|
63
|
+
columns: PM_COLUMNS,
|
|
64
|
+
mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
|
|
65
|
+
id: toStringValue(item.id, `pm-${index}`),
|
|
66
|
+
primary: `${toStringValue(item.type, "unknown").toUpperCase()} · ${toStringValue(item.displayLabel, "—")}`,
|
|
67
|
+
secondary: `User: ${toStringValue(item.userId, "unknown")}${item.banReason ? ` · ${toStringValue(item.banReason, "")}` : ""}`,
|
|
68
|
+
status: toStringValue(item.banStatus, ""),
|
|
69
|
+
updatedAt: toRelativeDate(item.bannedAt ?? item.updatedAt),
|
|
70
|
+
_raw: item,
|
|
71
|
+
})),
|
|
72
|
+
getTotal: (response, rows) => typeof response.total === "number" ? response.total : rows.length,
|
|
73
|
+
buildFilters: () => undefined,
|
|
74
|
+
renderRowActions: (row) => {
|
|
75
|
+
const actions = [];
|
|
76
|
+
if (row.status !== "banned") {
|
|
77
|
+
actions.push({
|
|
78
|
+
label: ACTIONS.ADMIN["ban-payment-method"].label,
|
|
79
|
+
destructive: true,
|
|
80
|
+
onClick: () => onAction(row.id, "ban"),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (row.status === "banned") {
|
|
84
|
+
actions.push({
|
|
85
|
+
label: ACTIONS.ADMIN["approve-payment-unban"].label,
|
|
86
|
+
onClick: () => onAction(row.id, "approve_unban"),
|
|
87
|
+
});
|
|
88
|
+
actions.push({
|
|
89
|
+
label: ACTIONS.ADMIN["reject-payment-unban"].label,
|
|
90
|
+
onClick: () => onAction(row.id, "reject_unban"),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (row.status !== "suspicious") {
|
|
94
|
+
actions.push({
|
|
95
|
+
label: "Flag Suspicious",
|
|
96
|
+
onClick: () => onAction(row.id, "flag_suspicious"),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (row.status) {
|
|
100
|
+
actions.push({
|
|
101
|
+
label: "Clear Flag",
|
|
102
|
+
onClick: () => onAction(row.id, "clear_ban"),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return _jsx(QuickEditMenu, { actions: actions });
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function AdminPaymentMethodsView(_props) {
|
|
110
|
+
const [banStatus, setBanStatus] = useState("");
|
|
111
|
+
const queryClient = useQueryClient();
|
|
112
|
+
const { showToast } = useToast();
|
|
113
|
+
const actionMutation = useApiMutation({
|
|
114
|
+
mutationFn: async ({ id, action }) => {
|
|
115
|
+
await apiClient.patch(ADMIN_ENDPOINTS.PAYMENT_METHOD_BY_ID(id), { action });
|
|
116
|
+
},
|
|
117
|
+
onSuccess: () => {
|
|
118
|
+
showToast("Payment method updated.", "success");
|
|
119
|
+
void queryClient.invalidateQueries({ queryKey: ["admin", "payment-methods"] });
|
|
120
|
+
},
|
|
121
|
+
onError: (err) => {
|
|
122
|
+
showToast(err.message ?? "Failed to update payment method.", "error");
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
const handleAction = (id, action) => {
|
|
126
|
+
actionMutation.mutate({ id, action });
|
|
127
|
+
};
|
|
128
|
+
const config = buildConfig(banStatus, handleAction);
|
|
129
|
+
return (_jsxs(Stack, { gap: "md", children: [_jsx(Div, { padding: "inline", border: "default", className: "border-b", children: _jsx(FilterChipGroup, { label: "Status", tabs: BAN_STATUS_TABS, value: banStatus, onChange: (v) => setBanStatus(v) }) }), _jsx(DataListingView, { config: config })] }));
|
|
130
|
+
}
|
|
@@ -405,10 +405,10 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
|
|
|
405
405
|
["legal", "⑫ Legal"],
|
|
406
406
|
["whatsapp", "⑬ WhatsApp"],
|
|
407
407
|
["notifications", "⑭ Notifications"],
|
|
408
|
-
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); aboutMutation.mutate(); }, className: "pt-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, className: CLS_TEXTAREA })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(GroupSaveButton, { isPending: aboutMutation.isPending })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); brandingMutation.mutate(); }, className: "pt-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(GroupSaveButton, { isPending: brandingMutation.isPending })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); appearanceMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { gap: "md", className: "grid-cols-3", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Primary color" }), _jsx(
|
|
408
|
+
].map(([value, label]) => (_jsx(TabsTrigger, { value: value, children: label }, value))) }), _jsx(TabsContent, { value: "about", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); aboutMutation.mutate(); }, className: "pt-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, className: CLS_TEXTAREA })] }), _jsx(Input, { label: "CTA banner title", value: aboutCtaTitle, onChange: (e) => setAboutCtaTitle(e.target.value), placeholder: "Ready to get started?" }), _jsx(GroupSaveButton, { isPending: aboutMutation.isPending })] }) }), _jsx(TabsContent, { value: "branding", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); brandingMutation.mutate(); }, className: "pt-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(GroupSaveButton, { isPending: brandingMutation.isPending })] }) }), _jsx(TabsContent, { value: "appearance", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); appearanceMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { gap: "md", className: "grid-cols-3", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Primary color" }), _jsx(Input, { type: "color", value: primaryColor || "#000000", onChange: (e) => setPrimaryColor(e.target.value), className: "h-10 w-full cursor-pointer", bare: true })] }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Secondary color" }), _jsx(Input, { type: "color", value: secondaryColor || "#000000", onChange: (e) => setSecondaryColor(e.target.value), className: "h-10 w-full cursor-pointer", bare: true })] }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Accent color" }), _jsx(Input, { type: "color", value: accentColor || "#000000", onChange: (e) => setAccentColor(e.target.value), className: "h-10 w-full cursor-pointer", bare: true })] })] }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Select, { label: "Default theme", options: THEME_OPTIONS, value: defaultTheme, onValueChange: setDefaultTheme }), _jsx(Select, { label: "Font family", options: FONT_OPTIONS, value: fontFamily, onValueChange: setFontFamily })] }), _jsx(GroupSaveButton, { isPending: appearanceMutation.isPending })] }) }), _jsx(TabsContent, { value: "themes", children: _jsxs(Form, { onSubmit: (e) => {
|
|
409
409
|
e.preventDefault();
|
|
410
410
|
themeMutation.mutate();
|
|
411
|
-
}, className: "pt-4", spacing: "md", children: [_jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }), _jsx(GroupSaveButton, { isPending: themeMutation.isPending })] }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); announcementMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: "/products", disabled: !announcementEnabled }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Background color" }), _jsx("input", { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 rounded border border-zinc-200 dark:border-zinc-700 cursor-pointer", disabled: !announcementEnabled })] }), _jsx(GroupSaveButton, { isPending: announcementMutation.isPending })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); seoMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex }), _jsx(GroupSaveButton, { isPending: seoMutation.isPending })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); contactMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx(Text, { className: "pt-[0.5rem]", color: "muted", size: "sm", weight: "medium", children: "Social links" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] }), _jsx(GroupSaveButton, { isPending: contactMutation.isPending })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); watermarkMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx("span", { ref: watermarkPreviewRef, className: "select-none font-medium p-1 text-[var(--appkit-color-text-faint)]", children: watermarkText }) })] }), _jsx(GroupSaveButton, { isPending: watermarkMutation.isPending })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); feesMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: feesMutation.isPending })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); integrationsMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Shiprocket" }), _jsx(MaskedInput, { label: "Shiprocket API token", value: shiprocketToken, onChange: setShiprocketToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsx(GroupSaveButton, { isPending: integrationsMutation.isPending })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); shippingMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(GroupSaveButton, { isPending: shippingMutation.isPending })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); auctionMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." }), _jsx(GroupSaveButton, { isPending: auctionMutation.isPending })] }) }), _jsx(TabsContent, { value: "limits", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); limitsMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: limitsMutation.isPending })] }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); whatsappMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(GroupSaveButton, { isPending: whatsappMutation.isPending })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); notifChannelsMutation.mutate(); }, className: "pt-4", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] }), _jsx(GroupSaveButton, { isPending: notifChannelsMutation.isPending })] }) }), _jsx(TabsContent, { value: "legal", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); legalMutation.mutate(); }, className: "pt-4", spacing: "md", children: [[
|
|
411
|
+
}, className: "pt-4", spacing: "md", children: [_jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }), _jsx(GroupSaveButton, { isPending: themeMutation.isPending })] }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); announcementMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: "/products", disabled: !announcementEnabled }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Background color" }), _jsx(Input, { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 cursor-pointer", bare: true, disabled: !announcementEnabled })] }), _jsx(GroupSaveButton, { isPending: announcementMutation.isPending })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); seoMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex }), _jsx(GroupSaveButton, { isPending: seoMutation.isPending })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); contactMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx(Text, { className: "pt-[0.5rem]", color: "muted", size: "sm", weight: "medium", children: "Social links" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] }), _jsx(GroupSaveButton, { isPending: contactMutation.isPending })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); watermarkMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx("span", { ref: watermarkPreviewRef, className: "select-none font-medium p-1 text-[var(--appkit-color-text-faint)]", children: watermarkText }) })] }), _jsx(GroupSaveButton, { isPending: watermarkMutation.isPending })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); feesMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: feesMutation.isPending })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); integrationsMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Shiprocket" }), _jsx(MaskedInput, { label: "Shiprocket API token", value: shiprocketToken, onChange: setShiprocketToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsx(GroupSaveButton, { isPending: integrationsMutation.isPending })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); shippingMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(GroupSaveButton, { isPending: shippingMutation.isPending })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); auctionMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." }), _jsx(GroupSaveButton, { isPending: auctionMutation.isPending })] }) }), _jsx(TabsContent, { value: "limits", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); limitsMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }), _jsx(GroupSaveButton, { isPending: limitsMutation.isPending })] }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); whatsappMutation.mutate(); }, className: "pt-4", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(GroupSaveButton, { isPending: whatsappMutation.isPending })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); notifChannelsMutation.mutate(); }, className: "pt-4", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] }), _jsx(GroupSaveButton, { isPending: notifChannelsMutation.isPending })] }) }), _jsx(TabsContent, { value: "legal", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); legalMutation.mutate(); }, className: "pt-4", spacing: "md", children: [[
|
|
412
412
|
["Terms of Service", termsHtml, setTermsHtml],
|
|
413
413
|
["Privacy Policy", privacyHtml, setPrivacyHtml],
|
|
414
414
|
["Refund Policy", refundHtml, setRefundHtml],
|
|
@@ -155,3 +155,11 @@ export { AdminScammerEditorView } from "./AdminScammerEditorView";
|
|
|
155
155
|
export type { AdminScammerEditorViewProps } from "./AdminScammerEditorView";
|
|
156
156
|
export { AdminFulfillmentView } from "./AdminFulfillmentView";
|
|
157
157
|
export type { AdminFulfillmentViewProps } from "./AdminFulfillmentView";
|
|
158
|
+
export { AdminAddressesView } from "./AdminAddressesView";
|
|
159
|
+
export type { AdminAddressesViewProps } from "./AdminAddressesView";
|
|
160
|
+
export { AdminAddressClustersView } from "./AdminAddressClustersView";
|
|
161
|
+
export type { AdminAddressClustersViewProps } from "./AdminAddressClustersView";
|
|
162
|
+
export { AdminPaymentMethodsView } from "./AdminPaymentMethodsView";
|
|
163
|
+
export type { AdminPaymentMethodsViewProps } from "./AdminPaymentMethodsView";
|
|
164
|
+
export { AdminPaymentClustersView } from "./AdminPaymentClustersView";
|
|
165
|
+
export type { AdminPaymentClustersViewProps } from "./AdminPaymentClustersView";
|
|
@@ -81,3 +81,7 @@ export { AdminSupportTicketDetailView } from "./AdminSupportTicketDetailView";
|
|
|
81
81
|
export { AdminScammersView } from "./AdminScammersView";
|
|
82
82
|
export { AdminScammerEditorView } from "./AdminScammerEditorView";
|
|
83
83
|
export { AdminFulfillmentView } from "./AdminFulfillmentView";
|
|
84
|
+
export { AdminAddressesView } from "./AdminAddressesView";
|
|
85
|
+
export { AdminAddressClustersView } from "./AdminAddressClustersView";
|
|
86
|
+
export { AdminPaymentMethodsView } from "./AdminPaymentMethodsView";
|
|
87
|
+
export { AdminPaymentClustersView } from "./AdminPaymentClustersView";
|