@mohasinac/appkit 4.9.4 → 4.10.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 (160) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +7 -2
  2. package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +5 -1
  3. package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +8 -2
  4. package/dist/_internal/client/features/layout/sidebarPositionClasses.d.ts +20 -0
  5. package/dist/_internal/client/features/layout/sidebarPositionClasses.js +20 -0
  6. package/dist/_internal/client/hand-mode/HandModeProvider.d.ts +21 -0
  7. package/dist/_internal/client/hand-mode/HandModeProvider.js +81 -0
  8. package/dist/_internal/client/hand-mode/index.d.ts +2 -0
  9. package/dist/_internal/client/hand-mode/index.js +1 -0
  10. package/dist/_internal/server/features/auctions/actions.js +3 -2
  11. package/dist/_internal/server/features/auctions/service.d.ts +4 -3
  12. package/dist/_internal/server/features/auctions/service.js +6 -6
  13. package/dist/_internal/server/features/bundles/data.d.ts +9 -0
  14. package/dist/_internal/server/features/bundles/data.js +14 -0
  15. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  16. package/dist/_internal/server/features/bundles/index.js +1 -1
  17. package/dist/_internal/server/features/catalogue/og.d.ts +20 -0
  18. package/dist/_internal/server/features/catalogue/og.js +27 -0
  19. package/dist/_internal/server/features/checkout/actions.d.ts +43 -0
  20. package/dist/_internal/server/features/checkout/actions.js +172 -81
  21. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  22. package/dist/_internal/server/features/checkout/index.js +1 -1
  23. package/dist/_internal/server/features/grouped/data.d.ts +12 -0
  24. package/dist/_internal/server/features/grouped/data.js +26 -1
  25. package/dist/_internal/server/features/grouped/index.d.ts +1 -1
  26. package/dist/_internal/server/features/grouped/index.js +1 -1
  27. package/dist/_internal/server/features/products/data.d.ts +23 -0
  28. package/dist/_internal/server/features/products/data.js +159 -0
  29. package/dist/_internal/server/features/products/index.d.ts +1 -1
  30. package/dist/_internal/server/features/products/index.js +1 -1
  31. package/dist/_internal/server/features/seo/robots.js +3 -2
  32. package/dist/_internal/server/features/seo/sitemap.js +70 -5
  33. package/dist/_internal/shared/config/schema.d.ts +1 -0
  34. package/dist/_internal/shared/features/auctions/config.d.ts +20 -0
  35. package/dist/_internal/shared/features/auctions/config.js +31 -1
  36. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -1
  37. package/dist/_internal/shared/listing-types/art/config.js +1 -1
  38. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -1
  39. package/dist/_internal/shared/listing-types/classified/config.js +2 -2
  40. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -1
  41. package/dist/_internal/shared/listing-types/digital-code/config.js +2 -2
  42. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -1
  43. package/dist/_internal/shared/listing-types/live/config.js +2 -2
  44. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -1
  45. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  46. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -1
  47. package/dist/_internal/shared/listing-types/stickers/config.js +1 -1
  48. package/dist/client.d.ts +4 -6
  49. package/dist/client.js +10 -4
  50. package/dist/constants/api-endpoints.d.ts +6 -0
  51. package/dist/constants/api-endpoints.js +2 -0
  52. package/dist/features/about/components/AboutView.js +2 -2
  53. package/dist/features/about/components/DeveloperView.js +2 -2
  54. package/dist/features/about/schemas/firestore.d.ts +1 -0
  55. package/dist/features/account/components/UserSidebar.js +8 -2
  56. package/dist/features/account/hooks/useProfile.d.ts +2 -0
  57. package/dist/features/admin/components/AdminSidebar.js +8 -2
  58. package/dist/features/admin/components/AdminSiteSettingsView.js +12 -4
  59. package/dist/features/admin/schemas/firestore.d.ts +9 -0
  60. package/dist/features/admin/schemas/firestore.js +6 -0
  61. package/dist/features/auctions/actions/bid-actions.js +4 -1
  62. package/dist/features/auctions/components/AuctionBottomActions.d.ts +2 -1
  63. package/dist/features/auctions/components/AuctionBottomActions.js +14 -1
  64. package/dist/features/auctions/components/AuctionDetailPageView.js +21 -9
  65. package/dist/features/auctions/components/LiveMinIncrement.d.ts +18 -0
  66. package/dist/features/auctions/components/LiveMinIncrement.js +17 -0
  67. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +5 -2
  68. package/dist/features/auctions/components/PlaceBidFormClient.js +7 -3
  69. package/dist/features/auth/schemas/firestore.d.ts +3 -0
  70. package/dist/features/cart/components/CartDrawer.js +4 -1
  71. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.d.ts +11 -0
  72. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +33 -0
  73. package/dist/features/catalogue/components/PublicCatalogueView.js +2 -1
  74. package/dist/features/catalogue/index.d.ts +2 -0
  75. package/dist/features/catalogue/index.js +1 -0
  76. package/dist/features/categories/components/BundleDetailView.d.ts +3 -1
  77. package/dist/features/categories/components/BundleDetailView.js +3 -2
  78. package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +12 -0
  79. package/dist/features/classified/components/ClassifiedContactSellerPanel.js +49 -0
  80. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +7 -0
  81. package/dist/features/classified/components/ClassifiedDetailPageView.js +69 -0
  82. package/dist/features/contact/components/ContactPageView.js +8 -2
  83. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.d.ts +19 -0
  84. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +79 -0
  85. package/dist/features/grouped/components/GroupedListingsCarousel.d.ts +13 -0
  86. package/dist/features/grouped/components/GroupedListingsCarousel.js +31 -0
  87. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +8 -0
  88. package/dist/features/grouped/repository/grouped-listings.repository.js +16 -0
  89. package/dist/features/homepage/components/BrandsSection.js +3 -1
  90. package/dist/features/homepage/components/FeaturedBundlesSection.js +12 -1
  91. package/dist/features/homepage/components/GoogleReviewsSection.js +2 -2
  92. package/dist/features/homepage/components/HeroCarousel.js +3 -1
  93. package/dist/features/homepage/components/SectionCarousel.js +5 -2
  94. package/dist/features/homepage/components/SocialFeedSection.js +2 -2
  95. package/dist/features/homepage/components/WhatsAppCommunitySection.js +3 -1
  96. package/dist/features/layout/BackToTop.js +3 -1
  97. package/dist/features/layout/BottomActions.js +4 -3
  98. package/dist/features/layout/BottomActionsContext.d.ts +10 -0
  99. package/dist/features/layout/BottomActionsContext.js +5 -1
  100. package/dist/features/layout/SidebarLayout.js +7 -1
  101. package/dist/features/layout/hooks/useBottomActions.d.ts +2 -0
  102. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  103. package/dist/features/live/components/LiveItemDetailPageView.d.ts +9 -0
  104. package/dist/features/live/components/LiveItemDetailPageView.js +74 -0
  105. package/dist/features/media/MediaVideo.js +17 -1
  106. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
  107. package/dist/features/products/components/PrizeDrawDetailPageView.js +11 -1
  108. package/dist/features/products/components/ProductDetailPageView.js +11 -116
  109. package/dist/features/products/components/RelatedItemsSection.d.ts +16 -0
  110. package/dist/features/products/components/RelatedItemsSection.js +17 -0
  111. package/dist/features/seller/components/SellerProductShell.js +1 -1
  112. package/dist/features/seller/components/SellerSidebar.js +8 -2
  113. package/dist/features/shell/QuickFormDrawer.js +3 -1
  114. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +114 -3
  115. package/dist/hand-mode/index.d.ts +12 -0
  116. package/dist/hand-mode/index.js +11 -0
  117. package/dist/index.d.ts +2 -0
  118. package/dist/index.js +1 -0
  119. package/dist/next/routing/route-map.d.ts +2 -0
  120. package/dist/next/routing/route-map.js +7 -0
  121. package/dist/react/contexts/SessionContext.d.ts +5 -0
  122. package/dist/react/contexts/SessionContext.js +2 -0
  123. package/dist/seed/bids-seed-data.js +48 -3
  124. package/dist/seed/grouped-listings-seed-data.js +167 -0
  125. package/dist/seed/payouts-seed-data.js +4 -4
  126. package/dist/seed/products-art-seed-data.js +188 -5
  127. package/dist/seed/products-auctions-seed-data.js +215 -6
  128. package/dist/seed/products-classifieds-seed-data.js +255 -8
  129. package/dist/seed/products-digital-codes-seed-data.js +258 -19
  130. package/dist/seed/products-live-items-seed-data.js +143 -10
  131. package/dist/seed/products-preorders-seed-data.js +213 -8
  132. package/dist/seed/products-prize-draws-seed-data.js +198 -0
  133. package/dist/seed/products-standard-seed-data.js +141 -0
  134. package/dist/seed/products-stickers-seed-data.js +178 -5
  135. package/dist/seed/site-settings-seed-data.js +13 -31
  136. package/dist/seed/store-extensions-seed-data.js +1 -1
  137. package/dist/server-entry.d.ts +5 -2
  138. package/dist/server-entry.js +5 -2
  139. package/dist/server.d.ts +4 -2
  140. package/dist/server.js +4 -2
  141. package/dist/styles.css +30 -1
  142. package/dist/tailwind-utilities.css +1 -1
  143. package/dist/ui/components/Drawer.js +7 -4
  144. package/dist/ui/components/FilterDrawer.js +1 -1
  145. package/dist/ui/components/HandModeRow.d.ts +12 -0
  146. package/dist/ui/components/HandModeRow.js +18 -0
  147. package/dist/ui/components/ImageLightbox.js +37 -3
  148. package/dist/ui/components/ImageLightbox.style.css +28 -0
  149. package/dist/ui/components/Layout.d.ts +5 -1
  150. package/dist/ui/components/Layout.js +2 -1
  151. package/dist/ui/components/Layout.style.css +1 -0
  152. package/dist/ui/components/ListingFilterDrawer.js +3 -1
  153. package/dist/ui/components/Modal.js +3 -1
  154. package/dist/ui/components/SideDrawer.js +8 -5
  155. package/dist/ui/components/SideModal.js +7 -4
  156. package/dist/ui/index.d.ts +1 -0
  157. package/dist/ui/index.js +1 -0
  158. package/dist/utils/media-url.d.ts +9 -0
  159. package/dist/utils/media-url.js +39 -0
  160. package/package.json +1 -1
