@mohasinac/appkit 4.11.3 → 4.12.1

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 (160) hide show
  1. package/dist/_internal/server/features/auctions/service.d.ts +7 -1
  2. package/dist/_internal/server/features/auctions/service.js +13 -5
  3. package/dist/_internal/server/features/checkout/actions.d.ts +36 -18
  4. package/dist/_internal/server/features/checkout/actions.js +127 -45
  5. package/dist/_internal/server/features/orders/actions.js +5 -3
  6. package/dist/_internal/server/features/orders/adapters.js +13 -0
  7. package/dist/_internal/server/features/products/list-public.d.ts +28 -0
  8. package/dist/_internal/server/features/products/list-public.js +92 -0
  9. package/dist/_internal/server/functions/firestore.d.ts +1 -2
  10. package/dist/_internal/server/functions/firestore.js +1 -9
  11. package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
  12. package/dist/_internal/server/jobs/core/index.d.ts +0 -1
  13. package/dist/_internal/server/jobs/core/index.js +0 -1
  14. package/dist/_internal/server/jobs/core/offerExpiry.js +33 -1
  15. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -2
  16. package/dist/_internal/server/jobs/handlers/index.js +1 -2
  17. package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
  18. package/dist/_internal/shared/checkout/lanes.js +28 -0
  19. package/dist/_internal/shared/features/auctions/config.d.ts +34 -0
  20. package/dist/_internal/shared/features/auctions/config.js +47 -3
  21. package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
  22. package/dist/_internal/shared/features/checkout/config.js +8 -0
  23. package/dist/_internal/shared/fees/calculator.d.ts +34 -2
  24. package/dist/_internal/shared/fees/calculator.js +45 -1
  25. package/dist/_internal/shared/listing-types/auction/config.js +1 -1
  26. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
  27. package/dist/_internal/shared/listing-types/live/config.js +1 -1
  28. package/dist/_internal/shared/listing-types/pre-order/config.js +1 -1
  29. package/dist/client.d.ts +7 -1
  30. package/dist/client.js +4 -1
  31. package/dist/constants/api-endpoints.d.ts +6 -0
  32. package/dist/constants/api-endpoints.js +2 -0
  33. package/dist/features/account/components/NotificationBell.js +1 -1
  34. package/dist/features/admin/components/AdminAddressesView.js +18 -10
  35. package/dist/features/admin/components/AdminArtView.d.ts +9 -0
  36. package/dist/features/admin/components/AdminArtView.js +11 -35
  37. package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
  38. package/dist/features/admin/components/AdminClassifiedView.js +11 -35
  39. package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
  40. package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
  41. package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
  42. package/dist/features/admin/components/AdminLiveView.js +11 -41
  43. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  44. package/dist/features/admin/components/AdminOrderEditorView.d.ts +14 -1
  45. package/dist/features/admin/components/AdminOrderEditorView.js +18 -5
  46. package/dist/features/admin/components/AdminOrdersView.js +15 -1
  47. package/dist/features/admin/components/AdminPaymentMethodsView.js +15 -10
  48. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  49. package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
  50. package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
  51. package/dist/features/admin/components/AdminStickersView.js +11 -35
  52. package/dist/features/admin/hooks/useAdminListing.js +7 -2
  53. package/dist/features/admin/schemas/firestore.d.ts +5 -0
  54. package/dist/features/admin/schemas/firestore.js +1 -0
  55. package/dist/features/auctions/actions/bid-actions.js +73 -11
  56. package/dist/features/auctions/components/AuctionDetailPageView.js +8 -2
  57. package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
  58. package/dist/features/auctions/components/PlaceBidFormClient.js +89 -14
  59. package/dist/features/auctions/schemas/bid-input.d.ts +19 -6
  60. package/dist/features/auctions/schemas/bid-input.js +19 -7
  61. package/dist/features/auth/repository/user.repository.js +6 -0
  62. package/dist/features/cart/components/CartDrawer.d.ts +9 -1
  63. package/dist/features/cart/components/CartDrawer.js +4 -3
  64. package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
  65. package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
  66. package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
  67. package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
  68. package/dist/features/cart/components/index.d.ts +4 -0
  69. package/dist/features/cart/components/index.js +2 -0
  70. package/dist/features/cart/repository/cart.repository.d.ts +20 -2
  71. package/dist/features/cart/repository/cart.repository.js +49 -0
  72. package/dist/features/cart/schemas/firestore.d.ts +26 -0
  73. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  74. package/dist/features/filters/FilterFacetSection.js +1 -1
  75. package/dist/features/homepage/components/WhatsAppCommunitySection.js +1 -1
  76. package/dist/features/layout/BackToTop.js +1 -1
  77. package/dist/features/layout/BottomActions.js +38 -5
  78. package/dist/features/layout/BottomActionsContext.d.ts +12 -0
  79. package/dist/features/layout/BottomActionsContext.js +5 -0
  80. package/dist/features/layout/FooterLayout.js +2 -2
  81. package/dist/features/layout/TitleBarLayout.js +1 -1
  82. package/dist/features/layout/hooks/useBottomActions.d.ts +6 -0
  83. package/dist/features/layout/hooks/useBottomActions.js +11 -1
  84. package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
  85. package/dist/features/orders/components/OrderAddonBadges.js +28 -0
  86. package/dist/features/orders/components/index.d.ts +1 -0
  87. package/dist/features/orders/components/index.js +1 -0
  88. package/dist/features/orders/repository/orders.repository.d.ts +32 -0
  89. package/dist/features/orders/repository/orders.repository.js +14 -0
  90. package/dist/features/orders/types/index.d.ts +22 -0
  91. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  92. package/dist/features/products/components/ArtStickersListView.js +5 -4
  93. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  94. package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
  95. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  96. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  97. package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
  98. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  99. package/dist/features/products/components/ProductGrid.js +3 -3
  100. package/dist/features/products/components/ProductsIndexListing.js +87 -11
  101. package/dist/features/products/components/ProductsIndexPageView.js +14 -2
  102. package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
  103. package/dist/features/products/config/listing-type-listing-config.js +74 -0
  104. package/dist/features/products/hooks/useProducts.js +14 -0
  105. package/dist/features/products/repository/products.repository.d.ts +0 -4
  106. package/dist/features/products/repository/products.repository.js +0 -15
  107. package/dist/features/products/types/index.d.ts +15 -0
  108. package/dist/features/promotions/repository/coupons.repository.d.ts +12 -0
  109. package/dist/features/promotions/repository/coupons.repository.js +12 -0
  110. package/dist/features/seller/components/SellerOrdersView.js +2 -1
  111. package/dist/features/seller/components/SellerSidebar.js +1 -1
  112. package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
  113. package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
  114. package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
  115. package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
  116. package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
  117. package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
  118. package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
  119. package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
  120. package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
  121. package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
  122. package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
  123. package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
  124. package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
  125. package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
  126. package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
  127. package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
  128. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
  129. package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
  130. package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
  131. package/dist/features/stores/components/StoreProductsListing.js +3 -2
  132. package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
  133. package/dist/features/stores/components/StoreProductsPageView.js +16 -21
  134. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +875 -0
  135. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +1 -1
  136. package/dist/jobs.d.ts +1 -1
  137. package/dist/jobs.js +1 -1
  138. package/dist/next/routing/route-map.d.ts +2 -0
  139. package/dist/next/routing/route-map.js +5 -0
  140. package/dist/seed/homepage-sections-seed-data.js +5 -1
  141. package/dist/seed/reviews-seed-data.js +52 -0
  142. package/dist/seed/site-settings-seed-data.js +18 -0
  143. package/dist/server-entry.d.ts +1 -0
  144. package/dist/server-entry.js +1 -0
  145. package/dist/styles.css +66 -1
  146. package/dist/tailwind-utilities.css +1 -1
  147. package/dist/tokens/color-pairs.js +1 -0
  148. package/dist/tokens/tokens.css +33 -0
  149. package/dist/ui/components/Button.style.css +21 -0
  150. package/dist/ui/components/Checkbox.d.ts +10 -1
  151. package/dist/ui/components/Checkbox.js +14 -2
  152. package/dist/ui/components/Checkbox.style.css +11 -0
  153. package/dist/ui/components/ImageLightbox.js +7 -4
  154. package/dist/ui/components/surface-tokens.d.ts +7 -0
  155. package/dist/ui/components/surface-tokens.js +7 -0
  156. package/package.json +1 -1
  157. package/dist/_internal/server/jobs/core/onBidPlaced.d.ts +0 -15
  158. package/dist/_internal/server/jobs/core/onBidPlaced.js +0 -63
  159. package/dist/_internal/server/jobs/handlers/onBidPlaced.d.ts +0 -3
  160. package/dist/_internal/server/jobs/handlers/onBidPlaced.js +0 -9
