@mohasinac/appkit 3.2.6 → 3.2.8
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/shared/actions/action-registry.js +62 -0
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +4 -0
- package/dist/features/about/components/PublicProfileView.js +8 -5
- 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/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/index.d.ts +8 -0
- package/dist/features/admin/components/index.js +4 -0
- package/dist/features/auth/actions/profile-actions.d.ts +3 -1
- package/dist/features/auth/actions/profile-actions.js +29 -2
- package/dist/features/cart/schemas/index.d.ts +6 -6
- package/dist/features/orders/schemas/index.d.ts +4 -4
- 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/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/seller/components/SellerAddressesView.js +7 -3
- package/dist/features/support/schemas/index.d.ts +8 -8
- package/dist/features/wishlist/schemas/index.d.ts +2 -2
- 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/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/package.json +1 -2
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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";
|
|
@@ -27,5 +27,7 @@ export declare function getPublicUserProfile(userId: string): Promise<Pick<UserD
|
|
|
27
27
|
/** Fetch approved reviews for a store. storeId === storeSlug in this project. */
|
|
28
28
|
export declare function getSellerReviews(storeId: string): Promise<Review[]>;
|
|
29
29
|
export declare function getProfileStoreProducts(storeId: string): Promise<import("../../products").ProductDocument[]>;
|
|
30
|
-
/** Approved reviews
|
|
30
|
+
/** Approved product reviews written by this user as a buyer. Excludes seller→buyer ratings. */
|
|
31
31
|
export declare function getReviewsAuthoredBy(userId: string): Promise<Review[]>;
|
|
32
|
+
/** Approved seller→buyer reviews received by this buyer. */
|
|
33
|
+
export declare function getReviewsReceivedBy(userId: string): Promise<Review[]>;
|
|
@@ -70,9 +70,36 @@ export async function getProfileStoreProducts(storeId) {
|
|
|
70
70
|
const products = await productRepository.findByStore(storeId);
|
|
71
71
|
return products.filter((p) => p.status === ProductStatusValues.PUBLISHED);
|
|
72
72
|
}
|
|
73
|
-
/** Approved reviews
|
|
73
|
+
/** Approved product reviews written by this user as a buyer. Excludes seller→buyer ratings. */
|
|
74
74
|
export async function getReviewsAuthoredBy(userId) {
|
|
75
|
-
const
|
|
75
|
+
const all = await reviewRepository.findApprovedByUser(userId).catch(() => []);
|
|
76
|
+
// reviewerRole:"seller" means this user rated a buyer — not a product review
|
|
77
|
+
const snapshot = all.filter((r) => r.reviewerRole !== "seller");
|
|
78
|
+
return snapshot.map((r) => ({
|
|
79
|
+
id: r.id,
|
|
80
|
+
productId: r.productId,
|
|
81
|
+
productTitle: r.productTitle,
|
|
82
|
+
userId: r.userId,
|
|
83
|
+
userName: maskPublicReview(r).userName,
|
|
84
|
+
userAvatar: r.userAvatar,
|
|
85
|
+
rating: r.rating,
|
|
86
|
+
title: r.title,
|
|
87
|
+
comment: r.comment,
|
|
88
|
+
images: r.images?.map((url) => ({ url })),
|
|
89
|
+
status: r.status,
|
|
90
|
+
helpfulCount: r.helpfulCount,
|
|
91
|
+
reportCount: r.reportCount,
|
|
92
|
+
verified: r.verified,
|
|
93
|
+
featured: r.featured,
|
|
94
|
+
createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : undefined,
|
|
95
|
+
updatedAt: r.updatedAt instanceof Date ? r.updatedAt.toISOString() : undefined,
|
|
96
|
+
storeSlug: r.storeId,
|
|
97
|
+
storeName: r.storeName,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
/** Approved seller→buyer reviews received by this buyer. */
|
|
101
|
+
export async function getReviewsReceivedBy(userId) {
|
|
102
|
+
const snapshot = await reviewRepository.findByReviewee(userId).catch(() => []);
|
|
76
103
|
return snapshot.map((r) => ({
|
|
77
104
|
id: r.id,
|
|
78
105
|
productId: r.productId,
|
|
@@ -66,8 +66,8 @@ export declare const cartItemSchema: z.ZodObject<{
|
|
|
66
66
|
slug?: string | undefined;
|
|
67
67
|
attributes?: Record<string, string> | undefined;
|
|
68
68
|
} | undefined;
|
|
69
|
-
sessionId?: string | undefined;
|
|
70
69
|
userId?: string | undefined;
|
|
70
|
+
sessionId?: string | undefined;
|
|
71
71
|
addedAt?: string | undefined;
|
|
72
72
|
}, {
|
|
73
73
|
id: string;
|
|
@@ -79,9 +79,9 @@ export declare const cartItemSchema: z.ZodObject<{
|
|
|
79
79
|
slug?: string | undefined;
|
|
80
80
|
attributes?: Record<string, string> | undefined;
|
|
81
81
|
} | undefined;
|
|
82
|
+
userId?: string | undefined;
|
|
82
83
|
sessionId?: string | undefined;
|
|
83
84
|
quantity?: number | undefined;
|
|
84
|
-
userId?: string | undefined;
|
|
85
85
|
addedAt?: string | undefined;
|
|
86
86
|
}>;
|
|
87
87
|
export declare const cartSummarySchema: z.ZodObject<{
|
|
@@ -122,8 +122,8 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
122
122
|
slug?: string | undefined;
|
|
123
123
|
attributes?: Record<string, string> | undefined;
|
|
124
124
|
} | undefined;
|
|
125
|
-
sessionId?: string | undefined;
|
|
126
125
|
userId?: string | undefined;
|
|
126
|
+
sessionId?: string | undefined;
|
|
127
127
|
addedAt?: string | undefined;
|
|
128
128
|
}, {
|
|
129
129
|
id: string;
|
|
@@ -135,9 +135,9 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
135
135
|
slug?: string | undefined;
|
|
136
136
|
attributes?: Record<string, string> | undefined;
|
|
137
137
|
} | undefined;
|
|
138
|
+
userId?: string | undefined;
|
|
138
139
|
sessionId?: string | undefined;
|
|
139
140
|
quantity?: number | undefined;
|
|
140
|
-
userId?: string | undefined;
|
|
141
141
|
addedAt?: string | undefined;
|
|
142
142
|
}>, "many">;
|
|
143
143
|
subtotal: z.ZodNumber;
|
|
@@ -156,8 +156,8 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
156
156
|
slug?: string | undefined;
|
|
157
157
|
attributes?: Record<string, string> | undefined;
|
|
158
158
|
} | undefined;
|
|
159
|
-
sessionId?: string | undefined;
|
|
160
159
|
userId?: string | undefined;
|
|
160
|
+
sessionId?: string | undefined;
|
|
161
161
|
addedAt?: string | undefined;
|
|
162
162
|
}[];
|
|
163
163
|
subtotal: number;
|
|
@@ -173,9 +173,9 @@ export declare const cartSummarySchema: z.ZodObject<{
|
|
|
173
173
|
slug?: string | undefined;
|
|
174
174
|
attributes?: Record<string, string> | undefined;
|
|
175
175
|
} | undefined;
|
|
176
|
+
userId?: string | undefined;
|
|
176
177
|
sessionId?: string | undefined;
|
|
177
178
|
quantity?: number | undefined;
|
|
178
|
-
userId?: string | undefined;
|
|
179
179
|
addedAt?: string | undefined;
|
|
180
180
|
}[];
|
|
181
181
|
subtotal: number;
|
|
@@ -417,10 +417,10 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
417
417
|
updatedAt: string | Date | z.objectOutputType<{
|
|
418
418
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
419
419
|
}, z.ZodTypeAny, "passthrough">;
|
|
420
|
+
userId: string;
|
|
420
421
|
productId: string;
|
|
421
422
|
quantity: number;
|
|
422
423
|
productTitle: string;
|
|
423
|
-
userId: string;
|
|
424
424
|
userEmail: string;
|
|
425
425
|
paymentStatus: "pending" | "paid" | "failed" | "processing" | "refunded" | "partial_refund";
|
|
426
426
|
orderDate: string | Date | z.objectOutputType<{
|
|
@@ -546,10 +546,10 @@ export declare const orderFirestoreSchema: z.ZodObject<{
|
|
|
546
546
|
updatedAt: string | Date | z.objectInputType<{
|
|
547
547
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
548
548
|
}, z.ZodTypeAny, "passthrough">;
|
|
549
|
+
userId: string;
|
|
549
550
|
productId: string;
|
|
550
551
|
quantity: number;
|
|
551
552
|
productTitle: string;
|
|
552
|
-
userId: string;
|
|
553
553
|
userEmail: string;
|
|
554
554
|
paymentStatus: "pending" | "paid" | "failed" | "processing" | "refunded" | "partial_refund";
|
|
555
555
|
orderDate: string | Date | z.objectInputType<{
|
|
@@ -794,6 +794,7 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
794
794
|
address: {} & {
|
|
795
795
|
[k: string]: unknown;
|
|
796
796
|
};
|
|
797
|
+
userId: string;
|
|
797
798
|
items: {
|
|
798
799
|
title: string;
|
|
799
800
|
price: number;
|
|
@@ -805,7 +806,6 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
805
806
|
attributes?: Record<string, string> | undefined;
|
|
806
807
|
}[];
|
|
807
808
|
total: number;
|
|
808
|
-
userId: string;
|
|
809
809
|
paymentStatus: string;
|
|
810
810
|
subtotal: number;
|
|
811
811
|
orderStatus: "cancelled" | "pending" | "processing" | "refunded" | "confirmed" | "shipped" | "delivered" | "return_requested" | "returned";
|
|
@@ -831,6 +831,7 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
831
831
|
address: {} & {
|
|
832
832
|
[k: string]: unknown;
|
|
833
833
|
};
|
|
834
|
+
userId: string;
|
|
834
835
|
items: {
|
|
835
836
|
title: string;
|
|
836
837
|
price: number;
|
|
@@ -842,7 +843,6 @@ export declare const orderSchema: z.ZodObject<{
|
|
|
842
843
|
attributes?: Record<string, string> | undefined;
|
|
843
844
|
}[];
|
|
844
845
|
total: number;
|
|
845
|
-
userId: string;
|
|
846
846
|
paymentStatus: string;
|
|
847
847
|
subtotal: number;
|
|
848
848
|
orderStatus: "cancelled" | "pending" | "processing" | "refunded" | "confirmed" | "shipped" | "delivered" | "return_requested" | "returned";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SavedPaymentMethodsRepository
|
|
3
|
+
*
|
|
4
|
+
* Persists user payment identifiers (UPI VPAs, card tokens, etc.) for
|
|
5
|
+
* checkout pre-fill and cross-account fraud detection.
|
|
6
|
+
*
|
|
7
|
+
* PII: `identifier` is encrypted at rest (AES-256-GCM) — never returned
|
|
8
|
+
* to clients. `identifierHash` is unencrypted SHA-256 for cross-account
|
|
9
|
+
* dedup queries. `displayLabel` is pre-masked and safe to display.
|
|
10
|
+
*/
|
|
11
|
+
import { BaseRepository, type DocumentSnapshot } from "../../../providers/db-firebase";
|
|
12
|
+
import { type SavedPaymentMethodBanStatus, type SavedPaymentMethodCreateInput, type SavedPaymentMethodDocument } from "../schemas/saved-methods-firestore";
|
|
13
|
+
export declare class SavedPaymentMethodsRepository extends BaseRepository<SavedPaymentMethodDocument> {
|
|
14
|
+
constructor();
|
|
15
|
+
private decrypt;
|
|
16
|
+
private encrypt;
|
|
17
|
+
protected mapDoc<D = SavedPaymentMethodDocument>(snap: DocumentSnapshot): D;
|
|
18
|
+
createWithId(id: string, data: Partial<SavedPaymentMethodDocument>): Promise<SavedPaymentMethodDocument>;
|
|
19
|
+
update(id: string, data: Partial<SavedPaymentMethodDocument>): Promise<SavedPaymentMethodDocument>;
|
|
20
|
+
/** List all saved methods for a user. Never returns raw `identifier` — only `displayLabel`. */
|
|
21
|
+
listByUser(userId: string): Promise<SavedPaymentMethodDocument[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Idempotent upsert by (userId + identifierHash).
|
|
24
|
+
* Computes hash + encrypts identifier before write.
|
|
25
|
+
* Updates lastUsedAt on re-add.
|
|
26
|
+
*/
|
|
27
|
+
upsertForUser(userId: string, input: SavedPaymentMethodCreateInput): Promise<SavedPaymentMethodDocument>;
|
|
28
|
+
/** Cross-account lookup by identifierHash. Returns display-safe docs (identifier stripped). */
|
|
29
|
+
listByIdentifierHash(hash: string): Promise<SavedPaymentMethodDocument[]>;
|
|
30
|
+
/** Batch-ban all methods for a user. Used by hard-ban cascade. */
|
|
31
|
+
banAllForUser(userId: string, banData: {
|
|
32
|
+
banReason: string;
|
|
33
|
+
bannedBy: string;
|
|
34
|
+
}): Promise<number>;
|
|
35
|
+
/** Reverse auto-ban cascade on user unban. Leaves manually-banned methods untouched. */
|
|
36
|
+
unbanAutoForUser(userId: string): Promise<number>;
|
|
37
|
+
/** List by banStatus for admin view. Returns display-safe docs. */
|
|
38
|
+
listByBanStatus(banStatus: SavedPaymentMethodBanStatus, limit?: number, offset?: number): Promise<SavedPaymentMethodDocument[]>;
|
|
39
|
+
banById(id: string, banData: {
|
|
40
|
+
banReason: string;
|
|
41
|
+
bannedBy: string;
|
|
42
|
+
}): Promise<void>;
|
|
43
|
+
clearBanById(id: string): Promise<void>;
|
|
44
|
+
deleteForUser(userId: string, id: string): Promise<void>;
|
|
45
|
+
/** Public wrapper — compute identifier hash from outside the class (e.g. in API routes). */
|
|
46
|
+
computeIdentifierHash(type: string, identifier: string): string;
|
|
47
|
+
}
|
|
48
|
+
export declare const savedPaymentMethodsRepository: SavedPaymentMethodsRepository;
|