@@ -122,6 +122,9 @@ export interface UserDocument extends BaseDocument {
122
122
  * stay unique). Absent/missing IDs default to collapsed for a section the
123
123
  * user has never interacted with. See useCollapsedSections. */
124
124
  collapsedSections?: string[];
125
+ /** Which side CTA panels/drawers/sidebars dock to and where close buttons
126
+ * sit. Absent/"right" = default (right-docked). "left" = left-hand mode. */
127
+ handMode?: "left" | "right";
125
128
  };
126
129
  }
127
130
  export interface UserSoftBan {
@@ -1,8 +1,10 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { Aside, Button, Div, Heading, Row, Span, Stack, Text, TextLink } from "../../../ui";
3
4
  import { MediaImage } from "../../media/MediaImage";
4
5
  import { formatCurrency } from "../../../utils/number.formatter";
5
6
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
7
+ import { useHandMode } from "../../../_internal/client/hand-mode";
6
8
  const __P = {
7
9
  p4: "p-[var(--appkit-space-4)]",
8
10
  };
@@ -18,7 +20,8 @@ export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock =
18
20
  .join(", ") })), _jsxs(Row, { justify: "between", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsxs(Row, { gap: "sm", children: [_jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity - 1), disabled: item.quantity <= 1, variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)] disabled:opacity-40", children: "\u2212" }), _jsx(Span, { size: "sm", className: "min-w-[1.5rem]", align: "center", children: item.quantity }), _jsx(Button, { rounded: "full", onClick: () => onQtyChange(item.id, item.quantity + 1), variant: "outline", size: "sm", className: "h-7 w-7 justify-center border border-neutral-200 border-[var(--appkit-color-border)] text-[length:var(--appkit-text-sm)]", children: "+" })] })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
19
21
  }
20
22
  export function CartDrawer({ isOpen, onClose, items, subtotal = 0, currency, isLoading, onQtyChange, onRemove, onCheckout, labels = {}, }) {
23
+ const { hand } = useHandMode();
21
24
  if (!isOpen)
22
25
  return null;
23
- return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: "fixed inset-y-0 right-0 z-50 w-full max-w-sm", children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
26
+ return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: `fixed inset-y-0 ${hand === "left" ? "left-0" : "right-0"} z-50 w-full max-w-sm`, children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, reverse: hand === "left", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
24
27
  }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export interface PublicCatalogueItemDetailViewProps {
3
+ ownerSlug: string;
4
+ itemId: string;
5
+ }
6
+ /**
7
+ * Public detail page for a single personal-catalogue item — previously had
8
+ * no route or view at all; the public catalogue grid rendered bare, unlinked
9
+ * cards (see Recurrent Root Cause sweep, 2026-08-20 session).
10
+ */
11
+ export declare function PublicCatalogueItemDetailView({ ownerSlug, itemId }: PublicCatalogueItemDetailViewProps): React.JSX.Element;
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { Container, Div, Grid, Heading, Main, Row, Section, Skeleton, Span, Stack, Text } from "../../../ui";
6
+ import { MediaImage } from "../../media/MediaImage";
7
+ import { apiClient } from "../../../http";
8
+ import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { formatCurrency } from "../../../utils/number.formatter";
10
+ import { ROUTES } from "../../../next/routing/route-map";
11
+ const __O = {
12
+ hidden: "overflow-hidden",
13
+ };
14
+ /**
15
+ * Public detail page for a single personal-catalogue item — previously had
16
+ * no route or view at all; the public catalogue grid rendered bare, unlinked
17
+ * cards (see Recurrent Root Cause sweep, 2026-08-20 session).
18
+ */
19
+ export function PublicCatalogueItemDetailView({ ownerSlug, itemId }) {
20
+ const { data, isLoading, isError } = useQuery({
21
+ queryKey: ["catalogue", "public-item", ownerSlug, itemId],
22
+ queryFn: () => apiClient.get(ACCOUNT_ENDPOINTS.PUBLIC_CATALOGUE_ITEM(ownerSlug, itemId)),
23
+ });
24
+ if (isLoading) {
25
+ return (_jsx(Main, { children: _jsx(Section, { padding: "y-xl", children: _jsx(Container, { size: "md", children: _jsx(Skeleton, { variant: "rectangular", height: 400 }) }) }) }));
26
+ }
27
+ if (isError || !data?.item) {
28
+ return (_jsx(Main, { children: _jsx(Section, { padding: "y-5xl", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "start", gap: "md", className: "text-left", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: "Item Not Found" }), _jsx(Text, { color: "muted", children: "This catalogue item may have been made private or removed." }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "text-[length:var(--appkit-text-sm)] font-medium text-primary-600 hover:underline", children: "Back to catalogue" })] }) }) }) }));
29
+ }
30
+ const { owner, item, linkedProductHref } = data;
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" }))] })] })] }) }));
33
+ }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
3
4
  import { useQuery } from "@tanstack/react-query";