package/dist/client.d.ts CHANGED
@@ -207,7 +207,9 @@ export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, Registe
207
207
  export type { AuthGuardUser, ForgotPasswordViewProps, LoginFormProps, LoginFormValues, RegisterFormProps, RegisterFormValues, ResetPasswordViewProps, VerifyEmailViewProps, } from "./features/auth/index";
208
208
  export { useLogout, useLogin, useGoogleLogin, useLinkGoogleAccount, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangeEmail } from "./features/auth/index";
209
209
  export type { LoginCredentials, RegisterData, ForgotPasswordData, ResetPasswordData, VerifyEmailData, ChangeEmailData } from "./features/auth/index";
210
- export { CartView, CartItemRow, CartSummary, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
210
+ export { CartView, CartItemRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
211
+ export { StoreAddonsPicker } from "./features/cart/index";
212
+ export type { CartPriceBreakdownData, CartPriceBreakdownStore, StoreAddonsValue, StoreAddonsRates } from "./features/cart/index";
211
213
  export { getCartOps, CART_OPS_CHANGE_EVENT } from "./features/cart/utils/pending-ops";
212
214
  export type { CartOp } from "./features/cart/utils/pending-ops";
213
215
  export type { CartItem, CartItemMeta, CartData, GuestCartItem } from "./features/cart/index";
@@ -300,6 +302,10 @@ export type { UserAccountHubViewProps, UserAccountHubViewLabels, UserOrdersViewP
300
302
  export { useOrders, useOrder, OrdersList } from "./features/orders/index";
301
303
  export { OrderStatusTimeline } from "./features/orders/components/OrderStatusTimeline";
302
304
  export type { OrderStatusTimelineProps } from "./features/orders/components/OrderStatusTimeline";
305
+ export { CountdownDisplay } from "./ui/components/CountdownDisplay";
306
+ export type { CountdownDisplayProps } from "./ui/components/CountdownDisplay";
307
+ export { OrderAddonBadges } from "./features/orders/components/OrderAddonBadges";
308
+ export type { OrderAddonBadgesOrder, OrderAddonBadgesProps } from "./features/orders/components/OrderAddonBadges";
303
309
  export { MANUAL_PAYMENT_METHODS, isManualPaymentMethod, PAYMENT_REVIEW_QUEUE_MODES, isPaymentReviewQueueMode, PAYMENT_WINDOW_MINUTES, } from "./features/orders/constants/payment-window";
304
310
  export type { PaymentReviewQueueMode } from "./features/orders/constants/payment-window";
305
311
  export { UserOrderTrackView } from "./features/account/components/UserOrderTrackView";
package/dist/client.js CHANGED
@@ -238,7 +238,8 @@ export { PAGE_VIEW_ENTITY_TYPES } from "./features/analytics/types";
238
238
  export { AuctionBidsTable } from "./features/auctions/components/AuctionBidsTable";
239
239
  export { ProtectedRoute, AuthStatusPanel, ForgotPasswordView, LoginForm, RegisterForm, ResetPasswordView, VerifyEmailView } from "./features/auth/index";
240
240
  export { useLogout, useLogin, useGoogleLogin, useLinkGoogleAccount, useRegister, useForgotPassword, useResetPassword, useVerifyEmail, useChangeEmail } from "./features/auth/index";
241
- export { CartView, CartItemRow, CartSummary, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
241
+ export { CartView, CartItemRow, CartSummary, CartPriceBreakdown, CartDrawer, CheckoutView, CheckoutSuccessView, CheckoutAddressStep, useGuestCart, useCartCount, useAddToCart, useCart, useGuestCartMerge, useCartQuery } from "./features/cart/index";
242
+ export { StoreAddonsPicker } from "./features/cart/index";
242
243
  export { getCartOps, CART_OPS_CHANGE_EVENT } from "./features/cart/utils/pending-ops";
243
244
  export { useAddresses, useCreateAddress, useUpdateAddress, useDeleteAddress, useSetDefaultAddress, useAddress } from "./features/account/index";
244
245
  export { AddressBook, AddressCard, AddressForm } from "./features/account/index";
@@ -301,6 +302,8 @@ export { SellerPayoutSettingsView, SellerShippingView, SellerReviewsView, Seller
301
302
  export { UserAccountHubView, UserOrdersView, UserBidsView, OrderDetailView, UserNotificationsView, UserReturnsView, UserSupportView, useNotifications } from "./features/account/index";
302
303
  export { useOrders, useOrder, OrdersList } from "./features/orders/index";
303
304
  export { OrderStatusTimeline } from "./features/orders/components/OrderStatusTimeline";
305
+ export { CountdownDisplay } from "./ui/components/CountdownDisplay";
306
+ export { OrderAddonBadges } from "./features/orders/components/OrderAddonBadges";
304
307
  // Manual-payment (cash / UPI / EMI) shared constants — pure data + predicates,
305
308
  // no server deps. Used by the buyer proof-upload page and order-detail CTAs.
306
309
  export { MANUAL_PAYMENT_METHODS, isManualPaymentMethod, PAYMENT_REVIEW_QUEUE_MODES, isPaymentReviewQueueMode, PAYMENT_WINDOW_MINUTES, } from "./features/orders/constants/payment-window";
@@ -234,6 +234,8 @@ export declare const CART_ENDPOINTS: {
234
234
  readonly BY_ITEM_ID: (itemId: string) => string;
235
235
  readonly VALIDATE: "/api/cart/validate";
236
236
  readonly SELECTION: "/api/cart/selection";
237
+ /** Per-store paid add-on selections — the source of truth for what's charged. */
238
+ readonly ADDONS: "/api/cart/addons";
237
239
  readonly COUPON: "/api/cart/coupon";
238
240
  };
239
241
  export declare const CATEGORY_ENDPOINTS: {
@@ -686,6 +688,8 @@ export declare const API_ENDPOINTS: {
686
688
  readonly BY_ITEM_ID: (itemId: string) => string;
687
689
  readonly VALIDATE: "/api/cart/validate";
688
690
  readonly SELECTION: "/api/cart/selection";
691
+ /** Per-store paid add-on selections — the source of truth for what's charged. */
692
+ readonly ADDONS: "/api/cart/addons";
689
693
  readonly COUPON: "/api/cart/coupon";
690
694
  };
691
695
  readonly CATEGORIES: {
@@ -1140,6 +1144,8 @@ export declare const API_ROUTES: {
1140
1144
  readonly BY_ITEM_ID: (itemId: string) => string;
1141
1145
  readonly VALIDATE: "/api/cart/validate";
1142
1146
  readonly SELECTION: "/api/cart/selection";
1147
+ /** Per-store paid add-on selections — the source of truth for what's charged. */
1148
+ readonly ADDONS: "/api/cart/addons";
1143
1149
  readonly COUPON: "/api/cart/coupon";
1144
1150
  };
1145
1151
  readonly CATEGORIES: {
@@ -274,6 +274,8 @@ export const CART_ENDPOINTS = {
274
274
  BY_ITEM_ID: (itemId) => `/api/cart/${encodeURIComponent(itemId)}`,
275
275
  VALIDATE: "/api/cart/validate",
276
276
  SELECTION: "/api/cart/selection",
277
+ /** Per-store paid add-on selections — the source of truth for what's charged. */
278
+ ADDONS: "/api/cart/addons",
277
279
  COUPON: "/api/cart/coupon",
278
280
  };
279
281
  // ---------------------------------------------------------------------------
@@ -10,7 +10,7 @@ const __O = {
10
10
  hidden: "overflow-hidden",
11
11
  yAuto: "overflow-y-auto",
12
12
  };
13
- const CLS_UNREAD_BADGE = "absolute -top-1 -right-1 bg-error-surface text-white min-w-[20px] h-5 px-[var(--appkit-space-1-5)] flex items-center justify-center rounded-full shadow-md";
13
+ const CLS_UNREAD_BADGE = "absolute -top-1 -right-1 bg-error-solid text-error-on-solid min-w-[20px] h-5 px-[var(--appkit-space-1-5)] flex items-center justify-center rounded-full shadow-md";
14
14
  const CLS_UNREAD_PILL = "ml-2 bg-error-surface text-error dark:bg-error-surface dark:text-error px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] rounded-full";
15
15
  const DEFAULT_ICONS = {
16
16
  order_placed: "🛍️",
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { sortBy } from "@mohasinac/appkit/client";
4
- import { useState } from "react";
4
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
5
  import { useQueryClient } from "@tanstack/react-query";
6
6
  import { Badge, Div, FilterChipGroup, Span, Stack, Text, useToast } from "../../../ui";
7
7
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -47,18 +47,22 @@ const ADDRESS_COLUMNS = [
47
47
  render: (row) => _jsx(Span, { size: "sm", color: "muted", children: row.updatedAt }),
48
48
  },
49
49
  ];
50
- function buildAddressConfig(banStatus, onAction) {
50
+ function buildAddressConfig(onAction) {
51
51
  return {
52
52
  portal: "admin",
53
53
  title: "Address Management",
54
54
  searchPlaceholder: "Search by city or owner",
55
55
  emptyLabel: "No addresses found",
56
- filterKeys: [],
56
+ // URL-synced via filterKeys (2026-08-21). The ban-status chips used to
57
+ // live outside this config in local useState, so the selection was not in
58
+ // the URL, not shareable, not counted by activeFilterCount, and lost on
59
+ // back-navigation. banStatus is a plain query param rather than a Sieve
60
+ // clause, which is exactly what buildExtraParams is for.
61
+ filterKeys: ["banStatus"],
57
62
  defaultSort: sortBy("bannedAt", "DESC"),
58
- queryKey: ["admin", "addresses", banStatus],
59
- endpoint: banStatus
60
- ? `${ADMIN_ENDPOINTS.ADDRESSES}?banStatus=${encodeURIComponent(banStatus)}`
61
- : ADMIN_ENDPOINTS.ADDRESSES,
63
+ queryKey: ["admin", "addresses"],
64
+ endpoint: ADMIN_ENDPOINTS.ADDRESSES,
65
+ buildExtraParams: (state) => state.banStatus ? { banStatus: state.banStatus } : undefined,
62
66
  sortOptions: [
63
67
  // -bannedAt (newest flagged first) to match defaultSort — the option was
64
68
  // "bannedAt" (oldest first), so the dropdown opened with nothing selected.
@@ -121,7 +125,11 @@ function buildAddressConfig(banStatus, onAction) {
121
125
  };
122
126
  }
123
127
  export function AdminAddressesView(_props) {
124
- const [banStatus, setBanStatus] = useState("");
128
+ // Independent useUrlTable() against the same URL param DataListingView's own
129
+ // table reads via filterKeys — safe because useUrlTable holds no local state
130
+ // (the documented Root Cause #35 pattern).
131
+ const chipTable = useUrlTable({ defaults: {} });
132
+ const banStatus = chipTable.get("banStatus");
125
133
  const queryClient = useQueryClient();
126
134
  const { showToast } = useToast();
127
135
  const actionMutation = useApiMutation({
@@ -139,6 +147,6 @@ export function AdminAddressesView(_props) {
139
147
  const handleAction = (id, action) => {
140
148
  actionMutation.mutate({ id, action });
141
149
  };
142
- const config = buildAddressConfig(banStatus, handleAction);
143
- 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 })] }));
150
+ const config = buildAddressConfig(handleAction);
151
+ 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) => chipTable.set("banStatus", v) }) }), _jsx(DataListingView, { config: config })] }));
144
152
  }
