@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.
Files changed (134) hide show
  1. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +1 -1
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +3 -3
  3. package/dist/_internal/server/features/products/data.d.ts +2 -1
  4. package/dist/_internal/server/jobs/core/countersReconcile.js +2 -1
  5. package/dist/_internal/shared/actions/action-registry.js +62 -0
  6. package/dist/_internal/shared/features/products/types.d.ts +3 -2
  7. package/dist/client.d.ts +2 -0
  8. package/dist/client.js +1 -0
  9. package/dist/constants/api-endpoints.d.ts +12 -0
  10. package/dist/constants/api-endpoints.js +4 -0
  11. package/dist/constants/field-names.d.ts +2 -0
  12. package/dist/constants/field-names.js +2 -0
  13. package/dist/features/about/components/PublicProfileView.js +8 -5
  14. package/dist/features/account/components/UserOffersPanel.js +0 -1
  15. package/dist/features/addresses/repository/addresses.repository.d.ts +14 -1
  16. package/dist/features/addresses/repository/addresses.repository.js +122 -1
  17. package/dist/features/addresses/schemas/firestore.d.ts +22 -3
  18. package/dist/features/addresses/schemas/firestore.js +25 -0
  19. package/dist/features/addresses/schemas/index.d.ts +2 -2
  20. package/dist/features/admin/components/AdminAddressClustersView.d.ts +5 -0
  21. package/dist/features/admin/components/AdminAddressClustersView.js +38 -0
  22. package/dist/features/admin/components/AdminAddressesView.d.ts +5 -0
  23. package/dist/features/admin/components/AdminAddressesView.js +137 -0
  24. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -4
  25. package/dist/features/admin/components/AdminFulfillmentView.js +2 -3
  26. package/dist/features/admin/components/AdminOrderEditorView.js +1 -3
  27. package/dist/features/admin/components/AdminPaymentClustersView.d.ts +5 -0
  28. package/dist/features/admin/components/AdminPaymentClustersView.js +37 -0
  29. package/dist/features/admin/components/AdminPaymentMethodsView.d.ts +5 -0
  30. package/dist/features/admin/components/AdminPaymentMethodsView.js +130 -0
  31. package/dist/features/admin/components/AdminSiteSettingsView.js +2 -2
  32. package/dist/features/admin/components/index.d.ts +8 -0
  33. package/dist/features/admin/components/index.js +4 -0
  34. package/dist/features/admin/schemas/firestore.d.ts +3 -5
  35. package/dist/features/admin/types/product.types.d.ts +2 -1
  36. package/dist/features/auth/actions/profile-actions.d.ts +3 -1
  37. package/dist/features/auth/actions/profile-actions.js +29 -2
  38. package/dist/features/auth/schemas/firestore.d.ts +8 -10
  39. package/dist/features/cart/schemas/index.d.ts +6 -6
  40. package/dist/features/categories/components/CategoryBundlesListing.js +1 -3
  41. package/dist/features/checkout/schemas/firestore.d.ts +4 -4
  42. package/dist/features/copilot/components/AdminCopilotView.js +4 -4
  43. package/dist/features/events/schemas/firestore.d.ts +2 -2
  44. package/dist/features/grouped/schemas/firestore.d.ts +2 -4
  45. package/dist/features/history/hooks/useHistory.js +3 -2
  46. package/dist/features/homepage/actions/homepage-section-actions.d.ts +6 -6
  47. package/dist/features/homepage/actions/homepage-section-actions.js +2 -2
  48. package/dist/features/homepage/components/PromoGrid.js +1 -1
  49. package/dist/features/homepage/schemas/firestore.d.ts +15 -14
  50. package/dist/features/layout/AppLayoutShell.js +3 -18
  51. package/dist/features/layout/background-seed-defaults.d.ts +19 -0
  52. package/dist/features/layout/background-seed-defaults.js +14 -0
  53. package/dist/features/lottery/schemas/firestore.d.ts +2 -2
  54. package/dist/features/messages/hooks/useConversation.js +5 -13
  55. package/dist/features/messages/hooks/useConversations.js +0 -1
  56. package/dist/features/messages/schemas/firestore.d.ts +2 -2
  57. package/dist/features/orders/components/RefundHistoryTable.js +1 -3
  58. package/dist/features/orders/schemas/firestore.d.ts +2 -1
  59. package/dist/features/orders/schemas/index.d.ts +11 -7
  60. package/dist/features/orders/schemas/index.js +1 -1
  61. package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +48 -0
  62. package/dist/features/payments/repository/saved-payment-methods.repository.js +236 -0
  63. package/dist/features/payments/schemas/index.d.ts +1 -0
  64. package/dist/features/payments/schemas/index.js +1 -0
  65. package/dist/features/payments/schemas/saved-methods-firestore.d.ts +63 -0
  66. package/dist/features/payments/schemas/saved-methods-firestore.js +60 -0
  67. package/dist/features/payments/server.d.ts +2 -0
  68. package/dist/features/payments/server.js +2 -0
  69. package/dist/features/products/actions/product-actions.d.ts +2 -1
  70. package/dist/features/products/api/[id]/route.js +1 -1
  71. package/dist/features/products/api/route.js +1 -1
  72. package/dist/features/products/components/CompareOverlay.d.ts +18 -1
  73. package/dist/features/products/components/CompareOverlay.js +0 -16
  74. package/dist/features/products/components/ProductDetailActions.js +2 -1
  75. package/dist/features/products/components/ShowGroupSection.js +1 -3
  76. package/dist/features/products/repository/product-features.repository.js +1 -1
  77. package/dist/features/products/schemas/firestore.d.ts +3 -6
  78. package/dist/features/promotions/actions/coupon-actions.d.ts +2 -1
  79. package/dist/features/promotions/hooks/useCouponValidate.d.ts +2 -1
  80. package/dist/features/promotions/repository/coupons.repository.d.ts +2 -1
  81. package/dist/features/promotions/schemas/firestore.d.ts +4 -4
  82. package/dist/features/promotions/schemas/index.d.ts +6 -6
  83. package/dist/features/reviews/repository/reviews.repository.d.ts +12 -0
  84. package/dist/features/reviews/repository/reviews.repository.js +24 -0
  85. package/dist/features/reviews/schemas/firestore.d.ts +5 -1
  86. package/dist/features/reviews/schemas/firestore.js +2 -0
  87. package/dist/features/reviews/schemas/index.d.ts +6 -6
  88. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  89. package/dist/features/scams/schemas/firestore.d.ts +6 -6
  90. package/dist/features/search/types/index.d.ts +3 -2
  91. package/dist/features/search/types/index.js +0 -1
  92. package/dist/features/seller/components/FulfillmentView.js +2 -3
  93. package/dist/features/seller/components/PrintCenterView.js +1 -3
  94. package/dist/features/seller/components/SellerAddressesView.js +7 -4
  95. package/dist/features/seller/components/SellerOffersPanel.js +0 -1
  96. package/dist/features/seller/components/SellerReviewsView.js +0 -1
  97. package/dist/features/seller/schemas/firestore.d.ts +2 -4
  98. package/dist/features/server-errors/schemas/firestore.d.ts +2 -2
  99. package/dist/features/store-extensions/schemas/firestore.d.ts +2 -2
  100. package/dist/features/stores/components/StoreScopedSearch.js +2 -4
  101. package/dist/features/stores/repository/store.repository.js +3 -1
  102. package/dist/features/stores/types/index.d.ts +2 -1
  103. package/dist/features/stores/types/index.js +0 -1
  104. package/dist/features/support/schemas/firestore.d.ts +2 -2
  105. package/dist/features/support/schemas/index.d.ts +8 -8
  106. package/dist/features/wishlist/schemas/index.d.ts +2 -2
  107. package/dist/features/wishlist/types/index.d.ts +2 -1
  108. package/dist/index.d.ts +9 -0
  109. package/dist/index.js +10 -0
  110. package/dist/next/routing/route-map.d.ts +8 -0
  111. package/dist/next/routing/route-map.js +4 -0
  112. package/dist/providers/auth-firebase/provider.js +3 -1
  113. package/dist/providers/auth-firebase/session.js +3 -1
  114. package/dist/repositories/index.d.ts +1 -0
  115. package/dist/repositories/index.js +1 -0
  116. package/dist/schemas/registry.d.ts +66 -66
  117. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  118. package/dist/security/index.d.ts +1 -1
  119. package/dist/security/index.js +1 -1
  120. package/dist/security/pii-schemas.d.ts +2 -0
  121. package/dist/security/pii-schemas.js +2 -0
  122. package/dist/seo/json-ld.d.ts +2 -1
  123. package/dist/styles.css +1 -1
  124. package/dist/tailwind-utilities.css +1 -1
  125. package/dist/ui/components/Form.d.ts +3 -1
  126. package/dist/ui/components/Form.js +2 -1
  127. package/dist/ui/components/Radio.d.ts +7 -0
  128. package/dist/ui/components/Radio.js +7 -0
  129. package/dist/ui/components/surface-tokens.d.ts +4 -0
  130. package/dist/ui/components/surface-tokens.js +24 -0
  131. package/dist/ui/index.d.ts +2 -2
  132. package/dist/ui/index.js +1 -1
  133. package/dist/ui/rich-text/RichText.js +0 -1
  134. package/package.json +5 -2
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  const HANDLE_STYLE = {
4
- background: "var(--appkit-gradient-sidebar)", // audit-inline-style-ok: theme gradient CSS var, not a raw colour
4
+ background: "var(--appkit-gradient-sidebar)",
5
5
  };