4
5
  import { Div, Grid, Heading, Skeleton, Text } from "../../../ui";
5
6
  import { MediaImage } from "../../media/MediaImage";
@@ -18,5 +19,5 @@ export function PublicCatalogueView({ ownerSlug }) {
18
19
  if (items.length === 0) {
19
20
  return _jsx(Text, { variant: "secondary", children: "Nothing public here yet." });
20
21
  }
21
- return (_jsx(Grid, { cols: 4, gap: "md", children: items.map((item) => (_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: formatCurrency(item.price) }))] }) }, item.id))) }));
22
23
  }
@@ -9,4 +9,6 @@ export { CatalogueItemEditorView } from "./components/CatalogueItemEditorView";
9
9
  export type { CatalogueItemEditorViewProps } from "./components/CatalogueItemEditorView";
10
10
  export { PublicCatalogueView } from "./components/PublicCatalogueView";
11
11
  export type { PublicCatalogueViewProps } from "./components/PublicCatalogueView";
12
+ export { PublicCatalogueItemDetailView } from "./components/PublicCatalogueItemDetailView";
13
+ export type { PublicCatalogueItemDetailViewProps } from "./components/PublicCatalogueItemDetailView";
12
14
  export { AdminCatalogueApprovalsView } from "./components/AdminCatalogueApprovalsView";
@@ -6,4 +6,5 @@ export { approveCatalogueListingAction, rejectCatalogueListingAction, } from "./
6
6
  export { UserCatalogueView } from "./components/UserCatalogueView";