@@ -1,3 +1,12 @@
1
+ /**
2
+ * AdminArtView — admin browse of art listings.
3
+ *
4
+ * A thin wrapper over the shared per-listing-type config. This file (and its
5
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
6
+ * were the same config with a different listingType, and all five shipped
7
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
8
+ * of a three-option sort array. See listing-type-listing-config.ts.
9
+ */
1
10
  import React from "react";
2
11
  import type { ListingLayoutProps } from "../../../ui";
3
12
  export type AdminArtViewProps = ListingLayoutProps;
@@ -1,50 +1,26 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
- * AdminArtView — admin browse of art print listings (EMI/art-stickers session).
5
- * Mirrors AdminClassifiedView's thin config-driven pattern.
4
+ * AdminArtView — admin browse of art listings.
5
+ *
6
+ * A thin wrapper over the shared per-listing-type config. This file (and its
7
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
8
+ * were the same config with a different listingType, and all five shipped
9
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
10
+ * of a three-option sort array. See listing-type-listing-config.ts.
6
11
  */
7
- import { sortBy } from "@mohasinac/appkit/client";
8
12
  import React from "react";
9
13
  import { ListingLayout } from "../../../ui";
10
- import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
- import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
14
+ import { buildListingTypeListingConfig } from "../../products/config/listing-type-listing-config";
12
15
  import { DataListingView } from "./DataListingView";
13
- import { ROUTES } from "../../../next/routing/route-map";
14
- const ADMIN_ART_CONFIG = {
15
- portal: "admin",
16
+ const CONFIG = buildListingTypeListingConfig("art", {
16
17
  title: "Art",
17
18
  searchPlaceholder: "Search art prints by name or seller",
18
19
  emptyLabel: "No art listings",
19
- filterKeys: [],
20
- defaultSort: sortBy("createdAt", "DESC"),
21
- queryKey: ["admin", "art", "listing"],
22
- endpoint: ADMIN_ENDPOINTS.PRODUCTS,
23
- sortOptions: [
24
- { value: sortBy("createdAt", "DESC"), label: "Newest" },
25
- { value: sortBy("createdAt", "ASC"), label: "Oldest" },
26
- { value: "title", label: "Title A–Z" },
27
- ],
28
- mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
29
- id: toStringValue(item.id, `art-${index}`),
30
- primary: toStringValue(item.title ?? item.productTitle, "Untitled print"),
31
- secondary: [
32
- toStringValue(item.sellerName, "Unknown seller"),
33
- toCurrency(item.price),
34
- ].join(" · "),
35
- status: toStringValue(item.status, "draft"),
36
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
37
- image: toStringValue(item.mainImage, "") || undefined,
38
- })),
39
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
40
- buildFilters: () => "listingType==art",
41
- // Art listings are products (filtered by listingType) — reuse the real
42
- // admin product edit page rather than leaving rows non-navigable.
43
- rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
44
- };
20
+ });
45
21
  export function AdminArtView({ children, ...props }) {
46
22
  if (React.Children.count(children) > 0) {
47
23
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
48
24
  }
49
- return _jsx(DataListingView, { config: ADMIN_ART_CONFIG });
25
+ return _jsx(DataListingView, { config: CONFIG });
50
26
  }
