@mohasinac/appkit 3.2.6 → 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.
Files changed (53) hide show
  1. package/dist/_internal/shared/actions/action-registry.js +62 -0
  2. package/dist/constants/api-endpoints.d.ts +12 -0
  3. package/dist/constants/api-endpoints.js +4 -0
  4. package/dist/features/about/components/PublicProfileView.js +8 -5
  5. package/dist/features/addresses/repository/addresses.repository.d.ts +14 -1
  6. package/dist/features/addresses/repository/addresses.repository.js +122 -1
  7. package/dist/features/addresses/schemas/firestore.d.ts +22 -3
  8. package/dist/features/addresses/schemas/firestore.js +25 -0
  9. package/dist/features/addresses/schemas/index.d.ts +2 -2
  10. package/dist/features/admin/components/AdminAddressClustersView.d.ts +5 -0
  11. package/dist/features/admin/components/AdminAddressClustersView.js +38 -0
  12. package/dist/features/admin/components/AdminAddressesView.d.ts +5 -0
  13. package/dist/features/admin/components/AdminAddressesView.js +137 -0
  14. package/dist/features/admin/components/AdminPaymentClustersView.d.ts +5 -0
  15. package/dist/features/admin/components/AdminPaymentClustersView.js +37 -0
  16. package/dist/features/admin/components/AdminPaymentMethodsView.d.ts +5 -0
  17. package/dist/features/admin/components/AdminPaymentMethodsView.js +130 -0
  18. package/dist/features/admin/components/index.d.ts +8 -0
  19. package/dist/features/admin/components/index.js +4 -0
  20. package/dist/features/auth/actions/profile-actions.d.ts +3 -1
  21. package/dist/features/auth/actions/profile-actions.js +29 -2
  22. package/dist/features/cart/schemas/index.d.ts +6 -6
  23. package/dist/features/orders/schemas/index.d.ts +4 -4
  24. package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +48 -0
  25. package/dist/features/payments/repository/saved-payment-methods.repository.js +236 -0
  26. package/dist/features/payments/schemas/index.d.ts +1 -0
  27. package/dist/features/payments/schemas/index.js +1 -0
  28. package/dist/features/payments/schemas/saved-methods-firestore.d.ts +63 -0
  29. package/dist/features/payments/schemas/saved-methods-firestore.js +60 -0
  30. package/dist/features/payments/server.d.ts +2 -0
  31. package/dist/features/payments/server.js +2 -0
  32. package/dist/features/promotions/schemas/index.d.ts +6 -6
  33. package/dist/features/reviews/repository/reviews.repository.d.ts +12 -0
  34. package/dist/features/reviews/repository/reviews.repository.js +24 -0
  35. package/dist/features/reviews/schemas/firestore.d.ts +5 -1
  36. package/dist/features/reviews/schemas/firestore.js +2 -0
  37. package/dist/features/reviews/schemas/index.d.ts +6 -6
  38. package/dist/features/seller/components/SellerAddressesView.js +7 -3
  39. package/dist/features/support/schemas/index.d.ts +8 -8
  40. package/dist/features/wishlist/schemas/index.d.ts +2 -2
  41. package/dist/index.d.ts +9 -0
  42. package/dist/index.js +10 -0
  43. package/dist/next/routing/route-map.d.ts +8 -0
  44. package/dist/next/routing/route-map.js +4 -0
  45. package/dist/repositories/index.d.ts +1 -0
  46. package/dist/repositories/index.js +1 -0
  47. package/dist/schemas/registry.d.ts +66 -66
  48. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  49. package/dist/security/index.d.ts +1 -1
  50. package/dist/security/index.js +1 -1
  51. package/dist/security/pii-schemas.d.ts +2 -0
  52. package/dist/security/pii-schemas.js +2 -0
  53. 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,5 @@
1
+ import React from "react";
2
+ export interface AdminPaymentClustersViewProps {
3
+ children?: React.ReactNode;
4
+ }
5
+ export declare function AdminPaymentClustersView(_props: AdminPaymentClustersViewProps): import("react/jsx-runtime").JSX.Element;
@@ -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,5 @@
1
+ import React from "react";
2
+ export interface AdminPaymentMethodsViewProps {
3
+ children?: React.ReactNode;
4
+ }
5
+ export declare function AdminPaymentMethodsView(_props: AdminPaymentMethodsViewProps): import("react/jsx-runtime").JSX.Element;
@@ -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 AUTHORED by this user (not reviews of their store). */
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 AUTHORED by this user (not reviews of their store). */
73
+ /** Approved product reviews written by this user as a buyer. Excludes seller→buyer ratings. */
74
74
  export async function getReviewsAuthoredBy(userId) {
75
- const snapshot = await reviewRepository.findApprovedByUser(userId).catch(() => []);
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;