7
7
  export { CatalogueItemEditorView } from "./components/CatalogueItemEditorView";
8
8
  export { PublicCatalogueView } from "./components/PublicCatalogueView";
9
+ export { PublicCatalogueItemDetailView } from "./components/PublicCatalogueItemDetailView";
9
10
  export { AdminCatalogueApprovalsView } from "./components/AdminCatalogueApprovalsView";
@@ -18,5 +18,7 @@ export interface BundleDetailViewProps {
18
18
  onBuyNow?: (input: {
19
19
  bundleSlug: string;
20
20
  }) => Promise<unknown>;
21
+ /** Other active bundles, excluding this one — renders a "Related Bundles" section when non-empty. */
22
+ relatedBundles?: CategoryDocument[];
21
23
  }
22
- export declare function BundleDetailView({ bundle, members, onBuyNow, }: BundleDetailViewProps): React.JSX.Element;
24
+ export declare function BundleDetailView({ bundle, members, onBuyNow, relatedBundles, }: BundleDetailViewProps): React.JSX.Element;
@@ -8,6 +8,7 @@ import { BundleCollage } from "./BundleCollage";
8
8
  import { MediaImage } from "../../media/MediaImage";
9
9
  import { ROUTES } from "../../../next/routing/route-map";
10
10
  import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing";
11
+ import { CategoryGrid } from "./CategoryGrid";
11
12
  const __O = {
12
13
  hidden: "overflow-hidden",
13
14
  };
@@ -16,7 +17,7 @@ const STOCK_BADGE_TEXT = {
16
17
  out_of_stock: BUNDLE_COPY.stockBadge.out_of_stock,
17
18
  };
18
19
  const PLACEHOLDER_EMOJI = "📦";