@@ -1,3 +1,12 @@
1
+ /**
2
+ * AdminClassifiedView — admin browse of classified listings.
3
+ *
4
+ * A thin wrapper over the shared per-listing-type config. This file (and its
5
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
6
+ * were the same config with a different listingType, and all five shipped
7
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
8
+ * of a three-option sort array. See listing-type-listing-config.ts.
9
+ */
1
10
  import React from "react";
2
11
  import type { ListingLayoutProps } from "../../../ui";
3
12
  export type AdminClassifiedViewProps = ListingLayoutProps;
@@ -1,50 +1,26 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
- * AdminClassifiedView — admin browse of classified listings (W1-29).
5
- * Read-only summary with edit-handoff to the seller-portal edit route.
4
+ * AdminClassifiedView — admin browse of classified listings.
5
+ *
6
+ * A thin wrapper over the shared per-listing-type config. This file (and its
7
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
8
+ * were the same config with a different listingType, and all five shipped
9
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
10
+ * of a three-option sort array. See listing-type-listing-config.ts.
6
11
  */
7
- import { sortBy } from "@mohasinac/appkit/client";
8
12
  import React from "react";
9
13
  import { ListingLayout } from "../../../ui";
10
- import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
- import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
14
+ import { buildListingTypeListingConfig } from "../../products/config/listing-type-listing-config";
12
15
  import { DataListingView } from "./DataListingView";
13
- import { ROUTES } from "../../../next/routing/route-map";
14
- const ADMIN_CLASSIFIED_CONFIG = {
15
- portal: "admin",
16
+ const CONFIG = buildListingTypeListingConfig("classified", {
16
17
  title: "Classified",
17
18
  searchPlaceholder: "Search classified by name or seller",
18
19
  emptyLabel: "No classified listings",
19
- filterKeys: [],
20
- defaultSort: sortBy("createdAt", "DESC"),
21
- queryKey: ["admin", "classified", "listing"],
22
- endpoint: ADMIN_ENDPOINTS.PRODUCTS,
23
- sortOptions: [
24
- { value: sortBy("createdAt", "DESC"), label: "Newest" },
25
- { value: sortBy("createdAt", "ASC"), label: "Oldest" },
26
- { value: "title", label: "Title A–Z" },
27
- ],
28
- mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
29
- id: toStringValue(item.id, `classified-${index}`),
30
- primary: toStringValue(item.title ?? item.productTitle, "Untitled classified"),
31
- secondary: [
32
- toStringValue(item.sellerName, "Unknown seller"),
33
- toCurrency(item.price),
34
- ].join(" · "),
35
- status: toStringValue(item.status, "draft"),
36
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
37
- image: toStringValue(item.mainImage, "") || undefined,
38
- })),
39
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
40
- buildFilters: () => "listingType==classified",
41
- // Classified listings are products (filtered by listingType) — reuse the
42
- // real admin product edit page rather than leaving rows non-navigable.
43
- rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
44
- };
20
+ });
45
21
  export function AdminClassifiedView({ children, ...props }) {
46
22
  if (React.Children.count(children) > 0) {
47
23
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
48
24
  }
49
- return _jsx(DataListingView, { config: ADMIN_CLASSIFIED_CONFIG });
25
+ return _jsx(DataListingView, { config: CONFIG });
50
26
  }
