@mohasinac/appkit 4.37.1 → 4.38.0

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 (126) hide show
  1. package/dist/_internal/server/features/bundles/og.d.ts +0 -1
  2. package/dist/_internal/server/features/bundles/og.js +2 -18
  3. package/dist/_internal/server/features/catalogue/og.d.ts +0 -1
  4. package/dist/_internal/server/features/catalogue/og.js +1 -9
  5. package/dist/_internal/server/features/classified/og.d.ts +0 -1
  6. package/dist/_internal/server/features/classified/og.js +1 -9
  7. package/dist/_internal/server/features/digital-code/og.d.ts +0 -1
  8. package/dist/_internal/server/features/digital-code/og.js +1 -9
  9. package/dist/_internal/server/features/grouped/og.d.ts +7 -4
  10. package/dist/_internal/server/features/grouped/og.js +7 -6
  11. package/dist/_internal/server/features/item-requests/og.d.ts +0 -1
  12. package/dist/_internal/server/features/item-requests/og.js +2 -18
  13. package/dist/_internal/server/features/live/og.d.ts +0 -1
  14. package/dist/_internal/server/features/live/og.js +1 -9
  15. package/dist/_internal/server/features/prize-draws/og.js +5 -10
  16. package/dist/_internal/server/features/products/og.js +5 -2
  17. package/dist/_internal/server/features/seo/sitemap.js +10 -2
  18. package/dist/_internal/server/features/shared/listing-adapters.d.ts +1 -2
  19. package/dist/_internal/server/features/shared/listing-adapters.js +5 -3
  20. package/dist/_internal/shared/listing-types/_registry.d.ts +31 -4
  21. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  22. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  23. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  24. package/dist/_internal/shared/listing-types/auction/config.js +3 -0
  25. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  26. package/dist/_internal/shared/listing-types/classified/config.js +3 -0
  27. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  28. package/dist/_internal/shared/listing-types/digital-code/config.js +3 -0
  29. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  30. package/dist/_internal/shared/listing-types/live/config.js +3 -0
  31. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  32. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  33. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  34. package/dist/_internal/shared/listing-types/prize-draw/config.js +3 -0
  35. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  36. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  37. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  38. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  39. package/dist/client/observability/reportClientError.js +11 -1
  40. package/dist/client.d.ts +4 -0
  41. package/dist/client.js +4 -0
  42. package/dist/core/hooks/useSyncManager.js +11 -1
  43. package/dist/features/account/hooks/useNotifications.js +25 -5
  44. package/dist/features/auctions/components/AuctionBottomActions.js +10 -2
  45. package/dist/features/auctions/components/AuctionDetailPageView.js +12 -3
  46. package/dist/features/auctions/components/LiveBidPrice.js +2 -2
  47. package/dist/features/auctions/components/LiveMinIncrement.d.ts +1 -1
  48. package/dist/features/auctions/components/LiveMinIncrement.js +8 -0
  49. package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
  50. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  51. package/dist/features/auctions/hooks/useAuctions.js +12 -1
  52. package/dist/features/auctions/hooks/useRealtimeBids.d.ts +3 -0
  53. package/dist/features/auctions/hooks/useRealtimeBids.js +138 -36
  54. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +2 -2
  55. package/dist/features/catalogue/components/PublicCatalogueView.js +2 -2
  56. package/dist/features/categories/components/BundleCollage.js +10 -5
  57. package/dist/features/categories/components/BundleDetailView.js +7 -3
  58. package/dist/features/categories/components/CategoryProductsListing.js +4 -1
  59. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
  60. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
  61. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -4
  62. package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
  63. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  64. package/dist/features/pre-orders/components/PreOrderActionsClient.js +2 -2
  65. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
  66. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +4 -1
  67. package/dist/features/products/actions/product-actions.d.ts +9 -0
  68. package/dist/features/products/actions/product-actions.js +11 -0
  69. package/dist/features/products/columns/productTableColumns.js +2 -2
  70. package/dist/features/products/components/AuctionsIndexListing.js +4 -1
  71. package/dist/features/products/components/BidHistory.js +2 -2
  72. package/dist/features/products/components/CompareOverlay.js +2 -2
  73. package/dist/features/products/components/GroupMemberPicker.js +5 -5
  74. package/dist/features/products/components/ListingBottomActions.js +12 -3
  75. package/dist/features/products/components/MakeOfferButton.js +7 -1
  76. package/dist/features/products/components/MarketplaceBundleCard.js +4 -4
  77. package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
  78. package/dist/features/products/components/PrizeDrawCollage.js +16 -5
  79. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  80. package/dist/features/products/components/PrizeDrawsIndexListing.js +4 -1
  81. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  82. package/dist/features/products/components/ProductDetailActions.js +10 -3
  83. package/dist/features/products/components/ProductFilters.js +2 -2
  84. package/dist/features/products/components/ProductGrid.js +5 -2
  85. package/dist/features/products/components/ProductsIndexListing.js +12 -1
  86. package/dist/features/products/components/ProductsIndexPageView.js +26 -0
  87. package/dist/features/products/components/ShowGroupSection.js +2 -2
  88. package/dist/features/products/components/SublistingCarouselSection.js +2 -2
  89. package/dist/features/products/constants/listing-tabs.d.ts +36 -12
  90. package/dist/features/products/constants/listing-tabs.js +38 -14
  91. package/dist/features/products/constants/sieve.d.ts +14 -0
  92. package/dist/features/products/constants/sieve.js +39 -0
  93. package/dist/features/tester/seed-data/authored/buying__bidding.js +5 -4
  94. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +85 -85
  95. package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +2 -2
  96. package/dist/features/tester/seed-data/authored/buying__product-detail.js +53 -15
  97. package/dist/features/tester/seed-data/authored/content-discovery__search.js +6 -4
  98. package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +4 -4
  99. package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +2 -2
  100. package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +4 -4
  101. package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +8 -8
  102. package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +8 -8
  103. package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +16 -0
  104. package/dist/features/tester/seed-data/authored/seo__og-images.js +17 -0
  105. package/dist/features/tester/seed-data/stores-tester-seed-data.js +41 -2
  106. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +36 -6
  107. package/dist/features/wishlist/components/WishlistPage.js +2 -2
  108. package/dist/index.d.ts +2 -1
  109. package/dist/index.js +4 -1
  110. package/dist/react/contexts/SessionContext.js +40 -1
  111. package/dist/react/hooks/useCanSeePrices.d.ts +37 -0
  112. package/dist/react/hooks/useCanSeePrices.js +40 -0
  113. package/dist/seed/addresses-seed-data.js +36 -2
  114. package/dist/seed/users-seed-data.js +10 -2
  115. package/dist/seo/json-ld.d.ts +21 -0
  116. package/dist/seo/json-ld.js +45 -0
  117. package/dist/styles.css +41 -0
  118. package/dist/ui/components/GatedPrice.d.ts +66 -0
  119. package/dist/ui/components/GatedPrice.js +80 -0
  120. package/dist/ui/components/GatedPrice.style.css +40 -0
  121. package/dist/ui/components/PriceDisplay.d.ts +12 -0
  122. package/dist/ui/components/PriceDisplay.js +17 -17
  123. package/dist/ui/components/index.style.css +1 -0
  124. package/dist/ui/index.d.ts +2 -0
  125. package/dist/ui/index.js +1 -0
  126. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