19
- export function BundleDetailView({ bundle, members = [], onBuyNow, }) {
20
+ export function BundleDetailView({ bundle, members = [], onBuyNow, relatedBundles = [], }) {
20
21
  const memberCount = members.length || bundle.bundleProductIds?.length || 0;
21
22
  const stock = bundle.bundleStockStatus ?? "in_stock";
22
23
  const cover = bundle.display?.coverImage;
@@ -24,5 +25,5 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, }) {
24
25
  ? formatCurrency(bundle.bundlePrice, "INR")
25
26
  : BUNDLE_COPY.detail.priceFallback;
26
27
  const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
27
- return (_jsx(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 })) : (_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))) }))] })] }) }) }) }));
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 })) : (_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
29
  }
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import type { startClassifiedConversationAction } from "../../../_internal/server/features/classified/actions";
3
+ export interface ClassifiedContactSellerPanelProps {
4
+ productId: string;
5
+ onContactSeller: typeof startClassifiedConversationAction;
6
+ }
7
+ /**
8
+ * The only purchase-path CTA on a classified listing — no cart, no checkout.
9
+ * Extracted from the old ClassifiedDetailView so the richer, full-parity
10
+ * ClassifiedDetailPageView can slot this into a `renderBuyBar` prop.
11
+ */
12
+ export declare function ClassifiedContactSellerPanel({ productId, onContactSeller }: ClassifiedContactSellerPanelProps): React.JSX.Element;
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { normalizeError } from "../../../errors/normalize";
4
+ import { useState } from "react";
5
+ import Link from "next/link";
6
+ import { Button, Div, Stack, Text } from "../../../ui";
7
+ import { ROUTES } from "../../../next/routing/route-map";
8
+ const __P = {
9
+ p4: "p-[var(--appkit-space-4)]",
10
+ };
11
+ /**
12
+ * The only purchase-path CTA on a classified listing — no cart, no checkout.
13
+ * Extracted from the old ClassifiedDetailView so the richer, full-parity
14
+ * ClassifiedDetailPageView can slot this into a `renderBuyBar` prop.
15
+ */
16
+ export function ClassifiedContactSellerPanel({ productId, onContactSeller }) {
17
+ const [pending, setPending] = useState(false);
18
+ const [conversation, setConversation] = useState(null);
19
+ const [error, setError] = useState(null);
20
+ async function handleContactSeller() {
21
+ setPending(true);
22
+ setError(null);
23
+ try {
24
+ const result = await onContactSeller({ productId });
25
+ if (result && typeof result === "object" && "ok" in result) {
26
+ if (result.ok) {
27
+ setConversation(result.data);
28
+ }
29
+ else {
30
+ setError(result.error);
31
+ }
32
+ }
33
+ else {
34
+ setConversation(result);
35
+ }
36
+ }
37
+ catch (e) {
38
+ void normalizeError(e);
39
+ setError(e instanceof Error ? e.message : "Something went wrong");
40
+ }
41
+ finally {
42
+ setPending(false);
43
+ }
44
+ }
45
+ if (conversation) {
46
+ return (_jsxs(Div, { className: `border border-border bg-muted/40 ${__P.p4}`, rounded: "lg", children: [_jsx(Text, { className: "mb-2", weight: "medium", children: "Conversation started!" }), _jsx(Link, { href: ROUTES.USER.MESSAGES, className: "text-primary underline underline-offset-2", children: "Go to your messages \u2192" })] }));
47
+ }
48
+ return (_jsxs(Stack, { gap: "sm", children: [error && (_jsx(Text, { className: "text-destructive", size: "sm", children: error })), _jsx(Button, { type: "button", variant: "primary", size: "lg", isLoading: pending, disabled: pending, onClick: handleContactSeller, className: "w-full", children: "Contact Seller" })] }));
49
+ }
@@ -0,0 +1,7 @@
1
+ import type { ProductDocument } from "../../products/schemas/firestore";
2
+ export interface ClassifiedDetailPageViewProps {
3
+ slug: string;
4
+ /** Pre-fetched product document — dedupes with generateMetadata() via React.cache(). */
5
+ initialProduct?: ProductDocument | null;
6
+ }
7
+ export declare function ClassifiedDetailPageView({ slug, initialProduct }: ClassifiedDetailPageViewProps): Promise<import("react").JSX.Element>;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import Link from "next/link";
3
+ import { getClassifiedForDetail } from "../../../_internal/server/features/classified/data";
4
+ import { startClassifiedConversationAction } from "../../../_internal/server/features/classified/actions";
5
+ const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
6
+ const CLS_CLASSIFIED_BADGE = "inline-block rounded-full bg-cyan-100 dark:bg-cyan-900/30 px-[var(--appkit-space-2-5)] py-[var(--appkit-space-0-5)] text-cyan-700 dark:text-cyan-300";
7
+ import { ROUTES } from "../../../next";
8
+ import { getDefaultCurrency } from "../../../core/baseline-resolver";
9
+ import { formatCurrency } from "../../../utils/number.formatter";
10
+ import { normalizeRichTextHtml } from "../../../utils/string.formatter";
11
+ import { safeDisplayName } from "../../../security";
12
+ import { Container, Div, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
13
+ import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
14
+ import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
15
+ import { ProductTabsShell } from "../../products/components/ProductTabsShell";
16
+ import { CustomSectionTabContent } from "../../products/components/CustomSectionTabContent";
17
+ import { ShareButton } from "../../products/components/ShareButton";
18
+ import { HistoryTracker } from "../../history/components/HistoryTracker";
19
+ import { RelatedItemsSection } from "../../products/components/RelatedItemsSection";
20
+ import { computeRelatedItems } from "../../../_internal/server/features/products/data";
21
+ import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
22
+ import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
23
+ import { ClassifiedContactSellerPanel } from "./ClassifiedContactSellerPanel";
24
+ function toDescriptionHtml(raw) {
25
+ if (!raw)
26
+ return "";
27
+ const s = typeof raw === "string" ? raw : JSON.stringify(raw);
28
+ return normalizeRichTextHtml(s);
29
+ }
30
+ export async function ClassifiedDetailPageView({ slug, initialProduct }) {
31
+ const product = initialProduct !== undefined
32
+ ? (initialProduct ?? undefined)
33
+ : await getClassifiedForDetail(slug).catch(() => undefined);
34
+ if (!product) {
35
+ return (_jsx(Main, { children: _jsx(Section, { padding: "y-5xl", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "start", gap: "md", className: "text-left", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: "Classified Listing Not Found" }), _jsx(Text, { color: "muted", children: "The classified listing you are looking for may have been removed." }), _jsx(Link, { href: String(ROUTES.PUBLIC.CLASSIFIED), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary-600 hover:underline", children: "Browse Classifieds" })] }) }) }) }));
36
+ }
37
+ const p = product;
38
+ const currency = p.currency || getDefaultCurrency();
39
+ const title = String(p.title ?? p.name ?? "Classified Listing");
40
+ const price = typeof p.price === "number" ? p.price : null;
41
+ const images = Array.isArray(p.images) ? p.images : typeof p.mainImage === "string" ? [p.mainImage] : [];
42
+ const meta = product.classified;
43
+ const location = meta?.meetupArea ? [meta.meetupArea.locality, meta.meetupArea.city].filter(Boolean).join(", ") : null;
44
+ const category = Array.isArray(p.categorySlugs) && p.categorySlugs.length > 0 ? String(p.categorySlugs[0]) : (typeof p.category === "string" ? p.category : null);
45
+ const categoryName = Array.isArray(p.categoryNames) && p.categoryNames.length > 0 ? String(p.categoryNames[0]) : (typeof p.categoryName === "string" ? p.categoryName : null);
46
+ const brand = typeof p.brand === "string" ? p.brand : undefined;
47
+ const storeName = typeof p.storeName === "string" ? p.storeName : null;
48
+ const safeSeller = storeName ? safeDisplayName(storeName, "") : null;
49
+ const storeSlug = (typeof p.storeSlug === "string" ? p.storeSlug : null) || (typeof p.storeId === "string" ? p.storeId : null);
50
+ const storeHref = storeSlug ? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug)) : null;
51
+ const specs = Array.isArray(p.specifications) ? p.specifications : [];
52
+ const customSections = Array.isArray(p.customSections) ? p.customSections : [];
53
+ const descriptionHtml = toDescriptionHtml(p.description);
54
+ const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups] = await Promise.all([
55
+ computeRelatedItems(product),
56
+ getGroupsWithItemsForProduct(product.id),
57
+ ]);
58
+ return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
59
+ title,
60
+ thumb: images[0],
61
+ price: price ?? undefined,
62
+ storeId: typeof p.storeId === "string" ? p.storeId : undefined,
63
+ storeName: storeName ?? undefined,
64
+ } }), _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] })] })), meta?.contactMethod && (_jsxs(Text, { size: "xs", color: "muted", children: ["Contact via: ", meta.contactMethod === "both" ? "chat or phone" : meta.contactMethod] })), 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) => ({
65
+ id: s.id,
66
+ label: s.title,
67
+ content: _jsx(CustomSectionTabContent, { section: s }),
68
+ })) })), renderBuyBar: () => (_jsx(Stack, { id: "classified-contact-bar", className: "p-[var(--appkit-space-5)]", border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: _jsx(ClassifiedContactSellerPanel, { productId: String(product.id), onContactSeller: startClassifiedConversationAction }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) })] })] }));
69
+ }
@@ -27,8 +27,14 @@ export function ContactPageView() {
27
27
  {
28
28
  icon: "💬",
29
29
  label: "WhatsApp",
30
- value: "+91 70000 00000",
31
- href: "https://wa.me/917000000000",
30
+ value: "+91 89196 65811",
31
+ href: "https://wa.me/918919665811",
32
+ },
33
+ {
34
+ icon: "👥",
35
+ label: "WhatsApp Community",
36
+ value: "Join the group",
37
+ href: "https://chat.whatsapp.com/JOlbdSwhVWYDKujolcBZDr",
32
38
  },
33
39
  ], labels: {
34
40
  title: "Support",
@@ -0,0 +1,19 @@
1
+ import type { ProductDocument } from "../../products/schemas/firestore";
2
+ export interface DigitalCodeDetailPageViewProps {
3
+ slug: string;
4
+ /** Pre-fetched product document — dedupes with generateMetadata() via React.cache(). */
5
+ initialProduct?: ProductDocument | null;
6
+ /** Render-prop for the buy bar (Add to Cart / Buy Now / Wishlist) — wired by the page shim, same component standard products use. */
7
+ renderPrimaryActions?: (ctx: {
8
+ productId: string;
9
+ productSlug: string;
10
+ productTitle: string;
11
+ productImage?: string;
12
+ price: number | null;
13
+ currency: string;
14
+ storeId?: string;
15
+ storeName?: string;
16
+ inStock: boolean;
17
+ }) => React.ReactNode;
18
+ }
19
+ export declare function DigitalCodeDetailPageView({ slug, initialProduct, renderPrimaryActions }: DigitalCodeDetailPageViewProps): Promise<import("react").JSX.Element>;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import Link from "next/link";
3
+ import { getDigitalCodeForDetail } from "../../../_internal/server/features/digital-code/data";
4
+ const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
5
+ const CLS_DIGITALCODE_BADGE = "inline-block rounded-full bg-violet-100 dark:bg-violet-900/30 px-[var(--appkit-space-2-5)] py-[var(--appkit-space-0-5)] text-violet-700 dark:text-violet-300";
6
+ import { ROUTES } from "../../../next";
7
+ import { getDefaultCurrency } from "../../../core/baseline-resolver";
8
+ import { formatCurrency } from "../../../utils/number.formatter";
9
+ import { normalizeRichTextHtml } from "../../../utils/string.formatter";
10
+ import { safeDisplayName } from "../../../security";
11
+ import { Div, Container, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui";
12
+ import { PreOrderDetailView } from "../../products/components/PreOrderDetailView";
13
+ import { ProductGalleryClient } from "../../products/components/ProductGalleryClient";
14
+ import { ProductTabsShell } from "../../products/components/ProductTabsShell";
15
+ import { CustomSectionTabContent } from "../../products/components/CustomSectionTabContent";
16
+ import { ShareButton } from "../../products/components/ShareButton";
17
+ import { HistoryTracker } from "../../history/components/HistoryTracker";
18
+ import { RelatedItemsSection } from "../../products/components/RelatedItemsSection";
19
+ import { computeRelatedItems, getReviewItemsForProduct } from "../../../_internal/server/features/products/data";
20
+ import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
21
+ import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
22
+ import { ReviewsList } from "../../reviews/components/ReviewsList";
23
+ function toDescriptionHtml(raw) {
24
+ if (!raw)
25
+ return "";
26
+ const s = typeof raw === "string" ? raw : JSON.stringify(raw);
27
+ return normalizeRichTextHtml(s);
28
+ }
29
+ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPrimaryActions }) {
30
+ const product = initialProduct !== undefined
31
+ ? (initialProduct ?? undefined)
32
+ : await getDigitalCodeForDetail(slug).catch(() => undefined);
33
+ if (!product) {
34
+ return (_jsx(Main, { children: _jsx(Section, { padding: "y-5xl", children: _jsx(Container, { size: "md", children: _jsxs(Stack, { align: "start", gap: "md", className: "text-left", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "semibold", color: "primary", children: "Digital Code Listing Not Found" }), _jsx(Text, { color: "muted", children: "The digital code listing you are looking for may have been removed." }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: "text-[length:var(--appkit-text-sm)] font-medium text-primary-600 hover:underline", children: "Browse Digital Codes" })] }) }) }) }));
35
+ }
36
+ const p = product;
37
+ const currency = p.currency || getDefaultCurrency();
38
+ const title = String(p.title ?? p.name ?? "Digital Code");
39
+ const price = typeof p.price === "number" ? p.price : null;
40
+ const images = Array.isArray(p.images) ? p.images : typeof p.mainImage === "string" ? [p.mainImage] : [];
41
+ const meta = product.digitalCode;
42
+ const codesLeft = meta?.codesAvailable ?? 0;
43
+ const category = Array.isArray(p.categorySlugs) && p.categorySlugs.length > 0 ? String(p.categorySlugs[0]) : (typeof p.category === "string" ? p.category : null);
44
+ const categoryName = Array.isArray(p.categoryNames) && p.categoryNames.length > 0 ? String(p.categoryNames[0]) : (typeof p.categoryName === "string" ? p.categoryName : null);
45
+ const brand = typeof p.brand === "string" ? p.brand : undefined;
46
+ const storeName = typeof p.storeName === "string" ? p.storeName : null;
47
+ const safeSeller = storeName ? safeDisplayName(storeName, "") : null;
48
+ const storeSlug = (typeof p.storeSlug === "string" ? p.storeSlug : null) || (typeof p.storeId === "string" ? p.storeId : null);
49
+ const storeHref = storeSlug ? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug)) : null;
50
+ const specs = Array.isArray(p.specifications) ? p.specifications : [];
51
+ const customSections = Array.isArray(p.customSections) ? p.customSections : [];
52
+ const descriptionHtml = toDescriptionHtml(p.description);
53
+ const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, reviews] = await Promise.all([
54
+ computeRelatedItems(product),
55
+ getGroupsWithItemsForProduct(product.id),
56
+ getReviewItemsForProduct(product.id),
57
+ ]);
58
+ return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "product", snapshot: {
59
+ title,
60
+ thumb: images[0],
61
+ price: price ?? undefined,
62
+ storeId: typeof p.storeId === "string" ? p.storeId : undefined,
63
+ storeName: storeName ?? undefined,
64
+ } }), _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(ReviewsList, { reviews: reviews, context: "listing", emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
65
+ id: s.id,
66
+ label: s.title,
67
+ content: _jsx(CustomSectionTabContent, { section: s }),
68
+ })) })), renderBuyBar: () => renderPrimaryActions?.({
69
+ productId: String(product.id),
70
+ productSlug: String(p.slug ?? product.id),
71
+ productTitle: title,
72
+ productImage: images[0],
73
+ price,
74
+ currency,
75
+ storeId: typeof p.storeId === "string" ? p.storeId : undefined,
76
+ storeName: storeName ?? undefined,
77
+ inStock: codesLeft > 0,
78
+ }), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand, storeLabel: storeName ?? undefined })] })) })] })] }));
79
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import type { GroupedListingWithItems } from "../../../_internal/server/features/grouped/data";
3
+ export interface GroupedListingsCarouselProps {
4
+ groups: GroupedListingWithItems[];
5
+ }
6
+ /**
7
+ * Public renderer for the admin/seller-authored groupedListings "theme
8
+ * scroller" collection — one carousel per group the current product
9
+ * belongs to. Previously fully built (schema, repository, CRUD UI) with
10
+ * zero public consumers; this is the first place it renders on a
11
+ * storefront page.
12
+ */
13
+ export declare function GroupedListingsCarousel({ groups }: GroupedListingsCarouselProps): React.JSX.Element | null;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
4
+ import { SectionCarousel } from "../../homepage/components/SectionCarousel";
5
+ import { ProductCard } from "../../products/components/ProductGrid";
6
+ import { toProductItem } from "../../../_internal/server/features/products/data";
7
+ import { CAROUSEL_PER_VIEW } from "../../homepage/constants/carousel-per-view";
8
+ import { Stack } from "../../../ui";
9
+ const GROUP_THEME_TITLE = {
10
+ related: "Other listings you might like",
11
+ character: "Same character",
12
+ lineage: "Same lineage",
13
+ set: "From the same set",
14
+ generic: "You might also like",
15
+ };
16
+ /**
17
+ * Public renderer for the admin/seller-authored groupedListings "theme
18
+ * scroller" collection — one carousel per group the current product
19
+ * belongs to. Previously fully built (schema, repository, CRUD UI) with
20
+ * zero public consumers; this is the first place it renders on a
21
+ * storefront page.
22
+ */
23
+ export function GroupedListingsCarousel({ groups }) {
24
+ const visibleGroups = groups.filter((g) => g.items.length > 0);
25
+ if (visibleGroups.length === 0)
26
+ return null;
27
+ return (_jsx(Stack, { gap: "xl", children: visibleGroups.map((group) => {
28
+ const items = group.items.map((p) => toProductItem(p));
29
+ return (_jsx(SectionCarousel, { title: group.title || GROUP_THEME_TITLE[group.groupTheme], pillLabel: "Grouped Listings", headingVariant: "editorial", items: items, isLoading: false, perView: CAROUSEL_PER_VIEW.standard, keyExtractor: (item) => item.id, renderItem: (item) => (_jsx(ProductCard, { product: item, href: item.slug || item.id ? pluginFor(item.listingType ?? "standard").detailRoute(item.slug || item.id) : undefined })) }, group.id));
30
+ }) }));
31
+ }
@@ -5,5 +5,13 @@ export declare class GroupedListingsRepository extends BaseRepository<GroupedLis
5
5
  listByStore(storeId: string): Promise<{
6
6
  items: GroupedListingDocument[];
7
7
  }>;