6
6
  const CHEVRON_STYLE = {
7
7
  color: "var(--appkit-color-text-on-primary)",
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useTransition } from "react";
4
4
  import { normalizeError } from "../../../../errors/normalize";
5
5
  import { Stack, Row, Text, Heading, Section, Container, Button, DataTable, } from "../../../../ui";
6
- import { Form, FieldInput, FieldSelect } from "../../../../ui";
6
+ import { Form, FieldInput, FieldSelect, Input } from "../../../../ui";
7
7
  export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
8
8
  const [isPending, startTransition] = useTransition();
9
9
  const [error, setError] = useState(null);
@@ -66,14 +66,14 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
66
66
  {
67
67
  key: "name",
68
68
  header: "Prize Name",
69
- render: (s) => (_jsx("input", { type: "text", className: "w-full rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-input)] px-2 py-1 text-sm", placeholder: `Prize for slot ${s.slotNumber}`, value: s.name, onChange: (e) => updateSlot(s._idx, "name", e.target.value), "aria-label": `Slot ${s.slotNumber} prize name` })),
69
+ render: (s) => (_jsx(Input, { type: "text", className: "w-full", placeholder: `Prize for slot ${s.slotNumber}`, value: s.name, onChange: (e) => updateSlot(s._idx, "name", e.target.value), "aria-label": `Slot ${s.slotNumber} prize name` })),
70
70
  },
71
71
  ...(pricingMode === "variable"
72
72
  ? [
73
73
  {
74
74
  key: "priceInPaise",
75
75
  header: "Price (₹)",
76
- render: (s) => (_jsx("input", { type: "number", className: "w-24 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-input)] px-2 py-1 text-sm", placeholder: "0", value: s.priceInPaise / 100, onChange: (e) => updateSlot(s._idx, "priceInPaise", e.target.value), "aria-label": `Slot ${s.slotNumber} price` })),
76
+ render: (s) => (_jsx(Input, { type: "number", className: "w-24", placeholder: "0", value: s.priceInPaise / 100, onChange: (e) => updateSlot(s._idx, "priceInPaise", e.target.value), "aria-label": `Slot ${s.slotNumber} price` })),
77
77
  },
78
78
  ]
79
79
  : []),