4
  import { useQuery } from "@tanstack/react-query";
5
- import { Container, Div, Grid, Heading, Main, Row, Section, Skeleton, Span, Stack, Text } from "../../../ui";
5
+ import { Container, Div, GatedPrice, Grid, Heading, Main, Row, Section, Skeleton, Span, Stack, Text } from "../../../ui";
6
6
  import { MediaImage } from "../../media/MediaImage";
7
7
  import { apiClient } from "../../../http";
8
8
  import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -29,5 +29,5 @@ export function PublicCatalogueItemDetailView({ ownerSlug, itemId }) {
29
29
  }
30
30
  const { owner, item, linkedProductHref } = data;
31
31
  const images = item.images.length > 0 ? item.images : item.mainImage ? [item.mainImage] : [];
32
- return (_jsx(Main, { children: _jsxs(Container, { size: "lg", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", gap: "xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: "/", className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "hover:text-primary-600 transition-colors", children: owner.displayName ?? "Catalogue" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: item.title })] }), _jsxs(Grid, { cols: 2, gap: "lg", children: [images.length > 0 ? (_jsx(Div, { className: `relative aspect-square w-full ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: images[0], alt: item.title, size: "hero", objectFit: "contain" }) })) : (_jsx(Div, { className: "relative aspect-square w-full", rounded: "xl", surface: "muted" })), images.length > 1 && (_jsx(Row, { gap: "sm", wrap: true, className: "col-span-2", children: images.slice(1).map((src, i) => (_jsx(Div, { className: `relative h-20 w-20 ${__O.hidden}`, rounded: "lg", surface: "muted", children: _jsx(MediaImage, { src: src, alt: `${item.title} photo ${i + 2}`, size: "thumbnail", objectFit: "cover" }) }, i))) })), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", color: "primary", children: item.title }), typeof item.price === "number" && item.price > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(item.price) }), _jsx(Text, { size: "xs", color: "muted", children: "Estimated value \u2014 not for direct sale from this page." })] })), _jsxs(Row, { gap: "sm", wrap: true, children: [item.condition && (_jsx(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: item.condition })), typeof item.quantity === "number" && (_jsxs(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Qty: ", item.quantity] }))] }), item.description && (_jsx(Text, { color: "muted", className: "whitespace-pre-line", children: item.description })), owner.displayName && (_jsxs(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Owned by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: owner.displayName })] })), linkedProductHref && (_jsx(Link, { href: linkedProductHref, className: "inline-flex w-fit items-center rounded-lg bg-primary px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white transition-colors hover:opacity-90", children: "View live listing \u2192" }))] })] })] }) }));
32
+ return (_jsx(Main, { children: _jsxs(Container, { size: "lg", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", gap: "xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: "/", className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "hover:text-primary-600 transition-colors", children: owner.displayName ?? "Catalogue" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: item.title })] }), _jsxs(Grid, { cols: 2, gap: "lg", children: [images.length > 0 ? (_jsx(Div, { className: `relative aspect-square w-full ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: images[0], alt: item.title, size: "hero", objectFit: "contain" }) })) : (_jsx(Div, { className: "relative aspect-square w-full", rounded: "xl", surface: "muted" })), images.length > 1 && (_jsx(Row, { gap: "sm", wrap: true, className: "col-span-2", children: images.slice(1).map((src, i) => (_jsx(Div, { className: `relative h-20 w-20 ${__O.hidden}`, rounded: "lg", surface: "muted", children: _jsx(MediaImage, { src: src, alt: `${item.title} photo ${i + 2}`, size: "thumbnail", objectFit: "cover" }) }, i))) })), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", color: "primary", children: item.title }), typeof item.price === "number" && item.price > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { label: "Sign in to see the estimated value", children: formatCurrency(item.price) }) }), _jsx(Text, { size: "xs", color: "muted", children: "Estimated value \u2014 not for direct sale from this page." })] })), _jsxs(Row, { gap: "sm", wrap: true, children: [item.condition && (_jsx(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: item.condition })), typeof item.quantity === "number" && (_jsxs(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Qty: ", item.quantity] }))] }), item.description && (_jsx(Text, { color: "muted", className: "whitespace-pre-line", children: item.description })), owner.displayName && (_jsxs(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Owned by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: owner.displayName })] })), linkedProductHref && (_jsx(Link, { href: linkedProductHref, className: "inline-flex w-fit items-center rounded-lg bg-primary px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white transition-colors hover:opacity-90", children: "View live listing \u2192" }))] })] })] }) }));
33
33
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
4
  import { useQuery } from "@tanstack/react-query";
5
- import { Div, Grid, Heading, Skeleton, Text } from "../../../ui";
5
+ import { Div, GatedPrice, Grid, Heading, Skeleton, Text } from "../../../ui";
6
6
  import { MediaImage } from "../../media/MediaImage";
7
7
  import { apiClient } from "../../../http";