8
+ /**
9
+ * Groups a product belongs to, restricted to groups that are actually
10
+ * eligible to render publicly (`isActive` + `visibilityStatus:"visible"` —
11
+ * the latter is recomputed by `onProductStockChange` whenever membership
12
+ * drops below `minActiveMembers`, so this is a read-side trust of that
13
+ * precomputed field, not a fresh recompute).
14
+ */
15
+ findByProductId(productId: string): Promise<GroupedListingDocument[]>;
8
16
  }
9
17
  export declare const groupedListingsRepository: GroupedListingsRepository;
@@ -1,4 +1,5 @@
1
1
  import { BaseRepository } from "../../../providers/db-firebase";
2
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
2
3
  import { GROUPED_LISTINGS_COLLECTION, } from "../schemas/firestore";
3
4
  export class GroupedListingsRepository extends BaseRepository {
4
5
  constructor() {
@@ -8,5 +9,20 @@ export class GroupedListingsRepository extends BaseRepository {
8
9
  const items = await this.findBy("storeId", storeId);
9
10
  return { items };
10
11
  }
12
+ /**
13
+ * Groups a product belongs to, restricted to groups that are actually
14
+ * eligible to render publicly (`isActive` + `visibilityStatus:"visible"` —
15
+ * the latter is recomputed by `onProductStockChange` whenever membership
16
+ * drops below `minActiveMembers`, so this is a read-side trust of that
17
+ * precomputed field, not a fresh recompute).
18
+ */
19
+ async findByProductId(productId) {
20
+ const snap = await this.getCollection()
21
+ .where("productIds", "array-contains", productId)
22
+ .where(PRODUCT_FIELDS.IS_ACTIVE, "==", true)
23
+ .where("visibilityStatus", "==", "visible")
24
+ .get();
25
+ return snap.docs.map((doc) => this.mapDoc(doc));
26
+ }
11
27
  }
12
28
  export const groupedListingsRepository = new GroupedListingsRepository();
@@ -8,6 +8,7 @@ import { Button, Div, Heading, HorizontalScroller, Row, Section, Text } from "..
8
8
  import { ROUTES } from "../../../next";
9
9
  import { resolveMediaUrl } from "../../../utils/media-url";
10
10
  import { useTopBrands } from "../hooks/useTopBrands";
11
+ import { useHandMode } from "../../../_internal/client/hand-mode";
11
12
  const __O = {
12
13
  hidden: "overflow-hidden",
13
14
  };
@@ -32,6 +33,7 @@ function BrandLogo({ brand }) {
32
33
  }
33
34
  export function BrandsSection({ title = "Top Brands", subtitle, limit = 12, viewMoreHref, viewMoreLabel = "All brands →", className = "", initialItems, cta, filters, autoScroll = true, scrollInterval = 4000, loop = true, }) {
34
35
  const themed = { textPrimary: THEMED_TEXT_PRIMARY };
36
+ const { hand } = useHandMode();
35
37
  const { data: allBrands = [], isLoading } = useTopBrands(limit, { initialData: initialItems });
36
38
  const [activeFilter, setActiveFilter] = useState("all");
37
39
  // Available filter chips: "All" + "Featured" when featuredOnly filter is configured
@@ -43,5 +45,5 @@ export function BrandsSection({ title = "Top Brands", subtitle, limit = 12, view
43
45
  });
44
46
  if (!isLoading && allBrands.length === 0)
45
47
  return null;
46
- return (_jsx(Section, { className: `${className}`, surface: "subtle", paddingY: "y-3xl", paddingX: "x-md", children: _jsxs(Div, { className: "mx-auto max-w-7xl", children: [_jsxs(Row, { className: "mb-6", align: "center", justify: "between", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: themed.textPrimary, size: "2xl", mdSize: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: subtitle }))] }), !cta && viewMoreHref && (_jsx(Link, { href: viewMoreHref, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: viewMoreLabel }))] }), showFeaturedChip && !isLoading && (_jsxs(Row, { wrap: true, gap: "sm", className: "mb-4", children: [_jsx(BrandFilterChip, { label: "All", active: activeFilter === "all", onClick: () => setActiveFilter("all") }), _jsx(BrandFilterChip, { label: "Featured", active: activeFilter === "featured", onClick: () => setActiveFilter(activeFilter === "featured" ? "all" : "featured") })] })), isLoading ? (_jsx(Div, { layout: "flex", gap: "3", className: `${__O.hidden}`, children: Array.from({ length: 8 }).map((_, i) => (_jsx(Div, { className: "h-32 w-36 flex-none animate-pulse md:h-40", surface: "subtle", rounded: "xl" }, i))) })) : (_jsx(HorizontalScroller, { items: brands, renderItem: (brand) => _jsx(BrandLogo, { brand: brand }), keyExtractor: (brand) => brand.id, gap: 12, showArrows: true, showScrollbar: false, autoScroll: autoScroll, autoScrollInterval: scrollInterval, pauseOnHover: true, loop: loop })), cta && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) }))] }) }));
48
+ return (_jsx(Section, { className: `${className}`, surface: "subtle", paddingY: "y-3xl", paddingX: "x-md", children: _jsxs(Div, { className: "mx-auto max-w-7xl", children: [_jsxs(Row, { className: "mb-6", align: "center", justify: "between", reverse: hand === "left", children: [_jsxs(_Fragment, { children: [_jsx(Heading, { level: 2, className: themed.textPrimary, size: "2xl", mdSize: "3xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { variant: "secondary", className: "mt-1", size: "sm", children: subtitle }))] }), !cta && viewMoreHref && (_jsx(Link, { href: viewMoreHref, className: "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-primary)] hover:underline", children: viewMoreLabel }))] }), showFeaturedChip && !isLoading && (_jsxs(Row, { wrap: true, gap: "sm", className: "mb-4", children: [_jsx(BrandFilterChip, { label: "All", active: activeFilter === "all", onClick: () => setActiveFilter("all") }), _jsx(BrandFilterChip, { label: "Featured", active: activeFilter === "featured", onClick: () => setActiveFilter(activeFilter === "featured" ? "all" : "featured") })] })), isLoading ? (_jsx(Div, { layout: "flex", gap: "3", className: `${__O.hidden}`, children: Array.from({ length: 8 }).map((_, i) => (_jsx(Div, { className: "h-32 w-36 flex-none animate-pulse md:h-40", surface: "subtle", rounded: "xl" }, i))) })) : (_jsx(HorizontalScroller, { items: brands, renderItem: (brand) => _jsx(BrandLogo, { brand: brand }), keyExtractor: (brand) => brand.id, gap: 12, showArrows: true, showScrollbar: false, autoScroll: autoScroll, autoScrollInterval: scrollInterval, pauseOnHover: true, loop: loop })), cta && !isLoading && (_jsx(Div, { className: "mt-6 text-left", children: _jsx(Link, { href: cta.href, className: CTA_CLASSES[cta.variant], children: cta.label }) }))] }) }));
47
49
  }