@@ -1,4 +1,5 @@
1
1
  import type { ProductDocument } from "../../../../features/products/schemas/firestore";
2
+ import type { ListingType } from "../../../../features/products/types";
2
3
  /** Fetch a single product by slug or id, deduped per request. */
3
4
  export declare const getProductForDetail: (slugOrId: string) => Promise<ProductDocument | null>;
4
5
  /** Fetch the first page of approved reviews for a product, deduped per request. */
@@ -8,7 +9,7 @@ export interface SitemapProduct {
8
9
  slugOrId: string;
9
10
  updatedAt: Date;
10
11
  /** Canonical listing-kind discriminator (SB1-G — Phase 4 dropped booleans). */
11
- listingType: "standard" | "auction" | "pre-order" | "prize-draw";
12
+ listingType: ListingType;
12
13
  }
13
14
  /** List all published products for sitemap generation. */
14
15
  export declare function listSitemapProducts(): Promise<SitemapProduct[]>;
@@ -96,7 +96,8 @@ export async function runCountersReconcile(ctx) {
96
96
  try {
97
97
  await reconcileCategories(ctx);
98
98
  }
99
- catch (err) { // audit-catch-raw-ok: pre-existing-handler-intentional
99
+ catch (err) {
100
+ void normalizeError(err);
100
101
  ctx.logger.error("Category reconciliation failed", err);
101
102
  }
102
103
  try {
@@ -1559,6 +1559,68 @@ export const ACTIONS = {
1559
1559
  confirmKind: "danger",
1560
1560
  },
1561
1561
  },
1562
+ // ── Address ban management ─────────────────────────────────────────────
1563
+ "ban-address": {
1564
+ id: "admin.ban-address",
1565
+ label: "Ban Address",
1566
+ ariaLabel: "Ban this address",
1567
+ description: "Ban an address from being used on the platform.",
1568
+ kind: "danger",
1569
+ permissions: ["admin", "moderator"],
1570
+ confirmation: {
1571
+ title: "Ban this address?",
1572
+ body: "The address will be blocked from use at checkout across all accounts.",
1573
+ confirmLabel: "Ban address",
1574
+ confirmKind: "danger",
1575
+ },
1576
+ },
1577
+ "approve-unban": {
1578
+ id: "admin.approve-unban",
1579
+ label: "Approve Unban",
1580
+ ariaLabel: "Approve unban request",
1581
+ description: "Approve an address unban request and restore it to active.",
1582
+ kind: "primary",
1583
+ permissions: ["admin"],
1584
+ },
1585
+ "reject-unban": {
1586
+ id: "admin.reject-unban",
1587
+ label: "Reject Unban",
1588
+ ariaLabel: "Reject unban request",
1589
+ description: "Reject an address unban request — address stays banned.",
1590
+ kind: "secondary",
1591
+ permissions: ["admin"],
1592
+ },
1593
+ // ── Payment method ban management ──────────────────────────────────────
1594
+ "ban-payment-method": {
1595
+ id: "admin.ban-payment-method",
1596
+ label: "Ban Payment Method",
1597
+ ariaLabel: "Ban this payment method",
1598
+ description: "Ban a payment identifier from being used on the platform.",
1599
+ kind: "danger",
1600
+ permissions: ["admin", "moderator"],
1601
+ confirmation: {
1602
+ title: "Ban this payment method?",
1603
+ body: "The payment identifier will be blocked from checkout across all accounts.",
1604
+ confirmLabel: "Ban payment method",
1605
+ confirmKind: "danger",
1606
+ },
1607
+ },
1608
+ "approve-payment-unban": {
1609
+ id: "admin.approve-payment-unban",
1610
+ label: "Approve Unban",
1611
+ ariaLabel: "Approve payment method unban",
1612
+ description: "Approve a payment method unban request.",
1613
+ kind: "primary",
1614
+ permissions: ["admin"],
1615
+ },
1616
+ "reject-payment-unban": {
1617
+ id: "admin.reject-payment-unban",
1618
+ label: "Reject Unban",
1619
+ ariaLabel: "Reject payment method unban",
1620
+ description: "Reject a payment method unban request — stays banned.",
1621
+ kind: "secondary",
1622
+ permissions: ["admin"],
1623
+ },
1562
1624
  },
1563
1625
  CART: {
1564
1626
  "clear-cart": {
@@ -1,4 +1,5 @@
1
- export type { ProductStatus, ProductCondition, ListingType, ProductItem, ProductListResponse, ProductListParams, ProductImage, ProductSeo, } from "../../../../features/products/types/index";
1
+ import type { ProductStatus, ProductCondition, ListingType, ProductItem, ProductListResponse, ProductListParams, ProductImage, ProductSeo } from "../../../../features/products/types/index";
2
+ export type { ProductStatus, ProductCondition, ListingType, ProductItem, ProductListResponse, ProductListParams, ProductImage, ProductSeo, };
2
3
  export type { ProductDocument, CustomField, CustomSection, ProductSpecification, } from "../../../../features/products/schemas/firestore";
3
4
  export type { ProductFeatureDocument } from "../../../../features/products/schemas/product-features";
4
5
  /** Serializable shape passed from server page → client view as initialData. */
@@ -21,7 +22,7 @@ export interface ProductDetailInitial {
21
22
  tags: string[];
22
23
  condition?: string;
23
24
  /** Canonical listing-kind discriminator (SB1-G — Phase 4 dropped legacy booleans). */
24
- listingType?: "standard" | "auction" | "pre-order" | "prize-draw";
25
+ listingType?: ListingType;
25
26
  auctionEndDate?: string;
26
27
  startingBid?: number;
27
28
  currentBid?: number;
package/dist/client.d.ts CHANGED
@@ -109,6 +109,8 @@ export { Button } from "./ui/components/Button";
109
109
  export { ClaimCouponButton } from "./ui/components/ClaimCouponButton";
110
110
  export type { ClaimCouponButtonProps } from "./ui/components/ClaimCouponButton";
111
111
  export { Checkbox } from "./ui/components/Checkbox";
112
+ export type { RadioItemProps } from "./ui/components/Radio";
113
+ export { RadioItem } from "./ui/components/Radio";
112
114
  export { Input } from "./ui/components/Input";
113
115
  export { OtpInput } from "./ui/components/OtpInput";
114
116
  export type { OtpInputProps } from "./ui/components/OtpInput";
package/dist/client.js CHANGED
@@ -171,6 +171,7 @@ export { Badge } from "./ui/components/Badge";
171
171
  export { Button } from "./ui/components/Button";
172
172
  export { ClaimCouponButton } from "./ui/components/ClaimCouponButton";
173
173
  export { Checkbox } from "./ui/components/Checkbox";
174
+ export { RadioItem } from "./ui/components/Radio";
174
175
  export { Input } from "./ui/components/Input";
175
176
  export { OtpInput } from "./ui/components/OtpInput";
176
177
  export { DateInput, DateRangeInput } from "./ui/components/DateInput";
@@ -137,6 +137,10 @@ export declare const ADMIN_ENDPOINTS: {
137
137
  readonly SCAMMER_BY_ID: (id: string) => string;
138
138
  readonly ADDRESSES: "/api/admin/addresses";
139
139
  readonly ADDRESS_BY_ID: (id: string) => string;
140
+ readonly ADDRESS_CLUSTERS: "/api/admin/addresses/clusters";
141
+ readonly PAYMENT_METHODS: "/api/admin/payment-methods";
142
+ readonly PAYMENT_METHOD_BY_ID: (id: string) => string;
143
+ readonly PAYMENT_METHOD_CLUSTERS: "/api/admin/payment-methods/clusters";
140
144
  readonly GROUPED_LISTINGS: "/api/admin/grouped-listings";
141
145
  readonly GROUPED_LISTING_BY_ID: (id: string) => string;
142
146
  readonly ADMIN_FULFILLMENT: (storeId: string) => string;
@@ -472,6 +476,10 @@ export declare const API_ENDPOINTS: {
472
476
  readonly SCAMMER_BY_ID: (id: string) => string;
473
477
  readonly ADDRESSES: "/api/admin/addresses";
474
478
  readonly ADDRESS_BY_ID: (id: string) => string;
479
+ readonly ADDRESS_CLUSTERS: "/api/admin/addresses/clusters";
480
+ readonly PAYMENT_METHODS: "/api/admin/payment-methods";
481
+ readonly PAYMENT_METHOD_BY_ID: (id: string) => string;
482
+ readonly PAYMENT_METHOD_CLUSTERS: "/api/admin/payment-methods/clusters";
475
483
  readonly GROUPED_LISTINGS: "/api/admin/grouped-listings";
476
484
  readonly GROUPED_LISTING_BY_ID: (id: string) => string;
477
485
  readonly ADMIN_FULFILLMENT: (storeId: string) => string;
@@ -809,6 +817,10 @@ export declare const API_ROUTES: {
809
817
  readonly SCAMMER_BY_ID: (id: string) => string;
810
818
  readonly ADDRESSES: "/api/admin/addresses";
811
819
  readonly ADDRESS_BY_ID: (id: string) => string;
820
+ readonly ADDRESS_CLUSTERS: "/api/admin/addresses/clusters";
821
+ readonly PAYMENT_METHODS: "/api/admin/payment-methods";
822
+ readonly PAYMENT_METHOD_BY_ID: (id: string) => string;
823
+ readonly PAYMENT_METHOD_CLUSTERS: "/api/admin/payment-methods/clusters";
812
824
  readonly GROUPED_LISTINGS: "/api/admin/grouped-listings";
813
825
  readonly GROUPED_LISTING_BY_ID: (id: string) => string;
814
826
  readonly ADMIN_FULFILLMENT: (storeId: string) => string;
@@ -156,6 +156,10 @@ export const ADMIN_ENDPOINTS = {
156
156
  SCAMMER_BY_ID: (id) => `/api/admin/scammers/${id}`,
157
157
  ADDRESSES: "/api/admin/addresses",
158
158
  ADDRESS_BY_ID: (id) => `/api/admin/addresses/${id}`,
159
+ ADDRESS_CLUSTERS: "/api/admin/addresses/clusters",
160
+ PAYMENT_METHODS: "/api/admin/payment-methods",
161
+ PAYMENT_METHOD_BY_ID: (id) => `/api/admin/payment-methods/${id}`,
162
+ PAYMENT_METHOD_CLUSTERS: "/api/admin/payment-methods/clusters",
159
163
  GROUPED_LISTINGS: "/api/admin/grouped-listings",
160
164
  GROUPED_LISTING_BY_ID: (id) => `/api/admin/grouped-listings/${id}`,
161
165
  ADMIN_FULFILLMENT: (storeId) => `/api/store/fulfillment?storeId=${encodeURIComponent(storeId)}`,
@@ -111,6 +111,8 @@ export declare const PRODUCT_FIELDS: {
111
111
  readonly OPEN: "open";
112
112
  readonly CLOSED: "closed";
113
113
  };
114
+ readonly IS_ACTIVE: "isActive";
115
+ readonly DISPLAY_ORDER: "displayOrder";
114
116
  readonly LISTING_TYPE_VALUES: {
115
117
  readonly STANDARD: "standard";
116
118
  readonly AUCTION: "auction";
@@ -116,6 +116,8 @@ export const PRODUCT_FIELDS = {
116
116
  OPEN: "open",
117
117
  CLOSED: "closed",
118
118
  },
119
+ IS_ACTIVE: "isActive",
120
+ DISPLAY_ORDER: "displayOrder",
119
121
  LISTING_TYPE_VALUES: {
120
122
  STANDARD: "standard",
121
123
  AUCTION: "auction",
@@ -1,6 +1,6 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
- import { getPublicUserProfile, getProfileStoreProducts, getSellerReviews, getReviewsAuthoredBy } from "../../auth/actions/profile-actions";
3
+ import { getPublicUserProfile, getProfileStoreProducts, getReviewsAuthoredBy, getReviewsReceivedBy } from "../../auth/actions/profile-actions";
4
4
  import { storeRepository } from "../../stores/repository/store.repository";
5
5
  import { ROUTES } from "../../../constants";
6
6
  import { PAGE_CONTAINER } from "../../../_internal/shared/styles/page";
@@ -63,11 +63,11 @@ export async function PublicProfileView({ userId, }) {
63
63
  const t = await getTranslations("publicProfile");
64
64
  const profile = await getPublicUserProfile(userId).catch(() => null);
65
65
  const storeId = profile?.storeSlug ?? null;
66
- const [products, reviewsReceived, store, reviewsAuthored] = await Promise.all([
66
+ const [products, store, reviewsAuthored, reviewsReceivedAsBuyer] = await Promise.all([
67
67
  storeId ? getProfileStoreProducts(storeId).catch(() => []) : Promise.resolve([]),
68
- storeId ? getSellerReviews(storeId).catch(() => []) : Promise.resolve([]),
69
68
  storeId ? storeRepository.findById(storeId).catch(() => null) : Promise.resolve(null),
70
69
  getReviewsAuthoredBy(userId).catch(() => []),
70
+ getReviewsReceivedBy(userId).catch(() => []),
71
71
  ]);
72
72
  const isSeller = isSellerUser(profile) || isAdminUser(profile);
73
73
  // For sellers: lead with store identity (name + logo). For buyers: user identity.
@@ -83,7 +83,7 @@ export async function PublicProfileView({ userId, }) {
83
83
  const pub = profile?.publicProfile;
84
84
  const stats = profile?.stats;
85
85
  const listingCount = products.length;
86
- const reviewCount = reviewsAuthored.length + reviewsReceived.length;
86
+ const reviewCount = reviewsAuthored.length + reviewsReceivedAsBuyer.length;
87
87
  const itemsSold = stats?.itemsSold ?? 0;
88
88
  const auctionsWon = stats?.auctionsWon ?? 0;
89
89
  const totalOrders = stats?.totalOrders ?? 0;
@@ -92,7 +92,7 @@ export async function PublicProfileView({ userId, }) {
92
92
  const storeDescription = store?.storeDescription ?? pub?.storeDescription;
93
93
  const profileHeroCtx = { displayName, photoURL, memberSince, isSeller, storeSlug, flex, page };
94
94
  const statItems = buildProfileStatItems(t, { listingCount, reviewCount, itemsSold, auctionsWon, totalOrders, isSeller });
95
- return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [renderProfileHero(t, profileHeroCtx), _jsxs(Stack, { gap: "2xl", className: `${page.container.md}`, paddingY: "y-3xl", children: [renderProfileStatsRow(flex, statItems), renderProfileBioSection(pub), renderStoreDescriptionSection(isSeller, storeSlug ?? null, storeDescription ?? null, storeName, t), renderProfileListingsSection(t, products, storeSlug ?? null), renderAuthoredReviewsSection(t, reviewsAuthored, displayName), isSeller && storeSlug && renderProfileReviewsSection(t, reviewsReceived, storeSlug), _jsx(Row, { justify: "center", padding: "t-xs", children: _jsxs(Link, { href: String(ROUTES.HOME), className: "text-sm text-zinc-400 dark:text-zinc-400 hover:text-neutral-600 dark:hover:text-zinc-300", children: ["\u2190 ", t("backHome")] }) })] })] }));
95
+ return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [renderProfileHero(t, profileHeroCtx), _jsxs(Stack, { gap: "2xl", className: `${page.container.md}`, paddingY: "y-3xl", children: [renderProfileStatsRow(flex, statItems), renderProfileBioSection(pub), renderStoreDescriptionSection(isSeller, storeSlug ?? null, storeDescription ?? null, storeName, t), renderProfileListingsSection(t, products, storeSlug ?? null), renderAuthoredReviewsSection(t, reviewsAuthored, displayName), reviewsReceivedAsBuyer.length > 0 && renderBuyerReceivedReviewsSection(t, reviewsReceivedAsBuyer), _jsx(Row, { justify: "center", padding: "t-xs", children: _jsxs(Link, { href: String(ROUTES.HOME), className: "text-sm text-zinc-400 dark:text-zinc-400 hover:text-neutral-600 dark:hover:text-zinc-300", children: ["\u2190 ", t("backHome")] }) })] })] }));
96
96
  }
97
97
  function buildProfileStatItems(t, ctx) {
98
98
  const { listingCount, reviewCount, itemsSold, auctionsWon, totalOrders, isSeller } = ctx;
@@ -136,3 +136,6 @@ function renderProfileReviewsSection(t, reviews, storeSlug) {
136
136
  function renderAuthoredReviewsSection(t, reviews, displayName) {
137
137
  return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("reviewsAuthoredTitle", { name: displayName }) }), reviews.length === 0 ? (_jsxs(Div, { rounded: "2xl", padding: "3xl", className: "text-center", border: "default", surface: "subtle", children: [_jsx(Star, { className: CLS_EMPTY_ICON }), _jsx(Text, { variant: "secondary", size: "sm", children: t("noReviewsAuthored", { name: displayName }) })] })) : (_jsx(Grid, { gap: "md", className: "grid-cols-1 sm:grid-cols-2", children: reviews.slice(0, 6).map((review) => _jsx(ReviewCard, { review: review }, review.id)) }))] }));
138
138
  }
139
+ function renderBuyerReceivedReviewsSection(t, reviews) {
140
+ return (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-4", children: t("reviewsReceivedAsBuyerTitle") || "Reviews Received" }), _jsx(Grid, { gap: "md", className: "grid-cols-1 sm:grid-cols-2", children: reviews.slice(0, 6).map((review) => _jsx(ReviewCard, { review: review }, review.id)) })] }));
141
+ }
@@ -83,7 +83,6 @@ export function UserOffersPanel({ fetchEndpoint = "/api/user/offers", onAcceptCo
83
83
  setOffers(items);
84
84
  }
85
85
  catch (err) {
86
- // toast-intentionally-silent: data load, error surfaced via fetchError state
87
86
  void normalizeError(err);
88
87
  setFetchError(err instanceof Error ? err.message : "Failed to load offers.");
89
88
  }
@@ -1,5 +1,5 @@
1
1
  import { BaseRepository, type DocumentSnapshot } from "../../../providers/db-firebase";
2
- import { type AddressCreateInput, type AddressDocument, type AddressOwnerType, type AddressUpdateInput } from "../schemas";
2
+ import { type AddressBanStatus, type AddressCreateInput, type AddressDocument, type AddressOwnerType, type AddressUpdateInput } from "../schemas";
3
3
  export declare class AddressesRepository extends BaseRepository<AddressDocument> {
4
4
  constructor();
5
5
  private decryptAddress;
@@ -15,6 +15,19 @@ export declare class AddressesRepository extends BaseRepository<AddressDocument>
15
15
  deleteForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<void>;
16
16
  setDefault(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<AddressDocument>;
17
17
  deleteAllForOwner(ownerType: AddressOwnerType, ownerId: string): Promise<number>;
18
+ private computeAddressHash;
19
+ banAllForOwner(ownerType: AddressOwnerType, ownerId: string, banData: {
20
+ banReason: string;
21
+ bannedBy: string;
22
+ }): Promise<number>;
23
+ unbanAutoForOwner(ownerType: AddressOwnerType, ownerId: string): Promise<number>;
24
+ listByAddressHash(addressHash: string): Promise<AddressDocument[]>;
25
+ listByBanStatus(banStatus: AddressBanStatus, limit?: number, offset?: number): Promise<AddressDocument[]>;
26
+ banById(id: string, banData: {
27
+ banReason: string;
28
+ bannedBy: string;
29
+ }): Promise<AddressDocument>;
30
+ clearBanById(id: string): Promise<void>;
18
31
  private clearDefaultFlag;
19
32
  }
20
33
  export declare const addressesRepository: AddressesRepository;
@@ -1,3 +1,4 @@
1
+ import { createHash } from "crypto";
1
2
  import { normalizeError } from "../../../errors/normalize";
2
3
  /**
3
4
  * AddressesRepository — SB-UNI-A 2026-05-13
@@ -86,6 +87,7 @@ export class AddressesRepository extends BaseRepository {
86
87
  ...input,
87
88
  ownerType,
88
89
  ownerId,
90
+ addressHash: this.computeAddressHash(input.postalCode, input.addressLine1, input.addressLine2),
89
91
  createdAt: now,
90
92
  updatedAt: now,
91
93
  };
@@ -121,7 +123,14 @@ export class AddressesRepository extends BaseRepository {
121
123
  if (input.isDefault) {
122
124
  await this.clearDefaultFlag(ownerType, ownerId);
123
125
  }
124
- return this.update(addressId, input);
126
+ const updateData = { ...input };
127
+ if (input.postalCode || input.addressLine1 || input.addressLine2 !== undefined) {
128
+ const postalCode = input.postalCode ?? existing.postalCode;
129
+ const line1 = input.addressLine1 ?? existing.addressLine1;
130
+ const line2 = input.addressLine2 ?? existing.addressLine2;
131
+ updateData.addressHash = this.computeAddressHash(postalCode, line1, line2);
132
+ }
133
+ return this.update(addressId, updateData);
125
134
  }
126
135
  async deleteForOwner(ownerType, ownerId, addressId) {
127
136
  const existing = await this.findById(addressId);
@@ -157,6 +166,118 @@ export class AddressesRepository extends BaseRepository {
157
166
  throw new DatabaseError(`Failed to delete all addresses for ${ownerType}:${ownerId}`, error);
158
167
  }
159
168
  }
169
+ computeAddressHash(postalCode, line1, line2) {
170
+ const norm = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
171
+ return createHash("sha256")
172
+ .update([postalCode, line1, line2 ?? ""].map(norm).join("|"))
173
+ .digest("hex");
174
+ }
175
+ async banAllForOwner(ownerType, ownerId, banData) {
176
+ try {
177
+ const snap = await this.getCollection()
178
+ .where(ADDRESS_FIELDS.OWNER_TYPE, "==", ownerType)
179
+ .where(ADDRESS_FIELDS.OWNER_ID, "==", ownerId)
180
+ .get();
181
+ if (snap.empty)
182
+ return 0;
183
+ const now = new Date();
184
+ const batch = this.db.batch();
185
+ snap.docs.forEach((doc) => {
186
+ batch.update(doc.ref, {
187
+ [ADDRESS_FIELDS.BAN_STATUS]: "banned",
188
+ [ADDRESS_FIELDS.BAN_REASON]: banData.banReason,
189
+ [ADDRESS_FIELDS.BANNED_BY]: banData.bannedBy,
190
+ [ADDRESS_FIELDS.BANNED_AT]: now,
191
+ [ADDRESS_FIELDS.AUTO_BANNED]: true,
192
+ [ADDRESS_FIELDS.UPDATED_AT]: now,
193
+ });
194
+ });
195
+ await batch.commit();
196
+ return snap.size;
197
+ }
198
+ catch (error) {
199
+ void normalizeError(error);
200
+ throw new DatabaseError(`Failed to ban addresses for ${ownerType}:${ownerId}`, error);
201
+ }
202
+ }
203
+ async unbanAutoForOwner(ownerType, ownerId) {
204
+ try {
205
+ const snap = await this.getCollection()
206
+ .where(ADDRESS_FIELDS.OWNER_TYPE, "==", ownerType)
207
+ .where(ADDRESS_FIELDS.OWNER_ID, "==", ownerId)
208
+ .where(ADDRESS_FIELDS.AUTO_BANNED, "==", true)
209
+ .get();
210
+ if (snap.empty)
211
+ return 0;
212
+ const now = new Date();
213
+ const batch = this.db.batch();
214
+ snap.docs.forEach((doc) => {
215
+ batch.update(doc.ref, {
216
+ [ADDRESS_FIELDS.BAN_STATUS]: null,
217
+ [ADDRESS_FIELDS.BAN_REASON]: null,
218
+ [ADDRESS_FIELDS.BANNED_BY]: null,
219
+ [ADDRESS_FIELDS.BANNED_AT]: null,
220
+ [ADDRESS_FIELDS.AUTO_BANNED]: null,
221
+ [ADDRESS_FIELDS.UPDATED_AT]: now,
222
+ });
223
+ });
224
+ await batch.commit();
225
+ return snap.size;
226
+ }
227
+ catch (error) {
228
+ void normalizeError(error);
229
+ throw new DatabaseError(`Failed to unban addresses for ${ownerType}:${ownerId}`, error);
230
+ }
231
+ }
232
+ async listByAddressHash(addressHash) {
233
+ try {
234
+ const snap = await this.getCollection()
235
+ .where(ADDRESS_FIELDS.ADDRESS_HASH, "==", addressHash)
236
+ .get();
237
+ return snap.docs.map((d) => this.mapDoc(d));
238
+ }
239
+ catch (error) {
240
+ void normalizeError(error);
241
+ throw new DatabaseError(`Failed to lookup addresses by hash`, error);
242
+ }
243
+ }
244
+ async listByBanStatus(banStatus, limit = 50, offset = 0) {
245
+ try {
246
+ const snap = await this.getCollection()
247
+ .where(ADDRESS_FIELDS.BAN_STATUS, "==", banStatus)
248
+ .orderBy(ADDRESS_FIELDS.BANNED_AT, "desc")
249
+ .limit(limit)
250
+ .offset(offset)
251
+ .get();
252
+ return snap.docs.map((d) => this.mapDoc(d));
253
+ }
254
+ catch (error) {
255
+ void normalizeError(error);
256
+ throw new DatabaseError(`Failed to list addresses by banStatus:${banStatus}`, error);
257
+ }
258
+ }
259
+ async banById(id, banData) {
260
+ return this.update(id, {
261
+ banStatus: "banned",
262
+ banReason: banData.banReason,
263
+ bannedBy: banData.bannedBy,
264
+ bannedAt: new Date(),
265
+ autoBanned: false,
266
+ });
267
+ }
268
+ async clearBanById(id) {
269
+ const now = new Date();
270
+ await this.db.collection(ADDRESSES_COLLECTION).doc(id).update({
271
+ [ADDRESS_FIELDS.BAN_STATUS]: null,
272
+ [ADDRESS_FIELDS.BAN_REASON]: null,
273
+ [ADDRESS_FIELDS.BANNED_BY]: null,
274
+ [ADDRESS_FIELDS.BANNED_AT]: null,
275
+ [ADDRESS_FIELDS.AUTO_BANNED]: null,
276
+ [ADDRESS_FIELDS.UNBAN_REQUEST_NOTE]: null,
277
+ [ADDRESS_FIELDS.UNBAN_REQUESTED_AT]: null,
278
+ [ADDRESS_FIELDS.UPDATED_AT]: now,
279
+ });
280
+ }
160
281
  async clearDefaultFlag(ownerType, ownerId) {
161
282
  try {
162
283
  const snapshot = await this.getCollection()
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
11
11
  export type AddressOwnerType = "user" | "store";
12
+ export type AddressBanStatus = "banned" | "unban_requested" | "suspicious";
12
13
  export interface AddressDocument extends BaseDocument {
13
14
  ownerType: AddressOwnerType;
14
15
  ownerId: string;
@@ -23,12 +24,22 @@ export interface AddressDocument extends BaseDocument {
23
24
  postalCode: string;
24
25
  country: string;
25
26
  isDefault: boolean;
27
+ /** SHA-256(normalised postalCode|line1|line2) — stored unencrypted for cross-account dedup. */
28
+ addressHash?: string;
29
+ banStatus?: AddressBanStatus;
30
+ banReason?: string;
31
+ bannedBy?: string;
32
+ bannedAt?: Date;
33
+ /** true when cascaded from a user hard-ban (vs manually banned by admin). */
34
+ autoBanned?: boolean;
35
+ unbanRequestNote?: string;
36
+ unbanRequestedAt?: Date;
26
37
  }
27
38
  export declare const ADDRESSES_COLLECTION: "addresses";
28
- export declare const ADDRESS_INDEXED_FIELDS: readonly ["ownerType", "ownerId", "isDefault", "createdAt"];
39
+ export declare const ADDRESS_INDEXED_FIELDS: readonly ["ownerType", "ownerId", "isDefault", "addressHash", "banStatus", "autoBanned", "createdAt"];
29
40
  export declare const DEFAULT_ADDRESS_DATA: Partial<AddressDocument>;
30
- export declare const ADDRESS_PUBLIC_FIELDS: readonly ["id", "ownerType", "ownerId", "label", "fullName", "phone", "addressLine1", "addressLine2", "landmark", "city", "state", "postalCode", "country", "isDefault", "createdAt", "updatedAt"];
31
- export declare const ADDRESS_UPDATABLE_FIELDS: readonly ["label", "fullName", "phone", "addressLine1", "addressLine2", "landmark", "city", "state", "postalCode", "country", "isDefault"];
41
+ export declare const ADDRESS_PUBLIC_FIELDS: readonly ["id", "ownerType", "ownerId", "label", "fullName", "phone", "addressLine1", "addressLine2", "landmark", "city", "state", "postalCode", "country", "isDefault", "addressHash", "banStatus", "banReason", "bannedAt", "autoBanned", "unbanRequestNote", "unbanRequestedAt", "createdAt", "updatedAt"];
42
+ export declare const ADDRESS_UPDATABLE_FIELDS: readonly ["label", "fullName", "phone", "addressLine1", "addressLine2", "landmark", "city", "state", "postalCode", "country", "isDefault", "banStatus", "banReason", "bannedBy", "bannedAt", "autoBanned", "unbanRequestNote", "unbanRequestedAt"];
32
43
  export type AddressCreateInput = Omit<AddressDocument, "id" | "ownerType" | "ownerId" | "createdAt" | "updatedAt">;
33
44
  export type AddressUpdateInput = Partial<Pick<AddressDocument, (typeof ADDRESS_UPDATABLE_FIELDS)[number]>>;
34
45
  export declare const ADDRESS_FIELDS: {
@@ -46,6 +57,14 @@ export declare const ADDRESS_FIELDS: {
46
57
  readonly POSTAL_CODE: "postalCode";
47
58
  readonly COUNTRY: "country";
48
59
  readonly IS_DEFAULT: "isDefault";
60
+ readonly ADDRESS_HASH: "addressHash";
61
+ readonly BAN_STATUS: "banStatus";
62
+ readonly BAN_REASON: "banReason";
63
+ readonly BANNED_BY: "bannedBy";
64
+ readonly BANNED_AT: "bannedAt";
65
+ readonly AUTO_BANNED: "autoBanned";
66
+ readonly UNBAN_REQUEST_NOTE: "unbanRequestNote";
67
+ readonly UNBAN_REQUESTED_AT: "unbanRequestedAt";
49
68
  readonly CREATED_AT: "createdAt";
50
69
  readonly UPDATED_AT: "updatedAt";
51
70
  };
@@ -12,6 +12,9 @@ export const ADDRESS_INDEXED_FIELDS = [
12
12
  "ownerType",
13
13
  "ownerId",
14
14
  "isDefault",
15
+ "addressHash",
16
+ "banStatus",
17
+ "autoBanned",
15
18
  "createdAt",
16
19
  ];
17
20
  export const DEFAULT_ADDRESS_DATA = {
@@ -32,6 +35,13 @@ export const ADDRESS_PUBLIC_FIELDS = [
32
35
  "postalCode",
33
36
  "country",
34
37
  "isDefault",
38
+ "addressHash",
39
+ "banStatus",
40
+ "banReason",
41
+ "bannedAt",
42
+ "autoBanned",
43
+ "unbanRequestNote",
44
+ "unbanRequestedAt",
35
45
  "createdAt",
36
46
  "updatedAt",
37
47
  ];
@@ -47,6 +57,13 @@ export const ADDRESS_UPDATABLE_FIELDS = [
47
57
  "postalCode",
48
58
  "country",
49
59
  "isDefault",
60
+ "banStatus",
61
+ "banReason",
62
+ "bannedBy",
63
+ "bannedAt",
64
+ "autoBanned",
65
+ "unbanRequestNote",
66
+ "unbanRequestedAt",
50
67
  ];
51
68
  export const ADDRESS_FIELDS = {
52
69
  ID: "id",
@@ -63,6 +80,14 @@ export const ADDRESS_FIELDS = {
63
80
  POSTAL_CODE: "postalCode",
64
81
  COUNTRY: "country",
65
82
  IS_DEFAULT: "isDefault",
83
+ ADDRESS_HASH: "addressHash",
84
+ BAN_STATUS: "banStatus",
85
+ BAN_REASON: "banReason",
86
+ BANNED_BY: "bannedBy",
87
+ BANNED_AT: "bannedAt",
88
+ AUTO_BANNED: "autoBanned",
89
+ UNBAN_REQUEST_NOTE: "unbanRequestNote",
90
+ UNBAN_REQUESTED_AT: "unbanRequestedAt",
66
91
  CREATED_AT: "createdAt",
67
92
  UPDATED_AT: "updatedAt",
68
93
  };
@@ -41,13 +41,13 @@ export declare const addressFirestoreSchema: z.ZodObject<{
41
41
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
42
42
  }, z.ZodTypeAny, "passthrough">;
43
43
  label: string;
44
+ isDefault: boolean;
44
45
  state: string;
45
46
  city: string;
46
47
  addressLine1: string;
47
48
  fullName: string;
48
49
  ownerType: "user" | "store";
49
50
  ownerId: string;
50
- isDefault: boolean;
51
51
  postalCode: string;
52
52
  addressLine2?: string | undefined;
53
53
  landmark?: string | undefined;
@@ -62,13 +62,13 @@ export declare const addressFirestoreSchema: z.ZodObject<{
62
62
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
63
63
  }, z.ZodTypeAny, "passthrough">;
64
64
  label: string;
65
+ isDefault: boolean;
65
66
  state: string;
66
67
  city: string;
67
68
  addressLine1: string;
68
69
  fullName: string;
69
70
  ownerType: "user" | "store";
70
71
  ownerId: string;
71
- isDefault: boolean;
72
72
  postalCode: string;
73
73
  addressLine2?: string | undefined;
74
74
  landmark?: string | undefined;
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export interface AdminAddressClustersViewProps {
3
+ children?: React.ReactNode;
4
+ }
5
+ export declare function AdminAddressClustersView(_props: AdminAddressClustersViewProps): import("react/jsx-runtime").JSX.Element;