8
8
  import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -19,5 +19,5 @@ export function PublicCatalogueView({ ownerSlug }) {
19
19
  if (items.length === 0) {
20
20
  return _jsx(Text, { variant: "secondary", children: "Nothing public here yet." });
21
21
  }
22
- return (_jsx(Grid, { cols: 4, gap: "md", children: items.map((item) => (_jsx(Link, { href: `/catalogue/${ownerSlug}/${item.id}`, className: "block transition-opacity hover:opacity-90", children: _jsxs(Div, { rounded: "xl", border: "default", padding: "md", surface: "card", children: [_jsx(MediaImage, { src: item.mainImage || item.images[0] || "", alt: item.title, size: "card" }), _jsx(Div, { padding: "t-sm", children: _jsx(Heading, { level: 4, size: "sm", children: item.title }) }), typeof item.price === "number" && item.price > 0 && (_jsx(Text, { size: "sm", color: "muted", children: formatCurrency(item.price) }))] }) }, item.id))) }));
22
+ return (_jsx(Grid, { cols: 4, gap: "md", children: items.map((item) => (_jsx(Link, { href: `/catalogue/${ownerSlug}/${item.id}`, className: "block transition-opacity hover:opacity-90", children: _jsxs(Div, { rounded: "xl", border: "default", padding: "md", surface: "card", children: [_jsx(MediaImage, { src: item.mainImage || item.images[0] || "", alt: item.title, size: "card" }), _jsx(Div, { padding: "t-sm", children: _jsx(Heading, { level: 4, size: "sm", children: item.title }) }), typeof item.price === "number" && item.price > 0 && (_jsx(Text, { size: "sm", color: "muted", children: _jsx(GatedPrice, { children: formatCurrency(item.price) }) }))] }) }, item.id))) }));
23
23
  }
@@ -10,19 +10,23 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  */
11
11
  import { useState } from "react";
12
12
  import Link from "next/link";
13
- import { Button, Div, Grid, Row, Text } from "../../../ui";
13
+ import { useCanSeePrices } from "../../../react/hooks/useCanSeePrices";
14
+ import { Button, Div, GatedPrice, Grid, Row, Text } from "../../../ui";
14
15
  import { MediaImage } from "../../media/MediaImage";
15
16
  import { ImageLightbox } from "../../../ui/components/ImageLightbox";
16
17
  import { ROUTES } from "../../../next/routing/route-map";
17
18
  import { formatCurrency } from "../../../utils/number.formatter";
18
19
  const PLACEHOLDER_EMOJI = "📦";
19
- function toGalleryImages(members) {
20
+ function toGalleryImages(members, canSeePrices) {
20
21
  return members.map((p, i) => ({
21
22
  src: p.mainImage ?? p.images?.[0] ?? "",
22
23
  alt: p.title,
23
24
  badge: `#${i + 1}`,
24
25
  caption: p.title,
25
- sub: formatCurrency(p.price ?? 0, p.currency ?? "INR"),
26
+ // `sub` is a plain string on the lightbox caption, so the gate has to
27
+ // happen here — the tile's own price is gated in the JSX below, and this
28
+ // would otherwise print it again one click away.
29
+ sub: canSeePrices ? formatCurrency(p.price ?? 0, p.currency ?? "INR") : undefined,
26
30
  }));
27
31
  }
28
32
  function makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex) {
@@ -37,13 +41,14 @@ function makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex) {
37
41
  }
38
42
  export function BundleCollage({ members, onItemClick }) {
39
43
  const [lightboxIndex, setLightboxIndex] = useState(null);
44
+ const { canSeePrices } = useCanSeePrices();
40
45
  if (!members.length)
41
46
  return null;
42
- const galleryImages = toGalleryImages(members);
47
+ const galleryImages = toGalleryImages(members, canSeePrices);
43
48
  return (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: "categoryCards", gap: "3", children: members.map((p, idx) => {
44
49
  const cover = p.mainImage ?? p.images?.[0];
45
50
  const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
46
- return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] })] }, p.id));
51
+ return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: _jsx(GatedPrice, { children: formatCurrency(p.price ?? 0, p.currency ?? "INR") }) })] })] }, p.id));
47
52
  }) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
48
53
  }
49
54
  export default BundleCollage;
@@ -1,5 +1,5 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Badge, Container, Div, Heading, Li, Main, Row, Section, Span, Stack, Text, Ul, } from "../../../ui";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge, Container, Div, GatedPrice, PricesOnly, Heading, Li, Main, Row, Section, Span, Stack, Text, Ul, } from "../../../ui";
3
3
  import { formatCurrency } from "../../../utils/number.formatter";
4
4
  import Link from "next/link";
5
5
  import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
@@ -25,5 +25,9 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, onAddToCart,
25
25
  ? formatCurrency(bundle.bundlePrice, "INR")
26
26
  : BUNDLE_COPY.detail.priceFallback;
27
27
  const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