@@ -1,3 +1,12 @@
1
+ /**
2
+ * AdminDigitalCodesView — admin browse of digital codes listings.
3
+ *
4
+ * A thin wrapper over the shared per-listing-type config. This file (and its
5
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
6
+ * were the same config with a different listingType, and all five shipped
7
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
8
+ * of a three-option sort array. See listing-type-listing-config.ts.
9
+ */
1
10
  import React from "react";
2
11
  import type { ListingLayoutProps } from "../../../ui";
3
12
  export type AdminDigitalCodesViewProps = ListingLayoutProps;
@@ -1,57 +1,26 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
- * AdminDigitalCodesView — admin browse of digital-code listings (W1-29).
4
+ * AdminDigitalCodesView — admin browse of digital codes listings.
5
+ *
6
+ * A thin wrapper over the shared per-listing-type config. This file (and its
7
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
8
+ * were the same config with a different listingType, and all five shipped
9
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
10
+ * of a three-option sort array. See listing-type-listing-config.ts.
5
11
  */
6
- import { sortBy } from "@mohasinac/appkit/client";
7
12
  import React from "react";
8
13
  import { ListingLayout } from "../../../ui";
9
- import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
- import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
14
+ import { buildListingTypeListingConfig } from "../../products/config/listing-type-listing-config";
11
15
  import { DataListingView } from "./DataListingView";
12
- import { ROUTES } from "../../../next/routing/route-map";
13
- const ADMIN_DIGITAL_CODES_CONFIG = {
14
- portal: "admin",
16
+ const CONFIG = buildListingTypeListingConfig("digital-code", {
15
17
  title: "Digital Codes",
16
18
  searchPlaceholder: "Search digital code listings",
17
19
  emptyLabel: "No digital code listings",
18
- filterKeys: [],
19
- defaultSort: sortBy("createdAt", "DESC"),
20
- queryKey: ["admin", "digital-codes", "listing"],
21
- endpoint: ADMIN_ENDPOINTS.PRODUCTS,
22
- sortOptions: [
23
- { value: sortBy("createdAt", "DESC"), label: "Newest" },
24
- { value: sortBy("createdAt", "ASC"), label: "Oldest" },
25
- { value: "title", label: "Title A–Z" },
26
- ],
27
- mapRows: (response) => toRecordArray(response.items).map((item, index) => {
28
- const dc = (item.digitalCode ?? {});
29
- return {
30
- id: toStringValue(item.id, `dc-${index}`),
31
- primary: toStringValue(item.title ?? item.productTitle, "Untitled digital code"),
32
- secondary: [
33
- toStringValue(item.sellerName, "Unknown seller"),
34
- toCurrency(item.price),
35
- typeof dc.codesAvailable === "number"
36
- ? `${dc.codesAvailable} avail`
37
- : null,
38
- ]
39
- .filter(Boolean)
40
- .join(" · "),
41
- status: toStringValue(item.status, "draft"),
42
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
43
- image: toStringValue(item.mainImage, "") || undefined,
44
- };
45
- }),
46
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
47
- buildFilters: () => "listingType==digital-code",
48
- // Digital-code listings are products (filtered by listingType) — reuse
49
- // the real admin product edit page rather than leaving rows non-navigable.
50
- rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
51
- };
20
+ });
52
21
  export function AdminDigitalCodesView({ children, ...props }) {
53
22
  if (React.Children.count(children) > 0) {
54
23
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
55
24
  }
56
- return _jsx(DataListingView, { config: ADMIN_DIGITAL_CODES_CONFIG });
25
+ return _jsx(DataListingView, { config: CONFIG });
57
26
  }
@@ -1,3 +1,12 @@
1
+ /**
2
+ * AdminLiveView — admin browse of live items listings.
3
+ *
4
+ * A thin wrapper over the shared per-listing-type config. This file (and its
5
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
6
+ * were the same config with a different listingType, and all five shipped
7
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
8
+ * of a three-option sort array. See listing-type-listing-config.ts.
9
+ */
1
10
  import React from "react";
2
11
  import type { ListingLayoutProps } from "../../../ui";
3
12
  export type AdminLiveViewProps = ListingLayoutProps;
@@ -1,56 +1,26 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
- * AdminLiveView — admin browse of live-item listings (W1-29).
4
+ * AdminLiveView — admin browse of live items listings.
5
+ *
6
+ * A thin wrapper over the shared per-listing-type config. This file (and its
7
+ * four siblings) used to carry a full hand-written ListingViewConfig; all five
8
+ * were the same config with a different listingType, and all five shipped
9
+ * an empty filterKeys array — no filter drawer at all — plus their own copy
10
+ * of a three-option sort array. See listing-type-listing-config.ts.
5
11
  */
6
- import { sortBy } from "@mohasinac/appkit/client";
7
12
  import React from "react";
8
13
  import { ListingLayout } from "../../../ui";
9
- import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
10
- import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../hooks/useAdminListingData";
14
+ import { buildListingTypeListingConfig } from "../../products/config/listing-type-listing-config";
11
15
  import { DataListingView } from "./DataListingView";
12
- import { ROUTES } from "../../../next/routing/route-map";
13
- const ADMIN_LIVE_CONFIG = {
14
- portal: "admin",
16
+ const CONFIG = buildListingTypeListingConfig("live", {
15
17
  title: "Live Items",
16
18
  searchPlaceholder: "Search live items",
17
19
  emptyLabel: "No live item listings",
18
- filterKeys: [],
19
- defaultSort: sortBy("createdAt", "DESC"),
20
- queryKey: ["admin", "live-items", "listing"],
21
- endpoint: ADMIN_ENDPOINTS.PRODUCTS,
22
- sortOptions: [
23
- { value: sortBy("createdAt", "DESC"), label: "Newest" },
24
- { value: sortBy("createdAt", "ASC"), label: "Oldest" },
25
- { value: "title", label: "Title A–Z" },
26
- ],
27
- mapRows: (response) => toRecordArray(response.items).map((item, index) => {
28
- const live = (item.liveItem ?? {});
29
- return {
30
- id: toStringValue(item.id, `live-${index}`),
31
- primary: toStringValue(item.title ?? item.productTitle, "Untitled live item"),
32
- secondary: [
33
- toStringValue(item.sellerName, "Unknown seller"),
34
- toCurrency(item.price),
35
- toStringValue(live.species, ""),
36
- Boolean(live.vendorVerified) ? "verified" : "pending",
37
- ]
38
- .filter(Boolean)
39
- .join(" · "),
40
- status: toStringValue(item.status, "draft"),
41
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
42
- image: toStringValue(item.mainImage, "") || undefined,
43
- };
44
- }),
45
- getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
46
- buildFilters: () => "listingType==live",
47
- // Live-item listings are products (filtered by listingType) — reuse the
48
- // real admin product edit page rather than leaving rows non-navigable.
49
- rowHrefTemplate: String(ROUTES.ADMIN.PRODUCTS_EDIT("{id}")),
50
- };
20
+ });
51
21
  export function AdminLiveView({ children, ...props }) {
52
22
  if (React.Children.count(children) > 0) {
53
23
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
54
24
  }
55
- return _jsx(DataListingView, { config: ADMIN_LIVE_CONFIG });
25
+ return _jsx(DataListingView, { config: CONFIG });
56
26
  }
@@ -82,7 +82,10 @@ export function AdminNotificationsView({ children, ...props }) {
82
82
  const config = {
83
83
  portal: "admin",
84
84
  title: "Notifications",
85
- searchPlaceholder: "Search by title or user ID",
85
+ // Title search is not expressible here — `title` is not in the repository's
86
+ // SIEVE_FIELDS and Sieve→Firestore cannot OR across two fields. The route
87
+ // treats `q` as an exact userId lookup, so the placeholder says exactly that.
88
+ searchPlaceholder: "Search by user ID",
86
89
  emptyLabel: "No notifications found",
87
90
  filterKeys: ["type", "readState"],
88
91
  defaultSort: sortBy("createdAt", "DESC"),
@@ -1,3 +1,4 @@
1
+ import { type OrderAddonBadgesOrder } from "../../orders/components/OrderAddonBadges";
1
2
  import React from "react";
2
3
  export interface AdminOrderItemRow {
3
4
  productId: string;
@@ -23,5 +24,17 @@ export interface AdminOrderEditorViewProps {
23
24
  paymentUpiMismatch?: boolean;
24
25
  buyerMarkedPaid?: boolean;
25
26
  buyerFraudAgreementAccepted?: boolean;
27
+ /**
28
+ * Which way a prior review went, if any. Without this the panel cannot tell
29
+ * "awaiting verification" from "already rejected" and re-offers live
30
+ * Verify/Reject buttons on a decided order.
31
+ */
32
+ paymentReviewOutcome?: string;
33
+ /**
34
+ * Paid add-ons + applied coupon for this order. Operational, not decorative:
35
+ * `whatsappNotifyAddon` is who a status change should message, and the
36
+ * coupon is what an admin needs when reconciling a discounted order.
37
+ */
38
+ addons?: OrderAddonBadgesOrder;
26
39
  }
27
- export declare function AdminOrderEditorView({ open, onClose, orderId, orderLabel, currentStatus, items, paymentProofUrl, paymentTransactionId, paymentMethod, paymentStatus, displayedUpiId, buyerReportedUpiId, paymentUpiMismatch, buyerMarkedPaid, buyerFraudAgreementAccepted, }: AdminOrderEditorViewProps): React.JSX.Element;
40
+ export declare function AdminOrderEditorView({ open, onClose, orderId, orderLabel, currentStatus, items, paymentProofUrl, paymentTransactionId, paymentMethod, paymentStatus, displayedUpiId, buyerReportedUpiId, paymentUpiMismatch, buyerMarkedPaid, buyerFraudAgreementAccepted, paymentReviewOutcome, addons, }: AdminOrderEditorViewProps): React.JSX.Element;
@@ -1,5 +1,7 @@
1
1
  "use client";
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { OrderAddonBadges } from "../../orders/components/OrderAddonBadges";
4
+ import { isManualPaymentMethod } from "../../orders/constants/payment-window";
3
5
  import { useApiMutation } from "@mohasinac/appkit/client";
4
6
  import React, { useState } from "react";
5
7
  import { normalizeError } from "../../../errors/normalize";
@@ -29,7 +31,7 @@ const CARRIER_OPTIONS = [
29
31
  { label: "Other", value: "Other" },
30
32
  ];
31
33
  // --- Component ---------------------------------------------------------------
32
- export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, currentStatus, items, paymentProofUrl, paymentTransactionId, paymentMethod, paymentStatus, displayedUpiId, buyerReportedUpiId, paymentUpiMismatch, buyerMarkedPaid, buyerFraudAgreementAccepted, }) {
34
+ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, currentStatus, items, paymentProofUrl, paymentTransactionId, paymentMethod, paymentStatus, displayedUpiId, buyerReportedUpiId, paymentUpiMismatch, buyerMarkedPaid, buyerFraudAgreementAccepted, paymentReviewOutcome, addons, }) {
33
35
  const queryClient = useQueryClient();
34
36
  const { showToast } = useToast();
35
37
  const [status, setStatus] = React.useState(currentStatus ?? "pending");
@@ -41,8 +43,19 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
41
43
  const [reviewNote, setReviewNote] = React.useState("");
42
44
  const [isRequestingReupload, setIsRequestingReupload] = useState(false);
43
45
  const [isRejectingFraud, setIsRejectingFraud] = useState(false);
44
- const isCashOrUpi = paymentMethod === "cash" || paymentMethod === "upi_manual";
45
- const needsVerification = isCashOrUpi && paymentStatus === "pending";
46
+ // This used to inline `paymentMethod === "cash" || === "upi_manual"`, which
47
+ // omitted `emi` so the whole proof panel never rendered for an EMI order and
48
+ // an admin had no way to verify one, even though the list row said "Awaiting
49
+ // verification". Use the shared predicate, which is the single source of truth
50
+ // for the manual-payment set.
51
+ const isManualPayment = isManualPaymentMethod(paymentMethod ?? "");
52
+ const isVerified = paymentStatus === "paid";
53
+ const isRejected = paymentReviewOutcome === "rejected_fraud";
54
+ const isReuploadRequested = paymentReviewOutcome === "reupload_requested";
55
+ // Only an undecided, unpaid manual order is actionable. Gating on
56
+ // `paymentStatus === "pending"` alone re-offered live Verify/Reject buttons on
57
+ // an order that had already been rejected or sent back for re-upload.
58
+ const needsVerification = isManualPayment && !isVerified && !isRejected && Boolean(paymentProofUrl);
46
59
  React.useEffect(() => {
47
60
  if (open) {
48
61
  setStatus(currentStatus ?? "pending");
@@ -136,5 +149,5 @@ export function AdminOrderEditorView({ open, onClose, orderId, orderLabel, curre
136
149
  return (_jsx(SideDrawer, { isOpen: open, onClose: onClose, title: orderLabel ? `Order: ${orderLabel}` : "Update Order", children: _jsxs(Form, { onSubmit: (e) => {
137
150
  e.preventDefault();
138
151
  saveMutation.mutate();
139
- }, spacing: "md", padding: "md", children: [items && items.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Label, { size: "sm", weight: "medium", color: "primary", children: ["Items (", items.length, ")"] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: items.map((item, i) => (_jsxs(Div, { layout: "flex", align: "center", gap: "3", padding: "sm", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) }), _jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.title }), _jsxs(Text, { size: "xs", color: "muted", children: ["Qty: ", item.quantity, " \u00D7 ", toCurrency(item.unitPrice)] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "semibold", children: toCurrency(item.totalPrice) })] }, item.productId || i))) })] })), _jsx(Select, { label: "Order status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Input, { label: "Tracking number (optional)", value: trackingNumber, onChange: (e) => setTrackingNumber(e.target.value), placeholder: "e.g. DEL1234567890IN" }), _jsx(Select, { label: "Carrier (optional)", options: CARRIER_OPTIONS, value: carrier, onValueChange: setCarrier }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Internal note (optional)" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Reason for status change, escalation notes\u2026" })] }), (status === "refunded" || status === "return_requested") && (_jsx(Input, { label: "Refund amount \u20B9 (optional)", type: "number", min: "0", step: "0.01", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "e.g. 499.00" })), isCashOrUpi && (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Payment Proof" }), paymentProofUrl ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { border: "default", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: paymentProofUrl, alt: "Payment screenshot", size: "card" }) }), paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", children: ["UTR: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: paymentTransactionId })] })), needsVerification && (_jsxs(Stack, { gap: "xs", children: [(displayedUpiId || buyerReportedUpiId) && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Expected UPI: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: displayedUpiId || "—" }), " · ", "Buyer reported: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerReportedUpiId || "—" })] }), paymentUpiMismatch && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-error/20", surface: "danger-surface", children: _jsx(Text, { size: "xs", className: "text-error", weight: "semibold", children: "UPI mismatch \u2014 buyer-reported ID doesn't match the one shown for this order." }) }))] })), _jsxs(Text, { size: "xs", color: "muted", children: ["Buyer marked as paid: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerMarkedPaid ? "Yes" : "No" }), " · ", "Fraud agreement accepted: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerFraudAgreementAccepted ? "Yes" : "No" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Review note (required for re-upload / reject)" }), _jsx(Textarea, { value: reviewNote, onChange: (e) => setReviewNote(e.target.value), rows: 2, placeholder: "e.g. Screenshot is blurry \u2014 amount not readable / UPI ID doesn't match order" })] }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["verify-payment"], onClick: handleVerifyPayment, isLoading: isVerifyingPayment, disabled: isVerifyingPayment, variant: "primary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["request-payment-reupload"], onClick: handleRequestReupload, isLoading: isRequestingReupload, disabled: isRequestingReupload || !reviewNote.trim(), variant: "secondary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["reject-payment-fraud"], onClick: handleRejectFraud, isLoading: isRejectingFraud, disabled: isRejectingFraud || !reviewNote.trim(), variant: "danger", className: "w-full" })] })), !needsVerification && paymentStatus === "paid" && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-success/20", surface: "success-surface", children: _jsx(Text, { size: "xs", className: "text-success", weight: "medium", children: "Payment verified" }) }))] })) : (_jsx(Text, { size: "xs", color: "faint", children: "No proof uploaded yet." }))] })), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !orderId || saveMutation.isPending, children: "Save changes" })] })] }) }));
152
+ }, spacing: "md", padding: "md", children: [addons && _jsx(OrderAddonBadges, { order: addons, variant: "detail" }), items && items.length > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Label, { size: "sm", weight: "medium", color: "primary", children: ["Items (", items.length, ")"] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: items.map((item, i) => (_jsxs(Div, { layout: "flex", align: "center", gap: "3", padding: "sm", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title, size: "thumbnail" }) }), _jsxs(Div, { className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.title }), _jsxs(Text, { size: "xs", color: "muted", children: ["Qty: ", item.quantity, " \u00D7 ", toCurrency(item.unitPrice)] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "semibold", children: toCurrency(item.totalPrice) })] }, item.productId || i))) })] })), _jsx(Select, { label: "Order status", options: STATUS_OPTIONS, value: status, onValueChange: setStatus }), _jsx(Input, { label: "Tracking number (optional)", value: trackingNumber, onChange: (e) => setTrackingNumber(e.target.value), placeholder: "e.g. DEL1234567890IN" }), _jsx(Select, { label: "Carrier (optional)", options: CARRIER_OPTIONS, value: carrier, onValueChange: setCarrier }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Internal note (optional)" }), _jsx(Textarea, { value: notes, onChange: (e) => setNotes(e.target.value), rows: 3, placeholder: "Reason for status change, escalation notes\u2026" })] }), (status === "refunded" || status === "return_requested") && (_jsx(Input, { label: "Refund amount \u20B9 (optional)", type: "number", min: "0", step: "0.01", value: refundAmount, onChange: (e) => setRefundAmount(e.target.value), placeholder: "e.g. 499.00" })), isManualPayment && (_jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Payment Proof" }), isRejected && (_jsx(Text, { size: "xs", className: "text-error", weight: "medium", children: "Payment rejected as fraud \u2014 no further action available here." })), isReuploadRequested && !isVerified && (_jsx(Text, { size: "xs", className: "text-warning", weight: "medium", children: "Re-upload requested \u2014 waiting for the buyer to submit a new proof." })), paymentProofUrl ? (_jsxs(Stack, { gap: "xs", children: [_jsx(Div, { border: "default", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: paymentProofUrl, alt: "Payment screenshot", size: "card" }) }), paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", children: ["UTR: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: paymentTransactionId })] })), needsVerification && (_jsxs(Stack, { gap: "xs", children: [(displayedUpiId || buyerReportedUpiId) && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Expected UPI: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: displayedUpiId || "—" }), " · ", "Buyer reported: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerReportedUpiId || "—" })] }), paymentUpiMismatch && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-error/20", surface: "danger-surface", children: _jsx(Text, { size: "xs", className: "text-error", weight: "semibold", children: "UPI mismatch \u2014 buyer-reported ID doesn't match the one shown for this order." }) }))] })), _jsxs(Text, { size: "xs", color: "muted", children: ["Buyer marked as paid: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerMarkedPaid ? "Yes" : "No" }), " · ", "Fraud agreement accepted: ", _jsx(Text, { as: "span", size: "xs", weight: "medium", children: buyerFraudAgreementAccepted ? "Yes" : "No" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Label, { size: "sm", weight: "medium", color: "primary", children: "Review note (required for re-upload / reject)" }), _jsx(Textarea, { value: reviewNote, onChange: (e) => setReviewNote(e.target.value), rows: 2, placeholder: "e.g. Screenshot is blurry \u2014 amount not readable / UPI ID doesn't match order" })] }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["verify-payment"], onClick: handleVerifyPayment, isLoading: isVerifyingPayment, disabled: isVerifyingPayment, variant: "primary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["request-payment-reupload"], onClick: handleRequestReupload, isLoading: isRequestingReupload, disabled: isRequestingReupload || !reviewNote.trim(), variant: "secondary", className: "w-full" }), _jsx(Button, { type: "button", action: ACTIONS.ADMIN["reject-payment-fraud"], onClick: handleRejectFraud, isLoading: isRejectingFraud, disabled: isRejectingFraud || !reviewNote.trim(), variant: "danger", className: "w-full" })] })), isVerified && (_jsx(Div, { rounded: "lg", padding: "inlineSm", className: "border border-success/20", surface: "success-surface", children: _jsx(Text, { size: "xs", className: "text-success", weight: "medium", children: "Payment verified" }) }))] })) : (_jsx(Text, { size: "xs", color: "faint", children: "No proof uploaded yet." }))] })), _jsxs(FormActions, { align: "right", children: [_jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", isLoading: saveMutation.isPending, disabled: !orderId || saveMutation.isPending, children: "Save changes" })] })] }) }));
140
153
  }
@@ -122,6 +122,20 @@ export function AdminOrdersView({ children, ...props }) {
122
122
  paymentUpiMismatch: Boolean(item.paymentUpiMismatch),
123
123
  buyerMarkedPaid: Boolean(item.buyerMarkedPaid),
124
124
  buyerFraudAgreementAccepted: Boolean(item.buyerFraudAgreementAccepted),
125
+ paymentReviewOutcome: toStringValue(item.paymentReviewOutcome, "") || undefined,
126
+ // Straight off the order document — the admin list route returns
127
+ // raw docs, so these need no serializer entry (cf. Root Cause #38).
128
+ addons: {
129
+ whatsappNotifyAddon: Boolean(item.whatsappNotifyAddon),
130
+ whatsappNotifyFee: Number(item.whatsappNotifyFee ?? 0) || undefined,
131
+ giftWrapAddon: Boolean(item.giftWrapAddon),
132
+ giftWrapFee: Number(item.giftWrapFee ?? 0) || undefined,
133
+ giftWrapMessage: toStringValue(item.giftWrapMessage) || undefined,
134
+ shipmentProtectionAddon: Boolean(item.shipmentProtectionAddon),
135
+ shipmentProtectionFee: Number(item.shipmentProtectionFee ?? 0) || undefined,
136
+ couponCode: toStringValue(item.couponCode) || undefined,
137
+ couponDiscount: Number(item.couponDiscount ?? 0) || undefined,
138
+ },
125
139
  };
126
140
  }),