28
- return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: priceLabel }), discount && (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }), _jsx(Badge, { variant: "success", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })] })), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), discount && (_jsx(Text, { size: "sm", color: "success", weight: "medium", children: BUNDLE_COPY.detail.savingsLabel(formatCurrency(discount.savings, "INR")) })), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text-muted)]", size: "sm", weight: "semibold", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, onAddToCart: onAddToCart })) : (_jsxs(Div, { textSize: "sm", className: "border-dashed", border: "strong", surface: "muted", rounded: "xl", padding: "md", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(Ul, { gap: "2", className: "grid grid-cols-1 sm:grid-cols-2", children: members.map((p, i) => (_jsxs(Li, { layout: "flex-center", gap: "2", rounded: "lg", border: "subtle", padding: "inlineSm", textSize: "sm", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "w-5", color: "faint", children: ["#", i + 1] }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#"), className: "flex-1 truncate font-medium hover:underline hover:text-primary", children: p.title }), _jsx(Span, { size: "xs", color: "muted", children: formatCurrency(p.price ?? 0, p.currency ?? "INR") })] }, p.id))) }))] })] }) }) }), relatedBundles.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Bundles" }), _jsx(CategoryGrid, { categories: relatedBundles, getHref: (c) => String(ROUTES.PUBLIC.BUNDLE_DETAIL(c.slug)) })] }) }))] }));
28
+ return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: priceLabel }) }), discount && (
29
+ // Both the struck-through total and the "N% off" badge are
30
+ // derived from the bundle price, so they disclose it even
31
+ // though the badge carries no currency symbol.
32
+ _jsxs(PricesOnly, { children: [_jsx(Text, { size: "sm", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }), _jsx(Badge, { variant: "success", children: BUNDLE_COPY.detail.discountBadge(discount.percent) })] })), _jsxs(Text, { size: "sm", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.detail.itemCount(memberCount)] }), _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: STOCK_BADGE_TEXT[stock] })] }), discount && (_jsx(PricesOnly, { children: _jsx(Text, { size: "sm", color: "success", weight: "medium", children: BUNDLE_COPY.detail.savingsLabel(formatCurrency(discount.savings, "INR")) }) })), bundle.description && (_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text-muted)]", size: "sm", weight: "semibold", children: BUNDLE_COPY.detailDescriptionHeading }), _jsx(Text, { className: "whitespace-pre-line", children: bundle.description })] })), onBuyNow ? (_jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, onAddToCart: onAddToCart })) : (_jsxs(Div, { textSize: "sm", className: "border-dashed", border: "strong", surface: "muted", rounded: "xl", padding: "md", "aria-live": "polite", children: [_jsx(Text, { weight: "semibold", className: "mb-1 block", children: BUNDLE_COPY.detail.ctaDisabled }), _jsx(Text, { size: "sm", color: "muted", children: BUNDLE_COPY.detail.ctaHint })] }))] })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", color: "primary", children: BUNDLE_COPY.detailItemsHeading }), members.length === 0 ? (_jsx(Text, { color: "muted", children: BUNDLE_COPY.detail.emptyMembers })) : (_jsx(Ul, { gap: "2", className: "grid grid-cols-1 sm:grid-cols-2", children: members.map((p, i) => (_jsxs(Li, { layout: "flex-center", gap: "2", rounded: "lg", border: "subtle", padding: "inlineSm", textSize: "sm", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "w-5", color: "faint", children: ["#", i + 1] }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#"), className: "flex-1 truncate font-medium hover:underline hover:text-primary", children: p.title }), _jsx(Span, { size: "xs", color: "muted", children: _jsx(GatedPrice, { children: formatCurrency(p.price ?? 0, p.currency ?? "INR") }) })] }, p.id))) }))] })] }) }) }), relatedBundles.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Bundles" }), _jsx(CategoryGrid, { categories: relatedBundles, getHref: (c) => String(ROUTES.PUBLIC.BUNDLE_DETAIL(c.slug)) })] }) }))] }));
29
33
  }
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { withoutPriceSorts } from "../../products/constants/sieve";
4
+ import { useCanSeePrices } from "../../../react/hooks/useCanSeePrices";
3
5
  import { useState, useCallback } from "react";
4
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
7
  import { usePendingTable } from "../../../react/hooks/usePendingTable";
@@ -32,6 +34,7 @@ function renderCategoryProductGrid(props) {
32
34
  }
33
35
  export function CategoryProductsListing({ categorySlug, categoryId, brandName, listingTypes = ["standard"], initialData, }) {
34
36
  const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
37
+ const { canSeePrices } = useCanSeePrices();
35
38
  const { showToast } = useToast();
36
39
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
37
40
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -114,7 +117,7 @@ export function CategoryProductsListing({ categorySlug, categoryId, brandName, l
114
117
  pushCartOp({ op: "add", productId: product.id, quantity: 1, ...snapshot });
115
118
  showToast("Added to cart", "success");
116
119
  }, [localCart, showToast]);
117
- return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: `Search in ${categorySlug.replace(/-/g, " ")}...`, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
120
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: `Search in ${categorySlug.replace(/-/g, " ")}...`, onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: withoutPriceSorts(PRODUCT_PUBLIC_SORT_OPTIONS, canSeePrices), onSortChange: (v) => {
118
121
  table.set("sort", v);
119
122
  }, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: listingTypes }) }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: renderCategoryProductGrid({
120
123
  isLoading, products: products, view,
@@ -8,7 +8,7 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver";
8
8
  import { formatCurrency } from "../../../utils/number.formatter";
9
9
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
10
10
  import { safeDisplayName } from "../../../security";
11
- import { Container, Div, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
11
+ import { Container, Div, GatedPrice, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
12
12
  import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
13
13
  import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
14
14
  import { ProductTabsShell } from "../../products/components/ProductTabsShell";
@@ -94,7 +94,7 @@ export async function ClassifiedDetailPageView({ slug, initialProduct, renderOff
94
94
  price: price ?? undefined,
95
95
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
96
96
  storeName: storeName ?? undefined,
97
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CLASSIFIED), className: CLS_BREADCRUMB_LINK, children: "Classifieds" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_CLASSIFIED_BADGE, children: "Classified" }), meta?.negotiable && (_jsx(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: "Negotiable" })), meta?.acceptsShipping && (_jsx(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: "Shipping available" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), location && (_jsxs(Row, { color: "muted", textSize: "sm", align: "center", gap: "xs", children: [_jsx(Span, { children: "\uD83D\uDCCD" }), _jsx(Span, { children: location }), meta?.meetupArea?.pincode && _jsxs(Span, { children: ["\u00B7 ", meta.meetupArea.pincode] })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, customTabs: customSections.map((s) => ({
97
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CLASSIFIED), className: CLS_BREADCRUMB_LINK, children: "Classifieds" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_CLASSIFIED_BADGE, children: "Classified" }), meta?.negotiable && (_jsx(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: "Negotiable" })), meta?.acceptsShipping && (_jsx(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: "Shipping available" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) })), location && (_jsxs(Row, { color: "muted", textSize: "sm", align: "center", gap: "xs", children: [_jsx(Span, { children: "\uD83D\uDCCD" }), _jsx(Span, { children: location }), meta?.meetupArea?.pincode && _jsxs(Span, { children: ["\u00B7 ", meta.meetupArea.pincode] })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, customTabs: customSections.map((s) => ({
98
98
  id: s.id,
99
99
  label: s.title,
100
100
  content: _jsx(CustomSectionTabContent, { section: s }),
@@ -8,7 +8,7 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver";
8
8
  import { formatCurrency } from "../../../utils/number.formatter";
9
9
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
10
10
  import { safeDisplayName } from "../../../security";
11
- import { Div, Container, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
11
+ import { Div, Container, GatedPrice, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
12
12
  import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
13
13
  import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
14
14
  import { ProductTabsShell } from "../../products/components/ProductTabsShell";
@@ -65,7 +65,7 @@ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPr
65
65
  price: price ?? undefined,
66
66
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
67
67
  storeName: storeName ?? undefined,
68
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: CLS_BREADCRUMB_LINK, children: "Digital Codes" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_DIGITALCODE_BADGE, children: "Digital Code" }), codesLeft > 0 ? (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: [codesLeft, " available"] })) : (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "danger-surface", color: "error", children: "Sold out" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), meta?.redemptionInstructions && (_jsx(Text, { size: "xs", color: "muted", children: meta.redemptionInstructions })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
68
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: CLS_BREADCRUMB_LINK, children: "Digital Codes" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_DIGITALCODE_BADGE, children: "Digital Code" }), codesLeft > 0 ? (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: [codesLeft, " available"] })) : (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "danger-surface", color: "error", children: "Sold out" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) })), meta?.redemptionInstructions && (_jsx(Text, { size: "xs", color: "muted", children: meta.redemptionInstructions })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
69
69
  id: s.id,
70
70
  label: s.title,
71
71
  content: _jsx(CustomSectionTabContent, { section: s }),
@@ -9,7 +9,7 @@
9
9
  "use client";
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import Link from "next/link";
12
- import { Badge, Div, Grid, Heading, Row, Section, Stack, Text } from "../../../ui";
12
+ import { Badge, Div, GatedPrice, Grid, Heading, PricesOnly, Row, Section, Stack, Text } from "../../../ui";
13
13
  import { MediaImage } from "../../media/MediaImage";
14
14
  import { ROUTES } from "../../../next/routing/route-map";
15
15
  import { formatCurrency } from "../../../utils/number.formatter";
@@ -38,7 +38,7 @@ function FeaturedBundleCard({ bundle, showSavingsBadge = true, onBuyNow, }) {
38
38
  const cover = bundle.display?.coverImage;
39
39
  const href = String(ROUTES.PUBLIC.BUNDLE_DETAIL?.(bundle.slug) ?? "#");
40
40
  const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
41
- return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsxs(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: [cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), showSavingsBadge && discount && (_jsx(Badge, { variant: "success", className: "absolute left-2 top-2", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }))] }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: bundle.bundlePrice
42
- ? formatCurrency(bundle.bundlePrice, "INR")
43
- : BUNDLE_COPY.featured.priceFallback }), discount && (_jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") })), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
41
+ return (_jsxs(Stack, { gap: "none", surface: "default", rounded: "xl", border: "default", children: [_jsxs(Link, { href: href, className: "group block p-[var(--appkit-space-3)] hover:no-underline", children: [_jsxs(Div, { className: `relative mb-2 aspect-square ${__O.hidden}`, rounded: "lg", surface: "subtle", children: [cover ? (_jsx(MediaImage, { src: cover, alt: bundle.name, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", className: "h-full w-full", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), showSavingsBadge && discount && (_jsx(Badge, { variant: "success", className: "absolute left-2 top-2", children: BUNDLE_COPY.detail.discountBadge(discount.percent) }))] }), _jsx(Text, { className: "line-clamp-2", size: "sm", weight: "semibold", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", className: "mt-1", wrap: true, children: [_jsx(Text, { size: "sm", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: bundle.bundlePrice
42
+ ? formatCurrency(bundle.bundlePrice, "INR")
43
+ : BUNDLE_COPY.featured.priceFallback }) }), discount && (_jsx(PricesOnly, { children: _jsx(Text, { size: "xs", color: "muted", className: "line-through", children: formatCurrency(discount.originalTotal, "INR") }) })), _jsxs(Text, { size: "xs", color: "muted", children: ["\u00B7 ", BUNDLE_COPY.featured.itemCount(memberCount)] }), stock !== "in_stock" && (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stock], children: BUNDLE_COPY.stockBadge.listVariantOutOfStock }))] })] }), onBuyNow && (_jsx(Div, { border: "subtle", className: `border-t ${__P.p3}`, padding: "t-xs", children: _jsx(BundleBuyNowCta, { bundleSlug: bundle.slug, outOfStock: stock === "out_of_stock", onBuyNow: onBuyNow, compact: true }) }))] }));
44
44
  }
@@ -11,7 +11,7 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver";
11
11
  import { formatCurrency } from "../../../utils/number.formatter";
12
12
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
13
13
  import { safeDisplayName } from "../../../security";
14
- import { Div, Container, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
14
+ import { Div, Container, GatedPrice, PricesOnly, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
15
15
  import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
16
16
  import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
17
17
  import { ProductTabsShell } from "../../products/components/ProductTabsShell";
@@ -70,7 +70,7 @@ export async function LiveItemDetailPageView({ slug, initialProduct, renderActio
70
70
  price: price ?? undefined,
71
71
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
72
72
  storeName: storeName ?? undefined,
73
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.LIVE), className: CLS_BREADCRUMB_LINK, children: "Live" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_ITEM_BADGE, children: "Live Item" }), meta?.vendorVerified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Verified Seller" })), meta?.cites && (_jsxs(Span, { color: "warning", surface: "warning-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: ["CITES: ", meta.cites] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title }), meta?.species && (_jsxs(Text, { className: "italic mt-1", color: "muted", size: "sm", children: [meta.species, meta.sex && meta.sex !== "n/a" && ` · ${meta.sex}`, meta.ageMonths !== undefined && ` · ${meta.ageMonths}mo`] }))] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) })), jurisdictions.length > 0 && (_jsxs(Div, { className: CLS_WARN_BOX, children: [_jsx(Text, { className: CLS_WARN_TITLE, children: "Delivery restrictions" }), _jsxs(Text, { className: CLS_WARN_BODY, children: ["This item can only be shipped to: ", jurisdictions.join(", ")] })] })), transport && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Transport" }), _jsxs(Text, { className: "text-muted-foreground", children: ["Method: ", transport.method, transport.handlingFee !== undefined && ` · Handling: ${formatCurrency(transport.handlingFee, "INR")}`, transport.insuranceIncluded && " · Insurance included"] })] })), meta?.careInfo && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Care information" }), _jsx(Text, { className: "mt-1 text-muted-foreground", children: meta.careInfo })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
73
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.LIVE), className: CLS_BREADCRUMB_LINK, children: "Live" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_ITEM_BADGE, children: "Live Item" }), meta?.vendorVerified && (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Verified Seller" })), meta?.cites && (_jsxs(Span, { color: "warning", surface: "warning-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: ["CITES: ", meta.cites] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title }), meta?.species && (_jsxs(Text, { className: "italic mt-1", color: "muted", size: "sm", children: [meta.species, meta.sex && meta.sex !== "n/a" && ` · ${meta.sex}`, meta.ageMonths !== undefined && ` · ${meta.ageMonths}mo`] }))] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) })), jurisdictions.length > 0 && (_jsxs(Div, { className: CLS_WARN_BOX, children: [_jsx(Text, { className: CLS_WARN_TITLE, children: "Delivery restrictions" }), _jsxs(Text, { className: CLS_WARN_BODY, children: ["This item can only be shipped to: ", jurisdictions.join(", ")] })] })), transport && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Transport" }), _jsxs(Text, { className: "text-muted-foreground", children: ["Method: ", transport.method, transport.handlingFee !== undefined && (_jsx(PricesOnly, { children: ` · Handling: ${formatCurrency(transport.handlingFee, "INR")}` })), transport.insuranceIncluded && " · Insurance included"] })] })), meta?.careInfo && (_jsxs(Div, { textSize: "sm", border: "subtle", surface: "muted", padding: "md", rounded: "lg", children: [_jsx(Text, { weight: "medium", children: "Care information" }), _jsx(Text, { className: "mt-1 text-muted-foreground", children: meta.careInfo })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
74
74
  id: s.id,
75
75
  label: s.title,
76
76
  content: _jsx(CustomSectionTabContent, { section: s }),
@@ -14,7 +14,7 @@ const CLS_PREORDER_BADGE = "inline-flex items-center rounded-full bg-warning-sol
14
14
  const CLS_SALE_BADGE = "rounded-full bg-success-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-bold text-success-on-solid";
15
15
  const CLS_TRENDING_BADGE = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-bold text-white";
16
16
  const CLS_LIMITED_BADGE = "rounded-full bg-info-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-bold text-info-on-solid";
17
- import { BaseListingCard, Button, RichText, Row, Span, Stack, Text, TextLink } from "../../../ui";
17
+ import { BaseListingCard, Button, GatedPrice, RichText, Row, Span, Stack, Text, TextLink } from "../../../ui";
18
18
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
19
19
  import { PreorderBadge } from "./PreorderCard";
20
20
  import { WishlistHeartButton } from "../../wishlist/components/WishlistHeartButton";
@@ -66,7 +66,7 @@ export function MarketplacePreorderCard({ product, className = "", variant = "gr
66
66
  onSelect(product.id, !isSelected);
67
67
  }, className: selectable || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" }))] }), _jsx(BaseListingCard.Info, { variant: variant, children: variant === "list" ? (
68
68
  /* ── Compact list layout ── */
69
- _jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist }))] }), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-primary", size: "sm", weight: "bold", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "self-start mt-0.5", onClick: handleNavigate, children: mergedLabels.reserveNow })] })) : (
69
+ _jsxs(_Fragment, { children: [_jsxs(Row, { className: "min-w-0", align: "start", justify: "between", gap: "sm", children: [_jsx(TextLink, { href: detailHref, className: "min-w-0 flex-1", children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), wishlistActions && (_jsx(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist }))] }), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Text, { className: "text-primary", size: "sm", weight: "bold", children: _jsx(GatedPrice, { children: formatCurrency(product.price, getDefaultCurrency()) }) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "self-start mt-0.5", onClick: handleNavigate, children: mergedLabels.reserveNow })] })) : (
70
70
  /* ── Full grid layout ── */
71
- _jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: formatCurrency(product.price, getDefaultCurrency()) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-[var(--appkit-space-2)]", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist })) : null] })] })) })] }));
71
+ _jsxs(_Fragment, { children: [_jsx(TextLink, { href: detailHref, children: _jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }) }), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), product.description ? (_jsx(RichText, { html: normalizeRichTextHtml(product.description), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]" })) : null, _jsxs(Row, { justify: "between", className: "mt-1", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(product.price, getDefaultCurrency()) }) }), shipDate && _jsx(PreorderBadge, { shipDate: shipDate })] }), _jsxs(Row, { justify: "between", className: "mt-auto pt-[var(--appkit-space-2)]", gap: "sm", children: [_jsx(Button, { type: "button", variant: "primary", size: "sm", textSize: "xs", className: "flex-1", onClick: handleNavigate, children: mergedLabels.reserveNow }), onAddToCart ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", textSize: "xs", className: "flex-1", onClick: (e) => { e.stopPropagation(); onAddToCart(product); }, children: mergedLabels.addToCart })) : null, wishlistActions ? (_jsx(WishlistHeartButton, { size: "inline", inWishlist: inWishlist, addLabel: mergedLabels.addToWishlist, removeLabel: mergedLabels.removeFromWishlist, onToggle: handleWishlist })) : null] })] })) })] }));
72
72
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useTransition } from "react";
4
- import { Button, Div, LoginRequiredModal, Row, Span, Stack, Text } from "../../../ui";
4
+ import { Button, Div, GatedPrice, LoginRequiredModal, PricesOnly, Row, Span, Stack, Text } from "../../../ui";
5
5
  import { isAuthError } from "../../../utils/auth-error";