127
141
  getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
@@ -198,5 +212,5 @@ export function AdminOrdersView({ children, ...props }) {
198
212
  ] })),
199
213
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_ORDER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id, paymentReview: "" })) }), _jsx(FilterChipGroup, { label: "Manual payment", tabs: ADMIN_ORDER_PAYMENT_REVIEW_TABS, value: pendingFilters.paymentReview ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, paymentReview: id, status: "" })) })] })),
200
214
  };
201
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminOrderEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), orderId: selectedRow?.id, orderLabel: selectedRow?.primary, currentStatus: selectedRow?.status, items: selectedRow?.items, paymentProofUrl: selectedRow?.paymentProofUrl, paymentTransactionId: selectedRow?.paymentTransactionId, paymentMethod: selectedRow?.paymentMethod, paymentStatus: selectedRow?.paymentStatus, displayedUpiId: selectedRow?.displayedUpiId, buyerReportedUpiId: selectedRow?.buyerReportedUpiId, paymentUpiMismatch: selectedRow?.paymentUpiMismatch, buyerMarkedPaid: selectedRow?.buyerMarkedPaid, buyerFraudAgreementAccepted: selectedRow?.buyerFraudAgreementAccepted })] }));
215
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(AdminOrderEditorView, { open: drawerOpen, onClose: () => setDrawerOpen(false), orderId: selectedRow?.id, orderLabel: selectedRow?.primary, currentStatus: selectedRow?.status, items: selectedRow?.items, paymentProofUrl: selectedRow?.paymentProofUrl, paymentTransactionId: selectedRow?.paymentTransactionId, paymentMethod: selectedRow?.paymentMethod, paymentStatus: selectedRow?.paymentStatus, displayedUpiId: selectedRow?.displayedUpiId, buyerReportedUpiId: selectedRow?.buyerReportedUpiId, paymentUpiMismatch: selectedRow?.paymentUpiMismatch, buyerMarkedPaid: selectedRow?.buyerMarkedPaid, buyerFraudAgreementAccepted: selectedRow?.buyerFraudAgreementAccepted, paymentReviewOutcome: selectedRow?.paymentReviewOutcome, addons: selectedRow?.addons })] }));
202
216
  }