6
6
  import { formatCurrency } from "../../../utils/number.formatter";
7
7
  import { normalizeError } from "../../../errors/normalize";
@@ -29,7 +29,7 @@ export function PreOrderActionsClient({ productId, price, currency, depositAmoun
29
29
  }
30
30
  });
31
31
  }
32
- return (_jsxs(Stack, { gap: "md", children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) }), depositAmount !== null && (_jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }))] })), error && (_jsx(Text, { className: "text-error", size: "xs", children: error })), success && (_jsx(Text, { className: "text-success", size: "xs", children: "\u2713 Reserved! Redirecting to checkout\u2026" })), _jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to reserve this pre-order. Please log in or create an account to continue." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isPending, onClick: handleReserve, children: isPending ? "Processing…" : "Reserve Now" }), isCancellable && (_jsx(Text, { size: "xs", align: "start", color: "muted", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", className: "", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) })), _jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { className: "text-center", justify: "center", gap: "md", wrap: true, children: [
32
+ return (_jsxs(Stack, { gap: "md", children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) }), depositAmount !== null && (_jsx(PricesOnly, { children: _jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }) }))] })), error && (_jsx(Text, { className: "text-error", size: "xs", children: error })), success && (_jsx(Text, { className: "text-success", size: "xs", children: "\u2713 Reserved! Redirecting to checkout\u2026" })), _jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to reserve this pre-order. Please log in or create an account to continue." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isPending, onClick: handleReserve, children: isPending ? "Processing…" : "Reserve Now" }), isCancellable && (_jsx(Text, { size: "xs", align: "start", color: "muted", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", className: "", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) })), _jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { className: "text-center", justify: "center", gap: "md", wrap: true, children: [
33
33
  { icon: "🔒", label: "Secure\nPayment" },
34
34
  { icon: "📅", label: "Guaranteed\nDelivery" },
35
35
  { icon: "↩", label: "Free\nCancellation" },
@@ -17,7 +17,7 @@ import { formatCurrency, roundRupees } from "../../../utils/number.formatter";
17
17
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
18
18
  import { safeDisplayName } from "../../../security";
19
19
  import { ProgressBarFill } from "../../../ui/components/ProgressBarFill";
20
- import { Button, Container, Div, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Ul, Li, Dl, Dt, Dd, } from "../../../ui";
20
+ import { Button, Container, Div, GatedPrice, PricesOnly, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Ul, Li, Dl, Dt, Dd, } from "../../../ui";
21
21
  import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
22
22
  import { PreOrderBottomActions } from "./PreOrderBottomActions";
23
23
  import { ProductTabsShell } from "../../products/components/ProductTabsShell";
@@ -66,7 +66,7 @@ function PreOrderInfoSection({ title, productionStatus, maxPerUser, deliveryDate
66
66
  } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryName || category })), !category && categoryName && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] }) }))] }));
67
67
  }
68
68
  function PreOrderBuyBarPanel({ reserveTarget, reservedCount, progressPct, productId, price, currency, depositAmount, depositPercent, isCancellable, tags, onReserveNow, }) {
69
- return (_jsxs(Stack, { id: "pre-order-buy-bar", className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [reserveTarget > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", color: "muted", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }), _jsx(Div, { className: `h-2 w-full ${__O.hidden}`, rounded: "full", surface: "subtle", children: _jsx(ProgressBarFill, { pct: progressPct, className: "h-full bg-primary transition-all rounded-full" }) })] })), onReserveNow ? (_jsx(PreOrderActionsClient, { productId: productId, price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) : (_jsxs(_Fragment, { children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: formatCurrency(price, currency) }), depositAmount !== null && (_jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Reserve Now" }), isCancellable && (_jsx(Text, { size: "xs", align: "start", color: "muted", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) })), _jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "sm", justify: "center", className: "text-center", children: [
69
+ return (_jsxs(Stack, { id: "pre-order-buy-bar", className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [reserveTarget > 0 && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Text, { size: "xs", color: "muted", children: [reservedCount, " of ", reserveTarget, " reserved"] }), _jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary-600 dark:text-primary-400", children: [progressPct, "%"] })] }), _jsx(Div, { className: `h-2 w-full ${__O.hidden}`, rounded: "full", surface: "subtle", children: _jsx(ProgressBarFill, { pct: progressPct, className: "h-full bg-primary transition-all rounded-full" }) })] })), onReserveNow ? (_jsx(PreOrderActionsClient, { productId: productId, price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) : (_jsxs(_Fragment, { children: [price !== null && (_jsxs(Div, { children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) }), depositAmount !== null && (_jsx(PricesOnly, { children: _jsxs(Text, { className: "mt-0.5", color: "muted", size: "xs", children: ["Reserve with ", formatCurrency(depositAmount, currency), depositPercent !== null ? ` (${depositPercent}% deposit)` : ""] }) }))] })), _jsxs(Stack, { gap: "sm", children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Reserve Now" }), isCancellable && (_jsx(Text, { size: "xs", align: "start", color: "muted", children: "\u2713 Free cancellation before production" }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) })), _jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "sm", justify: "center", className: "text-center", children: [
70
70
  { icon: "🔒", label: "Secure\nPayment" },
71
71
  { icon: "📅", label: "Guaranteed\nDelivery" },
72
72
  { icon: "↩", label: "Free\nCancellation" },
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { withoutPriceSorts } from "../../products/constants/sieve";
4
+ import { useCanSeePrices } from "../../../react/hooks/useCanSeePrices";
3
5
  import { useState, useCallback } from "react";
4
6
  import { Columns, Heart, ShoppingCart } from "lucide-react";
5
7
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
@@ -34,6 +36,7 @@ const DEFAULT_SORT = PREORDER_SORT_OPTIONS[2].value;
34
36
  const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_PRICE, TABLE_KEYS.MAX_PRICE, TABLE_KEYS.STORE_ID, TABLE_KEYS.PREORDER_STATUS, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
35
37
  export function PreOrdersIndexListing({ initialData, categorySlug, brandName }) {
36
38
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
39
+ const { canSeePrices } = useCanSeePrices();
37
40
  const { showToast } = useToast();
38
41
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
39
42
  const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
@@ -181,7 +184,7 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
181
184
  },
182
185
  },
183
186
  ] } } : {});
184
- return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: PRE_ORDER_LISTING_TYPES }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
187
+ return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: withoutPriceSorts(PREORDER_SORT_OPTIONS, canSeePrices), onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: PRE_ORDER_LISTING_TYPES }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
185
188
  {
186
189
  id: ACTION_ID.ADD_TO_CART,
187
190
  label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
@@ -15,6 +15,15 @@ export interface ProductListActionParams {
15
15
  export type ProductListResult = FirebaseSieveResult<ProductDocument>;
16
16
  export declare function listProducts(params?: ProductListActionParams): Promise<ProductListResult>;
17
17
  export declare function getProductById(id: string): Promise<ProductDocument | null>;
18
+ /**
19
+ * Featured items for the homepage's generic "Featured Products" strip.
20
+ *
21
+ * Scoped to `GENERIC_PRODUCT_LISTING_TYPES` for the same reason `/products` is:
22
+ * the homepage already has dedicated Live Auctions, Pre-Orders and Prize Draws
23
+ * sections, so leaving this unscoped put the same item in two strips on one
24
+ * page — and an auction rendered as a generic product card shows Buy-Now chrome
25
+ * instead of bid affordances.
26
+ */
18
27
  export declare function getFeaturedProducts(pageSize?: number): Promise<ProductListResult>;
19
28
  /**
20
29
  * The soonest-ending LIVE auctions.
@@ -3,6 +3,7 @@ import { sortBy } from "@mohasinac/appkit";
3
3
  import { listPublicProducts } from "../../../_internal/server/features/products/list-public";
4
4
  import { AVAILABILITY_VALUES, PRODUCT_FIELDS } from "../../../constants/field-names";
5
5
  import { productRepository } from "../repository/products.repository";
6
+ import { GENERIC_PRODUCT_LISTING_TYPES } from "../constants/listing-tabs";
6
7
  import { ProductStatusValues } from "../schemas";
7
8
  import { hidePublicTestData } from "../../../_internal/server/features/tester/visibility";
8
9
  /**
@@ -63,10 +64,20 @@ function toListResult(result, pageSize) {
63
64
  hasMore: result.hasMore,
64
65
  };
65
66
  }
67
+ /**
68
+ * Featured items for the homepage's generic "Featured Products" strip.
69
+ *
70
+ * Scoped to `GENERIC_PRODUCT_LISTING_TYPES` for the same reason `/products` is:
71
+ * the homepage already has dedicated Live Auctions, Pre-Orders and Prize Draws
72
+ * sections, so leaving this unscoped put the same item in two strips on one
73
+ * page — and an auction rendered as a generic product card shows Buy-Now chrome
74
+ * instead of bid affordances.
75
+ */
66
76
  export async function getFeaturedProducts(pageSize = 8) {
67
77
  return toListResult(await listPublicProducts({
68
78
  featured: true,
69
79
  availability: AVAILABILITY_VALUES.AVAILABLE,
80
+ listingTypes: GENERIC_PRODUCT_LISTING_TYPES,
70
81
  sorts: sortBy("createdAt", "DESC"),
71
82
  page: 1,
72
83
  pageSize,
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, Div, Row, Span } from "../../../ui";
2
+ import { Button, Div, GatedPrice, Row, Span } from "../../../ui";
3
3
  import { MediaImage } from "../../media/MediaImage";
4
4
  import { formatCurrency as defaultFormatCurrency } from "../../../utils/number.formatter";
5
5
  const __O = {
@@ -35,7 +35,7 @@ export function getProductTableColumns({ labels, onEdit, onDelete, currencyCode,
35
35
  header: labels.price,
36
36
  sortable: true,
37
37
  width: "10%",
38
- render: (product) => (_jsx(Span, { children: formatCurrency(product.price ?? 0, product.currency ?? currencyCode, locale) })),
38
+ render: (product) => (_jsx(Span, { children: _jsx(GatedPrice, { children: formatCurrency(product.price ?? 0, product.currency ?? currencyCode, locale) }) })),
39
39
  },
40
40
  {
41
41
  key: "stockQuantity",
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { withoutPriceSorts } from "../constants/sieve";
4
+ import { useCanSeePrices } from "../../../react/hooks/useCanSeePrices";
3
5
  import { useState, useCallback } from "react";
4
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
7
  import { useProducts } from "../hooks/useProducts";
@@ -30,6 +32,7 @@ const AUCTION_LISTING_TYPES = ["auction"];
30
32
  const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_BID, TABLE_KEYS.MAX_BID, TABLE_KEYS.STORE_ID, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
31
33
  export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
32
34
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
35
+ const { canSeePrices } = useCanSeePrices();
33
36
  const { showToast } = useToast();
34
37
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
35
38
  const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
@@ -134,7 +137,7 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
134
137
  },
135
138
  },
136
139
  ] } } : {});
137
- return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: AUCTION_LISTING_TYPES }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
140
+ return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: withoutPriceSorts(AUCTION_PUBLIC_SORT_OPTIONS, canSeePrices), onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: AUCTION_LISTING_TYPES }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
138
141
  {
139
142
  id: ACTION_ID.WATCH_AUCTION,
140
143
  label: "Add to Watchlist",
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { normalizeError } from "../../../errors/normalize";
3
3
  import React from "react";
4
- import { Div, Heading, Span, Stack, Text } from "../../../ui";
4
+ import { Div, GatedPrice, Heading, Span, Stack, Text } from "../../../ui";
5
5
  import { formatCurrency } from "../../../utils/number.formatter";
6
6
  function formatBidDate(raw) {
7
7
  if (!raw)
@@ -40,5 +40,5 @@ export function BidHistory({ isLoading = false, isEmpty = false, renderList, ren
40
40
  }
41
41
  if (renderList)
42
42
  return _jsx(Div, { className: className, children: renderList() });
43
- return (_jsxs(Div, { className: className, children: [labels.title && (_jsx(Heading, { level: 3, className: "mb-3 tracking-wide", color: "muted", size: "sm", weight: "semibold", transform: "uppercase", children: labels.title })), _jsx(Stack, { gap: "sm", children: bids.map((bid, i) => renderBid ? (_jsx(React.Fragment, { children: renderBid(bid, i) }, bid.id)) : (_jsxs(Div, { layout: "flex", align: "center", justify: "between", rounded: "lg", padding: "sm", textSize: "sm", className: "border border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", "data-section": "bidhistory-div-422", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Span, { weight: "bold", className: "text-primary-600 dark:text-primary-400", children: currency ? formatCurrency(bid.amount, currency) : bid.amount.toLocaleString() }), _jsx(Span, { size: "xs", color: "muted", children: bid.bidderName || maskBidderId(bid.bidderId) })] }), _jsx(Span, { size: "xs", color: "muted", children: formatBidDate(bid.placedAt) })] }, bid.id))) })] }));
43
+ return (_jsxs(Div, { className: className, children: [labels.title && (_jsx(Heading, { level: 3, className: "mb-3 tracking-wide", color: "muted", size: "sm", weight: "semibold", transform: "uppercase", children: labels.title })), _jsx(Stack, { gap: "sm", children: bids.map((bid, i) => renderBid ? (_jsx(React.Fragment, { children: renderBid(bid, i) }, bid.id)) : (_jsxs(Div, { layout: "flex", align: "center", justify: "between", rounded: "lg", padding: "sm", textSize: "sm", className: "border border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", "data-section": "bidhistory-div-422", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Span, { weight: "bold", className: "text-primary-600 dark:text-primary-400", children: _jsx(GatedPrice, { label: "Sign in to see bids", children: currency ? formatCurrency(bid.amount, currency) : bid.amount.toLocaleString() }) }), _jsx(Span, { size: "xs", color: "muted", children: bid.bidderName || maskBidderId(bid.bidderId) })] }), _jsx(Span, { size: "xs", color: "muted", children: formatBidDate(bid.placedAt) })] }, bid.id))) })] }));
44
44
  }