@mohasinac/appkit 4.64.0 → 4.65.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 (163) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/checkout/actions.js +18 -1
  3. package/dist/_internal/server/features/search/index-build/sources.js +27 -12
  4. package/dist/_internal/server/features/seo/sitemap.js +78 -12
  5. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  6. package/dist/_internal/server/features/site-settings/adapters.js +16 -1
  7. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  8. package/dist/_internal/server/features/stores/adapters.js +14 -0
  9. package/dist/_internal/server/features/stores/tabs.d.ts +19 -15
  10. package/dist/_internal/server/features/stores/tabs.js +20 -21
  11. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +9 -4
  12. package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.js +22 -0
  13. package/dist/_internal/server/jobs/core/countersReconcile.js +0 -1
  14. package/dist/_internal/server/jobs/handlers/onCatalogueSubmittedForApproval.js +1 -1
  15. package/dist/_internal/shared/features/site-settings/types.d.ts +10 -2
  16. package/dist/_internal/shared/features/sticker-trace/trace/segment.js +13 -2
  17. package/dist/_internal/shared/listing-types/feature-flags.d.ts +54 -0
  18. package/dist/_internal/shared/listing-types/feature-flags.js +72 -0
  19. package/dist/client.d.ts +5 -2
  20. package/dist/client.js +8 -1
  21. package/dist/constants/field-names.d.ts +1 -0
  22. package/dist/constants/field-names.js +14 -0
  23. package/dist/features/account/api/[userId]/addresses/route.js +1 -5
  24. package/dist/features/addresses/repository/addresses.repository.js +1 -1
  25. package/dist/features/admin/api/bids/route.js +1 -5
  26. package/dist/features/admin/api/coupons/route.js +1 -5
  27. package/dist/features/admin/api/products/route.js +1 -5
  28. package/dist/features/admin/api/reviews/route.js +1 -5
  29. package/dist/features/admin/components/AdminCarouselEditorView.js +0 -1
  30. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -2
  31. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  32. package/dist/features/admin/components/AdminProductEditorView.js +4 -1
  33. package/dist/features/admin/components/AdminSiteSettingsView.js +19 -3
  34. package/dist/features/admin/components/OwnerQuickActions.d.ts +7 -1
  35. package/dist/features/admin/components/OwnerQuickActions.js +1 -1
  36. package/dist/features/admin/schemas/admin-editor-forms.js +0 -1
  37. package/dist/features/admin/schemas/firestore.d.ts +52 -0
  38. package/dist/features/admin/schemas/firestore.js +20 -0
  39. package/dist/features/auctions/api/route.js +1 -8
  40. package/dist/features/auctions/components/AuctionDetailPageView.js +1 -7
  41. package/dist/features/auth/hooks/useAuth.js +1 -1
  42. package/dist/features/before-after/api/route.js +1 -5
  43. package/dist/features/blog/api/route.js +1 -8
  44. package/dist/features/blog/components/BlogIndexPageView.d.ts +2 -3
  45. package/dist/features/blog/components/BlogIndexPageView.js +6 -9
  46. package/dist/features/cart/actions/cart-actions.js +1 -1
  47. package/dist/features/cart/api/route.js +1 -5
  48. package/dist/features/cart/utils/guest-cart.js +1 -5
  49. package/dist/features/catalogue/repository/catalogue.repository.js +1 -1
  50. package/dist/features/categories/api/route.js +5 -23
  51. package/dist/features/categories/components/BundlesListView.d.ts +2 -3
  52. package/dist/features/categories/components/BundlesListView.js +2 -5
  53. package/dist/features/categories/components/CategoriesIndexListing.js +47 -5
  54. package/dist/features/categories/components/CategoriesIndexPageView.d.ts +2 -3
  55. package/dist/features/categories/components/CategoriesIndexPageView.js +0 -32
  56. package/dist/features/categories/constants/category-tabs.d.ts +1 -1
  57. package/dist/features/categories/constants/category-tabs.js +7 -0
  58. package/dist/features/categories/hooks/useCategories.d.ts +6 -0
  59. package/dist/features/categories/hooks/useCategories.js +73 -2
  60. package/dist/features/categories/types/index.d.ts +1 -1
  61. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -8
  62. package/dist/features/collections/api/route.js +1 -5
  63. package/dist/features/consultation/api/route.js +1 -5
  64. package/dist/features/contact/email.js +2 -5
  65. package/dist/features/corporate/api/route.js +1 -5
  66. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +1 -7
  67. package/dist/features/events/components/AdminEventEditorView.js +1 -1
  68. package/dist/features/events/components/EventsListPageView.d.ts +2 -3
  69. package/dist/features/events/components/EventsListPageView.js +8 -11
  70. package/dist/features/faq/constants/help-guides.d.ts +8 -5
  71. package/dist/features/faq/constants/help-guides.js +9 -5
  72. package/dist/features/filters/AsyncFacetSection.js +2 -2
  73. package/dist/features/filters/FilterFacetSection.js +5 -5
  74. package/dist/features/filters/RangeFilter.js +2 -2
  75. package/dist/features/filters/SwitchFilter.js +2 -2
  76. package/dist/features/filters/filterUtils.d.ts +0 -2
  77. package/dist/features/filters/filterUtils.js +0 -4
  78. package/dist/features/filters/index.d.ts +1 -1
  79. package/dist/features/filters/index.js +1 -1
  80. package/dist/features/forms/Checkbox.js +4 -3
  81. package/dist/features/forms/Input.js +5 -4
  82. package/dist/features/forms/Radio.js +6 -5
  83. package/dist/features/forms/Select.js +3 -2
  84. package/dist/features/forms/Slider.js +3 -2
  85. package/dist/features/forms/Textarea.js +3 -2
  86. package/dist/features/forms/Toggle.js +3 -3
  87. package/dist/features/forms/index.d.ts +1 -1
  88. package/dist/features/forms/index.js +1 -1
  89. package/dist/features/forms/utils.d.ts +0 -2
  90. package/dist/features/forms/utils.js +0 -4
  91. package/dist/features/grouped/schemas/grouped-listing-form.d.ts +6 -6
  92. package/dist/features/grouped/schemas/index.d.ts +2 -2
  93. package/dist/features/history/utils/guest-history.js +1 -5
  94. package/dist/features/homepage/components/CustomCardsSection.js +8 -2
  95. package/dist/features/homepage/components/FeaturedBundlesSection.d.ts +6 -1
  96. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
  97. package/dist/features/layout/AppLayoutShell.d.ts +23 -1
  98. package/dist/features/layout/AppLayoutShell.js +13 -12
  99. package/dist/features/layout/BottomNavItem.d.ts +9 -1
  100. package/dist/features/layout/BottomNavItem.js +18 -4
  101. package/dist/features/layout/index.d.ts +1 -1
  102. package/dist/features/live/components/LiveItemDetailPageView.js +1 -7
  103. package/dist/features/media/MediaLightbox.js +3 -5
  104. package/dist/features/orders/components/OrderCompletionPanel.d.ts +1 -1
  105. package/dist/features/orders/components/OrderCompletionPanel.js +1 -1
  106. package/dist/features/orders/components/RaiseDisputeForm.js +1 -1
  107. package/dist/features/orders/components/RefundRequestView.js +1 -1
  108. package/dist/features/parts/schemas/firestore.d.ts +274 -0
  109. package/dist/features/parts/schemas/firestore.js +19 -0
  110. package/dist/features/payments/repository/saved-payment-methods.repository.js +1 -1
  111. package/dist/features/pre-orders/api/admin/route.js +1 -5
  112. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -7
  113. package/dist/features/products/api/route.js +1 -3
  114. package/dist/features/products/components/CustomSectionTabContent.js +0 -5
  115. package/dist/features/products/components/GroupMemberPicker.js +2 -2
  116. package/dist/features/products/components/GroupSettingsPanel.js +1 -1
  117. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -7
  118. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -1
  119. package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
  120. package/dist/features/products/components/ProductDetailPageView.js +1 -7
  121. package/dist/features/products/repository/products.repository.js +0 -1
  122. package/dist/features/promotions/api/route.js +1 -8
  123. package/dist/features/reviews/components/ReviewsIndexPageView.d.ts +2 -3
  124. package/dist/features/reviews/components/ReviewsIndexPageView.js +9 -12
  125. package/dist/features/seller/api/coupons/route.js +1 -5
  126. package/dist/features/seller/api/products/route.js +1 -5
  127. package/dist/features/seller/components/SellerProductShell.js +1 -2
  128. package/dist/features/seller/hooks/useSellerListingData.js +0 -30
  129. package/dist/features/stores/api/[storeSlug]/auctions/route.js +1 -10
  130. package/dist/features/stores/api/[storeSlug]/products/route.js +1 -10
  131. package/dist/features/stores/components/InteractiveStoreCard.js +9 -1
  132. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  133. package/dist/features/stores/components/StoresIndexPageView.d.ts +2 -3
  134. package/dist/features/stores/components/StoresIndexPageView.js +6 -9
  135. package/dist/features/stores/schemas/firestore.d.ts +27 -0
  136. package/dist/features/stores/schemas/firestore.js +2 -0
  137. package/dist/features/wishlist/utils/guest-wishlist.js +1 -5
  138. package/dist/index.d.ts +4 -1
  139. package/dist/index.js +28 -1
  140. package/dist/providers/db-firebase/admin-app-lite.js +14 -2
  141. package/dist/providers/db-firebase/admin.js +8 -2
  142. package/dist/react/hooks/useBrowsePageFlags.d.ts +26 -0
  143. package/dist/react/hooks/useBrowsePageFlags.js +18 -0
  144. package/dist/schemas/registry.d.ts +4 -4
  145. package/dist/security/csp.d.ts +32 -2
  146. package/dist/security/csp.js +32 -2
  147. package/dist/styles.css +1 -1
  148. package/dist/tailwind-utilities.css +1 -1
  149. package/dist/ui/components/Button.js +1 -1
  150. package/dist/ui/components/Chart.js +0 -1
  151. package/dist/ui/rich-text/RichText.js +1 -3
  152. package/dist/ui/rich-text/RichTextRenderer.js +1 -3
  153. package/dist/utils/browser-storage.d.ts +14 -0
  154. package/dist/utils/browser-storage.js +18 -0
  155. package/dist/utils/classnames.d.ts +19 -0
  156. package/dist/utils/classnames.js +21 -0
  157. package/dist/utils/search-params.d.ts +44 -0
  158. package/dist/utils/search-params.js +46 -0
  159. package/dist/utils/sieve-builder.d.ts +13 -0
  160. package/dist/utils/sieve-builder.js +22 -0
  161. package/dist/utils/string.formatter.d.ts +12 -0
  162. package/dist/utils/string.formatter.js +17 -0
  163. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link.js";
4
- import { Div, Heading, HorizontalScroller, PlainCard, PLAIN_CLASS, PosterCard, PosterGrid, POSTER_CLASS, Section, } from "../../../ui/index.js";
4
+ import { Div, Heading, HorizontalScroller, PlainCard, PLAIN_CLASS, PosterCard, PosterGrid, POSTER_CLASS, Section, Anchor, } from "../../../ui/index.js";
5
5
  import { THEMED_TEXT_PRIMARY } from "../../../_internal/shared/styles/themed.js";
6
6
  /* Not re-exported from `ui/index` — import from the defining module (Root Cause #18). */
7
7
  import { Iframe } from "../../../ui/components/Iframe.js";
@@ -22,7 +22,13 @@ function isKeyChip(variant) {
22
22
  function Chip({ button, className, }) {
23
23
  const external = button.target === "_blank" || /^https?:\/\//.test(button.href);
24
24
  if (external) {
25
- return (_jsx("a", { href: button.href, className: className, target: button.target ?? "_blank", rel: "noopener noreferrer", children: button.label }));
25
+ /*
26
+ * 🛑 `<Anchor>`, not a raw `<a>`. It owns the target+rel pair, so an external
27
+ * link cannot ship `target="_blank"` without `rel="noopener noreferrer"` —
28
+ * that is a tabnabbing hole, not a style preference. This branch had both
29
+ * written out by hand and correct; the point is that the next one will not be.
30
+ */
31
+ return (_jsx(Anchor, { href: button.href, className: className, external: true, target: button.target ?? "_blank", children: button.label }));
26
32
  }
27
33
  return (_jsx(Link, { href: button.href, className: className, children: button.label }));
28
34
  }
@@ -15,6 +15,11 @@ export interface FeaturedBundlesSectionProps {
15
15
  /** Cap the rendered bundles. Items are supplied pre-fetched, so this slices. */
16
16
  maxItems?: number;
17
17
  /** Show the "N% off" badge on each cover. Defaults to `true`. */
18
+ /**
19
+ * 🛑 ACCEPTED AND NOT READ — the savings badge renders unconditionally, so
20
+ * passing `false` has never hidden it. Declared because callers pass it;
21
+ * either wire it to the badge or delete it with its 7 call sites.
22
+ */
18
23
  showSavingsBadge?: boolean;
19
24
  /** "View all" link label. */
20
25
  viewMoreLabel?: string;
@@ -23,4 +28,4 @@ export interface FeaturedBundlesSectionProps {
23
28
  bundleSlug: string;
24
29
  }) => Promise<unknown>;
25
30
  }
26
- export declare function FeaturedBundlesSection({ title, description, initialItems, maxItems, showSavingsBadge, viewMoreLabel, className, onBuyNow, }: FeaturedBundlesSectionProps): React.JSX.Element | null;
31
+ export declare function FeaturedBundlesSection({ title, description, initialItems, maxItems, viewMoreLabel, className, onBuyNow, }: FeaturedBundlesSectionProps): React.JSX.Element | null;
@@ -24,7 +24,7 @@ import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundl
24
24
  import { MarketplaceBundleCard } from "../../products/components/MarketplaceBundleCard.js";
25
25
  import { BundleBuyNowCta } from "../../categories/components/BundleBuyNowCta.js";
26
26
  import { useHandMode } from "../../../_internal/client/hand-mode/index.js";
27
- export function FeaturedBundlesSection({ title = BUNDLE_COPY.featuredDefaultTitle, description, initialItems = [], maxItems, showSavingsBadge = true, viewMoreLabel = BUNDLE_COPY.featured.viewAll, className = "", onBuyNow, }) {
27
+ export function FeaturedBundlesSection({ title = BUNDLE_COPY.featuredDefaultTitle, description, initialItems = [], maxItems, viewMoreLabel = BUNDLE_COPY.featured.viewAll, className = "", onBuyNow, }) {
28
28
  const { hand } = useHandMode();
29
29
  const bundles = initialItems.filter((c) => c.categoryType === "bundle");
30
30
  const items = maxItems && maxItems > 0 ? bundles.slice(0, maxItems) : bundles;
@@ -26,6 +26,22 @@ export interface AppLayoutShellSidebarSection {
26
26
  /** When true, this section is expanded by default. */
27
27
  defaultOpen?: boolean;
28
28
  }
29
+ /**
30
+ * Trailing control rendered beside a sidebar link — the seam for an admin's
31
+ * inline "hide this from the nav" toggle.
32
+ *
33
+ * 🛑 A SLOT, not a built-in toggle. appkit has no idea what `navConfig` is,
34
+ * which role may write it, or what a write costs; the consumer owns all three.
35
+ * Baking an admin control into a layout primitive would also put a write path
36
+ * inside a component every visitor renders.
37
+ *
38
+ * 🛑 Whatever it returns is rendered OUTSIDE the `<TextLink>`, as a sibling.
39
+ * A `<button>` inside an `<a>` is invalid HTML that browsers resolve by
40
+ * splitting the anchor — the same trap `InteractiveStoreCard` documents for
41
+ * its trust badges. That is why the row became a flex container rather than
42
+ * the control simply being appended to the link's children.
43
+ */
44
+ export type SidebarItemActionRenderer = (item: AppLayoutShellSidebarLink) => React.ReactNode;
29
45
  export interface AppLayoutShellSidebarAction {
30
46
  href: string;
31
47
  label: string;
@@ -36,6 +52,12 @@ export interface AppLayoutShellProps {
36
52
  navItems: MainNavbarItem[];
37
53
  sidebarItems?: AppLayoutShellSidebarLink[];
38
54
  sidebarSections?: AppLayoutShellSidebarSection[];
55
+ /**
56
+ * Trailing control beside each sidebar link — the admin inline-toggle seam.
57
+ * See `SidebarItemActionRenderer`. Omitted for every non-admin viewer, so a
58
+ * normal visitor renders exactly what they rendered before.
59
+ */
60
+ renderSidebarItemAction?: SidebarItemActionRenderer;
39
61
  sidebarPrimaryActions?: AppLayoutShellSidebarAction[];
40
62
  sidebarTitle?: string;
41
63
  hiddenNavItems?: string[];
@@ -156,4 +178,4 @@ export interface AppLayoutShellProps {
156
178
  };
157
179
  };
158
180
  }
159
- export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, showBottomNav, contentClassName, lightBackground, darkBackground, }: AppLayoutShellProps): React.JSX.Element;
181
+ export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, showBottomNav, contentClassName, lightBackground, darkBackground, }: AppLayoutShellProps): React.JSX.Element;
@@ -74,7 +74,7 @@ function flattenSidebarLinks(items) {
74
74
  * no caret small enough to aim at reliably — the tap has to do both jobs, in
75
75
  * order.
76
76
  */
77
- function SidebarParentLink({ item, activeItem, activeHref, navItemClass, navItemActiveClass, }) {
77
+ function SidebarParentLink({ item, activeItem, activeHref, navItemClass, navItemActiveClass, renderItemAction, }) {
78
78
  const children = item.children ?? [];
79
79
  const containsActive = !!activeItem && children.some((child) => child.href === activeItem.href);
80
80
  const [expanded, setExpanded] = useState(containsActive);
@@ -92,19 +92,20 @@ function SidebarParentLink({ item, activeItem, activeHref, navItemClass, navItem
92
92
  return;
93
93
  e.preventDefault();
94
94
  setExpanded(true);
95
- }, children: [item.icon && (_jsx(Span, { className: "flex-shrink-0 w-5 text-center", "aria-hidden": "true", children: item.icon })), item.label, _jsx(ChevronDown, { className: `ms-auto w-3.5 h-3.5 shrink-0 opacity-70 transition-transform duration-200 ${expanded ? "rotate-180" : ""}`, "aria-hidden": "true" })] }), expanded && (_jsx(SidebarSectionLinks, { items: children, activeHref: activeHref, activeItem: activeItem, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, nested: true }))] }));
95
+ }, children: [item.icon && (_jsx(Span, { className: "flex-shrink-0 w-5 text-center", "aria-hidden": "true", children: item.icon })), item.label, _jsx(ChevronDown, { className: `ms-auto w-3.5 h-3.5 shrink-0 opacity-70 transition-transform duration-200 ${expanded ? "rotate-180" : ""}`, "aria-hidden": "true" })] }), expanded && (_jsx(SidebarSectionLinks, { items: children, activeHref: activeHref, activeItem: activeItem, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, nested: true, renderItemAction: renderItemAction }))] }));
96
96
  }
97
97
  /** One sidebar link plus its indented sub-links. */
98
- function SidebarSectionLinks({ items, activeHref, activeItem, navItemClass, navItemActiveClass, nested = false, }) {
98
+ function SidebarSectionLinks({ items, activeHref, activeItem, navItemClass, navItemActiveClass, nested = false, renderItemAction, }) {
99
99
  return (_jsx(Ul, { spacing: "2xs", className: nested ? "ps-[var(--appkit-space-6)]" : undefined, children: items.map((item) => {
100
100
  const isActive = activeItem?.href === item.href;
101
101
  if (item.children && item.children.length > 0) {
102
- return (_jsx(SidebarParentLink, { item: item, activeItem: activeItem, activeHref: activeHref, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass }, `${item.href}-${item.label}`));
102
+ return (_jsx(SidebarParentLink, { item: item, activeItem: activeItem, activeHref: activeHref, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, renderItemAction: renderItemAction }, `${item.href}-${item.label}`));
103
103
  }
104
- return (_jsx(Li, { children: _jsxs(TextLink, { href: item.href, variant: "none", className: isActive ? navItemActiveClass : navItemClass, "aria-current": isActive ? "page" : undefined, children: [item.icon && (_jsx(Span, { className: "flex-shrink-0 w-5 text-center", "aria-hidden": "true", children: item.icon })), item.label] }) }, `${item.href}-${item.label}`));
104
+ const action = renderItemAction?.(item);
105
+ return (_jsx(Li, { children: _jsxs(Row, { align: "center", gap: "xs", className: action ? "w-full" : undefined, children: [_jsxs(TextLink, { href: item.href, variant: "none", className: `${isActive ? navItemActiveClass : navItemClass}${action ? " min-w-0 flex-1" : ""}`, "aria-current": isActive ? "page" : undefined, children: [item.icon && (_jsx(Span, { className: "flex-shrink-0 w-5 text-center", "aria-hidden": "true", children: item.icon })), item.label] }), action] }) }, `${item.href}-${item.label}`));
105
106
  }) }));
106
107
  }
107
- function CollapsibleSidebarSection({ section, navItemClass, navItemActiveClass, activeHref, }) {
108
+ function CollapsibleSidebarSection({ section, navItemClass, navItemActiveClass, activeHref, renderItemAction, }) {
108
109
  const activeItem = findActiveNavItem(flattenSidebarLinks(section.items), activeHref);
109
110
  const hasActive = !!activeItem;
110
111
  const [open, setOpen] = useState((section.defaultOpen ?? false) || hasActive);
@@ -116,9 +117,9 @@ function CollapsibleSidebarSection({ section, navItemClass, navItemActiveClass,
116
117
  }, [hasActive]);
117
118
  const hasTitle = !!section.title;
118
119
  if (!hasTitle) {
119
- return (_jsx(SidebarSectionLinks, { items: section.items, activeHref: activeHref, activeItem: activeItem, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass }));
120
+ return (_jsx(SidebarSectionLinks, { items: section.items, activeHref: activeHref, activeItem: activeItem, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, renderItemAction: renderItemAction }));
120
121
  }
121
- return (_jsxs(Stack, { gap: "none", className: "", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center justify-between px-[var(--appkit-space-1)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-semibold uppercase tracking-wider text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text-muted)] transition-colors", children: [_jsx(Span, { children: section.title }), _jsx("svg", { className: `w-3.5 h-3.5 transition-transform duration-200 ${open ? "rotate-180" : ""} shrink-0`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), open && (_jsx(SidebarSectionLinks, { items: section.items, activeHref: activeHref, activeItem: activeItem, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass }))] }));
122
+ return (_jsxs(Stack, { gap: "none", className: "", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "flex w-full items-center justify-between px-[var(--appkit-space-1)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-semibold uppercase tracking-wider text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text-muted)] transition-colors", children: [_jsx(Span, { children: section.title }), _jsx("svg", { className: `w-3.5 h-3.5 transition-transform duration-200 ${open ? "rotate-180" : ""} shrink-0`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), open && (_jsx(SidebarSectionLinks, { items: section.items, activeHref: activeHref, activeItem: activeItem, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, renderItemAction: renderItemAction }))] }));
122
123
  }
123
124
  /** Sidebar header when a user is logged in — avatar + display name + close button. */
124
125
  function SidebarUserHeader({ user, onClose, profileHref, }) {
@@ -136,7 +137,7 @@ function SidebarUserHeader({ user, onClose, profileHref, }) {
136
137
  function SidebarGuestHeader({ sidebarTitle, onClose, }) {
137
138
  return (_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Div, { textWeight: "semibold", textSize: "sm", color: "primary", children: sidebarTitle }), _jsx("button", { type: "button", "aria-label": "Close menu", onClick: onClose, className: "rounded-full p-[var(--appkit-space-2)] hover:bg-surface-hover hover:text-zinc-900 text-[var(--appkit-color-text-muted)] dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }));
138
139
  }
139
- function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions, user, activeHref, profileHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, adminHref, storeHref, sellerHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, theme, activeThemeName, toggleTheme, onLogout, onAfterLogout, }) {
140
+ function SidebarContent({ sidebarItems, sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, user, activeHref, profileHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, adminHref, storeHref, sellerHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, theme, activeThemeName, toggleTheme, onLogout, onAfterLogout, }) {
140
141
  const hasLegacyItems = sidebarItems.length > 0;
141
142
  const hasSections = !!(sidebarSections && sidebarSections.length > 0);
142
143
  const isAuthenticated = !!user;
@@ -185,10 +186,10 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
185
186
  action.variant === "outline"
186
187
  ? "border hover:bg-surface-hover border-[var(--appkit-color-border)] text-[var(--appkit-color-text)]"
187
188
  : "bg-primary text-white hover:bg-primary-600 dark:bg-primary dark:hover:bg-primary-600 btn-glow",
188
- ].join(" "), children: action.label }, `${action.href}-${action.label}`))) })), isAuthenticated && (_jsx(CollapsibleNavGroup, { title: labels.sectionTitle, hasActive: !!activeProfileLink, children: _jsxs(Ul, { spacing: "2xs", children: [_jsx(Li, { children: _jsx(TextLink, { href: profileHref, variant: "none", className: activeProfileLink?.href === profileHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === profileHref ? "page" : undefined, children: labels.profile }) }), userOrdersHref && (_jsx(Li, { children: _jsx(TextLink, { href: userOrdersHref, variant: "none", className: activeProfileLink?.href === userOrdersHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userOrdersHref ? "page" : undefined, children: labels.orders }) })), userWishlistHref && (_jsx(Li, { children: _jsx(TextLink, { href: userWishlistHref, variant: "none", className: activeProfileLink?.href === userWishlistHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userWishlistHref ? "page" : undefined, children: labels.wishlist }) })), userSettingsHref && (_jsx(Li, { children: _jsx(TextLink, { href: userSettingsHref, variant: "none", className: activeProfileLink?.href === userSettingsHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userSettingsHref ? "page" : undefined, children: labels.settings }) }))] }) })), isAuthenticated && user?.stats && (_jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2", children: [user.stats.totalOrders != null && userOrdersHref && (_jsxs(TextLink, { href: userOrdersHref, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.totalOrders }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Orders" })] })), user.stats.reviewsCount != null && userReviewsHref && (_jsxs(TextLink, { href: userReviewsHref, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.reviewsCount }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Reviews" })] })), auctionsEnabled && user.stats.auctionsWon != null && userOrdersHref && (_jsxs(TextLink, { href: `${userOrdersHref}?orderType=auction`, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.auctionsWon }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Auctions Won" })] }))] })), isAuthenticated && isAdminOrSeller && (adminHref || resolvedStoreHref) && (_jsx(CollapsibleNavGroup, { title: labels.dashboardSectionTitle, hasActive: !!activeDashboardLink, children: _jsxs(Ul, { spacing: "2xs", children: [adminHref && role === "admin" && (_jsx(Li, { children: _jsx(TextLink, { href: adminHref, variant: "none", className: activeDashboardLink?.href === adminHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === adminHref ? "page" : undefined, children: labels.adminDashboard }) })), resolvedStoreHref && isAdminOrSeller && (_jsx(Li, { children: _jsx(TextLink, { href: resolvedStoreHref, variant: "none", className: activeDashboardLink?.href === resolvedStoreHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === resolvedStoreHref ? "page" : undefined, children: labels.storeDashboard }) }))] }) })), normalizedSections.map((section, sectionIndex) => (_jsx(CollapsibleSidebarSection, { section: section, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, activeHref: activeHref }, `sidebar-section-${sectionIndex}`))), (sidebarLocaleSlot || showThemeToggleInSidebar || (isAuthenticated && onLogout)) && (_jsxs(Stack, { border: "default", className: "border-t border-[var(--appkit-color-border-subtle)]", padding: "t-md", gap: "3", children: [sidebarLocaleSlot, showThemeToggleInSidebar && (_jsxs("button", { type: "button", onClick: toggleTheme, className: "flex w-full items-center justify-end gap-[var(--appkit-space-3)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] transition-colors hover:bg-primary-surface hover:text-primary-800 dark:hover:text-primary-200", children: [_jsx(Span, { "aria-hidden": "true", children: theme === "dark" ? "☀️" : "🌙" }), activeThemeName] })), isAuthenticated && onLogout && (_jsx("button", { type: "button", onClick: () => { onLogout(); onAfterLogout(); }, className: CLS_LOGOUT_BTN, children: labels.logout }))] }))] }));
189
+ ].join(" "), children: action.label }, `${action.href}-${action.label}`))) })), isAuthenticated && (_jsx(CollapsibleNavGroup, { title: labels.sectionTitle, hasActive: !!activeProfileLink, children: _jsxs(Ul, { spacing: "2xs", children: [_jsx(Li, { children: _jsx(TextLink, { href: profileHref, variant: "none", className: activeProfileLink?.href === profileHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === profileHref ? "page" : undefined, children: labels.profile }) }), userOrdersHref && (_jsx(Li, { children: _jsx(TextLink, { href: userOrdersHref, variant: "none", className: activeProfileLink?.href === userOrdersHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userOrdersHref ? "page" : undefined, children: labels.orders }) })), userWishlistHref && (_jsx(Li, { children: _jsx(TextLink, { href: userWishlistHref, variant: "none", className: activeProfileLink?.href === userWishlistHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userWishlistHref ? "page" : undefined, children: labels.wishlist }) })), userSettingsHref && (_jsx(Li, { children: _jsx(TextLink, { href: userSettingsHref, variant: "none", className: activeProfileLink?.href === userSettingsHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userSettingsHref ? "page" : undefined, children: labels.settings }) }))] }) })), isAuthenticated && user?.stats && (_jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2", children: [user.stats.totalOrders != null && userOrdersHref && (_jsxs(TextLink, { href: userOrdersHref, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.totalOrders }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Orders" })] })), user.stats.reviewsCount != null && userReviewsHref && (_jsxs(TextLink, { href: userReviewsHref, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.reviewsCount }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Reviews" })] })), auctionsEnabled && user.stats.auctionsWon != null && userOrdersHref && (_jsxs(TextLink, { href: `${userOrdersHref}?orderType=auction`, variant: "none", className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.auctionsWon }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Auctions Won" })] }))] })), isAuthenticated && isAdminOrSeller && (adminHref || resolvedStoreHref) && (_jsx(CollapsibleNavGroup, { title: labels.dashboardSectionTitle, hasActive: !!activeDashboardLink, children: _jsxs(Ul, { spacing: "2xs", children: [adminHref && role === "admin" && (_jsx(Li, { children: _jsx(TextLink, { href: adminHref, variant: "none", className: activeDashboardLink?.href === adminHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === adminHref ? "page" : undefined, children: labels.adminDashboard }) })), resolvedStoreHref && isAdminOrSeller && (_jsx(Li, { children: _jsx(TextLink, { href: resolvedStoreHref, variant: "none", className: activeDashboardLink?.href === resolvedStoreHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === resolvedStoreHref ? "page" : undefined, children: labels.storeDashboard }) }))] }) })), normalizedSections.map((section, sectionIndex) => (_jsx(CollapsibleSidebarSection, { section: section, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, activeHref: activeHref, renderItemAction: renderSidebarItemAction }, `sidebar-section-${sectionIndex}`))), (sidebarLocaleSlot || showThemeToggleInSidebar || (isAuthenticated && onLogout)) && (_jsxs(Stack, { border: "default", className: "border-t border-[var(--appkit-color-border-subtle)]", padding: "t-md", gap: "3", children: [sidebarLocaleSlot, showThemeToggleInSidebar && (_jsxs("button", { type: "button", onClick: toggleTheme, className: "flex w-full items-center justify-end gap-[var(--appkit-space-3)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] transition-colors hover:bg-primary-surface hover:text-primary-800 dark:hover:text-primary-200", children: [_jsx(Span, { "aria-hidden": "true", children: theme === "dark" ? "☀️" : "🌙" }), activeThemeName] })), isAuthenticated && onLogout && (_jsx("button", { type: "button", onClick: () => { onLogout(); onAfterLogout(); }, className: CLS_LOGOUT_BTN, children: labels.logout }))] }))] }));
189
190
  }
190
191
  // ─── Main component ────────────────────────────────────────────────────────────
191
- export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter = true, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, showBottomNav = true, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
192
+ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, renderSidebarItemAction, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, showFooter = true, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, userReviewsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, showBottomNav = true, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
192
193
  // QueryClientProvider used to be created here so AppLayoutShell could stand
193
194
  // alone. The real cause of the 2026-06-10/11 "No QueryClient set" prod crash
194
195
  // turned out to be a peer-dep duplicate of @tanstack/query-core under
@@ -236,7 +237,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
236
237
  // by its countdown alone got no clearance). That mirror is gone: BottomChrome
237
238
  // measures the real tier and publishes --bottom-chrome-height, and the footer
238
239
  // reserves space from that. Nothing needs to guess any more.
239
- const sidebarContent = (_jsx(SidebarContent, { sidebarItems: sidebarItems, sidebarSections: sidebarSections, sidebarPrimaryActions: sidebarPrimaryActions, user: user, activeHref: pathname ?? "", profileHref: profileHref, userOrdersHref: userOrdersHref, userWishlistHref: userWishlistHref, userSettingsHref: userSettingsHref, userReviewsHref: userReviewsHref, adminHref: adminHref, storeHref: storeHref, sellerHref: sellerHref, sidebarLocaleSlot: sidebarLocaleSlot, showThemeToggleInSidebar: showThemeToggleInSidebar, sidebarProfileLabels: sidebarProfileLabels, theme: theme, activeThemeName: activeTheme.name, toggleTheme: toggleTheme, onLogout: onLogout, onAfterLogout: () => setSidebarOpen(false) }));
240
+ const sidebarContent = (_jsx(SidebarContent, { sidebarItems: sidebarItems, sidebarSections: sidebarSections, renderSidebarItemAction: renderSidebarItemAction, sidebarPrimaryActions: sidebarPrimaryActions, user: user, activeHref: pathname ?? "", profileHref: profileHref, userOrdersHref: userOrdersHref, userWishlistHref: userWishlistHref, userSettingsHref: userSettingsHref, userReviewsHref: userReviewsHref, adminHref: adminHref, storeHref: storeHref, sellerHref: sellerHref, sidebarLocaleSlot: sidebarLocaleSlot, showThemeToggleInSidebar: showThemeToggleInSidebar, sidebarProfileLabels: sidebarProfileLabels, theme: theme, activeThemeName: activeTheme.name, toggleTheme: toggleTheme, onLogout: onLogout, onAfterLogout: () => setSidebarOpen(false) }));
240
241
  const normalizedLightBackground = {
241
242
  type: lightBackground.type,
242
243
  value: lightBackground.value,
@@ -6,6 +6,14 @@ export interface BottomNavItemProps {
6
6
  badge?: number | null;
7
7
  isActive?: boolean;
8
8
  className?: string;
9
+ /**
10
+ * The link component. Defaults to `next/link`, and that default is
11
+ * load-bearing — see the note at the render below.
12
+ *
13
+ * Override it to supply a locale-aware link (`@/i18n/navigation`), never to go
14
+ * back to a bare anchor. Same contract as `OverflowRow`'s `linkAs`.
15
+ */
16
+ linkAs?: React.ElementType;
9
17
  }
10
18
  /**
11
19
  * BottomNavItem — a single tap target inside `<BottomNavLayout>`.
@@ -25,4 +33,4 @@ export interface BottomNavItemProps {
25
33
  * </BottomNavLayout>
26
34
  * ```
27
35
  */
28
- export declare function BottomNavItem({ href, label, icon, badge, isActive, className, }: BottomNavItemProps): React.JSX.Element;
36
+ export declare function BottomNavItem({ href, label, icon, badge, isActive, className, linkAs: Link, }: BottomNavItemProps): React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import NextLink from "next/link.js";
2
3
  import { Li, Span } from "../../ui/index.js";
3
4
  /**
4
5
  * BottomNavItem — a single tap target inside `<BottomNavLayout>`.
@@ -18,10 +19,23 @@ import { Li, Span } from "../../ui/index.js";
18
19
  * </BottomNavLayout>
19
20
  * ```
20
21
  */
21
- export function BottomNavItem({ href, label, icon, badge, isActive = false, className, }) {
22
- // Use a plain anchor so this component stays framework-agnostic.
23
- // Consumer projects may swap for next/link if preferred.
24
- return (_jsx(Li, { className: "flex-1", children: _jsxs("a", { href: href, "aria-label": label, "aria-current": isActive ? "page" : undefined, className: `relative flex flex-col items-center justify-center gap-[var(--appkit-space-0-5)] h-full w-full min-h-[44px] transition-colors ${isActive
22
+ export function BottomNavItem({ href, label, icon, badge, isActive = false, className, linkAs: Link = NextLink, }) {
23
+ /*
24
+ * 🛑 `next/link`, not a bare `<a>`. This rendered a plain anchor until
25
+ * 2026-10-24, with two comments in this file contradicting each other about
26
+ * it — the docstring above said "Renders as `<a>` via Next.js `<Link>`" while
27
+ * the line below it said "use a plain anchor so this component stays
28
+ * framework-agnostic". The code did the second, so every tap on the MOBILE
29
+ * BOTTOM NAV — the primary navigation on a phone — was a full page load, and
30
+ * the only symptom is a flash.
31
+ *
32
+ * "Framework-agnostic" was never the real constraint: `OverflowRow` has
33
+ * imported `next/link` as its `linkAs` default since it shipped, and
34
+ * CLAUDE.md records that default as load-bearing for exactly this reason. The
35
+ * same escape hatch is here, so a consumer can still inject a locale-aware
36
+ * link.
37
+ */
38
+ return (_jsx(Li, { className: "flex-1", children: _jsxs(Link, { href: href, "aria-label": label, "aria-current": isActive ? "page" : undefined, className: `relative flex flex-col items-center justify-center gap-[var(--appkit-space-0-5)] h-full w-full min-h-[44px] transition-colors ${isActive
25
39
  ? "text-primary-600 dark:text-secondary-400"
26
40
  : "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)]"}${className ? ` ${className}` : ""}`, children: [_jsxs(Span, { className: "relative", children: [icon, badge != null && badge > 0 && (_jsx(Span, { layout: "flex-center", color: "inverse", "aria-label": `${badge} items`, className: "absolute -top-1.5 -right-1.5 min-w-[16px] h-4 px-[var(--appkit-space-1)] text-[10px] bg-secondary-500 leading-none", rounded: "full", weight: "bold", children: badge > 99 ? "99+" : badge }))] }), _jsx(Span, { weight: "medium", className: "text-[10px] leading-none", children: label })] }) }));
27
41
  }
@@ -25,7 +25,7 @@ export type { BackToTopProps } from "./BackToTop";
25
25
  export { LayoutClient } from "./LayoutClient";
26
26
  export type { LayoutClientProps, LayoutProvider } from "./LayoutClient";
27
27
  export { AppLayoutShell } from "./AppLayoutShell";
28
- export type { AppLayoutShellProps, AppLayoutShellSidebarLink, AppLayoutShellSidebarSection } from "./AppLayoutShell";
28
+ export type { AppLayoutShellProps, AppLayoutShellSidebarLink, AppLayoutShellSidebarSection, SidebarItemActionRenderer } from "./AppLayoutShell";
29
29
  export { BottomActionsProvider, useBottomActionsContext, } from "./BottomActionsContext";
30
30
  export type { BottomAction, BottomBulkConfig, BottomActionsState, BottomActionsDesktopMode, } from "./BottomActionsContext";
31
31
  export { useBottomActions } from "./hooks/useBottomActions";
@@ -10,7 +10,7 @@ const CLS_WARN_BODY = "mt-1 text-warning";
10
10
  import { ROUTES } from "../../../next/routing/route-map.js";
11
11
  import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
12
12
  import { formatCurrency } from "../../../utils/number.formatter.js";
13
- import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
13
+ import { toDescriptionHtml } from "../../../utils/string.formatter.js";
14
14
  import { safeDisplayName } from "../../../security/index.js";
15
15
  import { Div, Container, GatedPrice, PricesOnly, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, SpecTable, } from "../../../ui/index.js";
16
16
  import { PreOrderDetailView } from "../../products/components/PreOrderDetailView.js";
@@ -27,12 +27,6 @@ import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPane
27
27
  import { ListingBottomActions } from "../../products/components/ListingBottomActions.js";
28
28
  import { productImageUrls } from "../../products/schemas/firestore.js";
29
29
  import { PurchaseReviewAffordance } from "../../reviews/components/PurchaseReviewAffordance.js";
30
- function toDescriptionHtml(raw) {
31
- if (!raw)
32
- return "";
33
- const s = typeof raw === "string" ? raw : JSON.stringify(raw);
34
- return normalizeRichTextHtml(s);
35
- }
36
30
  export async function LiveItemDetailPageView({ slug, initialProduct, renderActions }) {
37
31
  const product = initialProduct !== undefined
38
32
  ? (initialProduct ?? undefined)
@@ -7,6 +7,7 @@ import { Button } from "../../ui/components/Button.js";
7
7
  import { Div } from "../../ui/components/Div.js";
8
8
  import { Row } from "../../ui/components/Layout.js";
9
9
  import { Span } from "../../ui/components/Typography.js";
10
+ import { joinClasses } from "../../utils/classnames.js";
10
11
  const CLS_CLOSE_HOVER = "hover:!bg-error-surface";
11
12
  const MIN_ZOOM = 1;
12
13
  const MAX_ZOOM = 4;
@@ -23,18 +24,15 @@ const DEFAULT_LABELS = {
23
24
  imageThumbnail: (n) => `Image ${n}`,
24
25
  lightboxTitle: "Image viewer",
25
26
  };
26
- function cn(...classes) {
27
- return classes.filter(Boolean).join(" ");
28
- }
29
27
  function IconBtn({ onClick, disabled, ariaLabel, ariaCurrent, className, children, }) {
30
- return (_jsx(Button, { type: "button", onClick: onClick, disabled: disabled, "aria-label": ariaLabel, "aria-current": ariaCurrent, className: cn("inline-flex items-center justify-center w-12 h-12 rounded-full bg-white/15 hover:bg-white/30 text-white border-0 active:scale-95 transition-colors disabled:opacity-30 disabled:cursor-not-allowed", className), children: children }));
28
+ return (_jsx(Button, { type: "button", onClick: onClick, disabled: disabled, "aria-label": ariaLabel, "aria-current": ariaCurrent, className: joinClasses("inline-flex items-center justify-center w-12 h-12 rounded-full bg-white/15 hover:bg-white/30 text-white border-0 active:scale-95 transition-colors disabled:opacity-30 disabled:cursor-not-allowed", className), children: children }));
31
29
  }
32
30
  // --- Controls bar sub-components ---------------------------------------------
33
31
  function LightboxControlsBar({ items, index, zoom, isFullscreen, L, goPrev, goNext, zoomIn, zoomOut, resetZoom, toggleFullscreen, onClose, }) {
34
32
  return (_jsxs(Row, { gap: "xs", className: "bg-black/85 backdrop-blur-sm shrink-0 border-t border-white/10", padding: "inline", align: "center", justify: "center", wrap: true, children: [items.length > 1 && (_jsx(IconBtn, { onClick: (e) => { e.stopPropagation(); goPrev(); }, ariaLabel: L.prevImage, children: _jsx(ChevronLeft, { className: "w-7 h-7" }) })), _jsx(IconBtn, { onClick: zoomOut, disabled: zoom <= MIN_ZOOM, ariaLabel: L.zoomOut, children: _jsx(ZoomOut, { className: "w-7 h-7" }) }), _jsxs(Span, { color: "inverse-muted", size: "xs", className: "w-14 tabular-nums", align: "center", children: [Math.round(zoom * 100), "%"] }), _jsx(IconBtn, { onClick: zoomIn, disabled: zoom >= MAX_ZOOM, ariaLabel: L.zoomIn, children: _jsx(ZoomIn, { className: "w-7 h-7" }) }), zoom > 1 && (_jsx(IconBtn, { onClick: resetZoom, ariaLabel: L.resetZoom, children: _jsx(RotateCcw, { className: "w-7 h-7" }) })), _jsx(Span, { className: "w-px h-6 bg-white/15 mx-1 shrink-0", "aria-hidden": "true" }), _jsxs(Span, { color: "inverse-muted", size: "xs", className: "w-12 tabular-nums", align: "center", children: [index + 1, " / ", items.length] }), _jsx(Span, { className: "w-px h-6 bg-white/15 mx-1 shrink-0", "aria-hidden": "true" }), _jsx(IconBtn, { onClick: toggleFullscreen, ariaLabel: isFullscreen ? L.exitFullscreen : L.enterFullscreen, children: isFullscreen ? _jsx(Minimize, { className: "w-6 h-6" }) : _jsx(Maximize, { className: "w-6 h-6" }) }), _jsx(IconBtn, { onClick: onClose, ariaLabel: L.close, className: CLS_CLOSE_HOVER, children: _jsx(X, { className: "w-7 h-7" }) }), items.length > 1 && (_jsx(IconBtn, { onClick: (e) => { e.stopPropagation(); goNext(); }, ariaLabel: L.nextImage, children: _jsx(ChevronRight, { className: "w-7 h-7" }) }))] }));
35
33
  }
36
34
  function LightboxThumbnailStrip({ items, index, onSelect, resetZoom, L, }) {
37
- return (_jsx(Row, { surface: "overlay-xl", className: "backdrop-blur-sm overflow-x-auto shrink-0", padding: "inline", justify: "center", gap: "sm", style: { paddingBottom: "calc(0.75rem + env(safe-area-inset-bottom, 0px))" }, children: items.map((item, i) => (_jsx(Button, { type: "button", onClick: () => { onSelect(i); resetZoom(); }, "aria-label": L.imageThumbnail(i + 1), "aria-current": i === index ? "true" : undefined, className: cn("relative shrink-0 w-14 h-14 p-[var(--appkit-space-0)] overflow-hidden rounded-lg border-2 transition-all active:scale-95", i === index ? "border-white opacity-100" : "border-transparent opacity-40 hover:opacity-75"), children: _jsx(MediaImage, { src: item.src, alt: item.alt, size: "thumbnail" }) }, i))) }));
35
+ return (_jsx(Row, { surface: "overlay-xl", className: "backdrop-blur-sm overflow-x-auto shrink-0", padding: "inline", justify: "center", gap: "sm", style: { paddingBottom: "calc(0.75rem + env(safe-area-inset-bottom, 0px))" }, children: items.map((item, i) => (_jsx(Button, { type: "button", onClick: () => { onSelect(i); resetZoom(); }, "aria-label": L.imageThumbnail(i + 1), "aria-current": i === index ? "true" : undefined, className: joinClasses("relative shrink-0 w-14 h-14 p-[var(--appkit-space-0)] overflow-hidden rounded-lg border-2 transition-all active:scale-95", i === index ? "border-white opacity-100" : "border-transparent opacity-40 hover:opacity-75"), children: _jsx(MediaImage, { src: item.src, alt: item.alt, size: "thumbnail" }) }, i))) }));
38
36
  }
39
37
  // --- MediaLightbox ------------------------------------------------------------
40
38
  export function MediaLightbox({ items, initialIndex = 0, isOpen, onClose, labels, }) {
@@ -24,4 +24,4 @@ export interface OrderCompletionPanelProps {
24
24
  /** Called after a successful completion, so the page can refresh the order. */
25
25
  onCompleted?: () => void;
26
26
  }
27
- export declare function OrderCompletionPanel({ orderId, endpoint, completedAt, completedBy, isDelivered, isContested, onCompleted, }: OrderCompletionPanelProps): import("react").JSX.Element | null;
27
+ export declare function OrderCompletionPanel({ endpoint, completedAt, completedBy, isDelivered, isContested, onCompleted, }: OrderCompletionPanelProps): import("react").JSX.Element | null;
@@ -80,7 +80,7 @@ const MIN_COMMENT = 10;
80
80
  function StarRow({ value, onChange, disabled, }) {
81
81
  return (_jsx(Row, { gap: "xs", role: "group", "aria-label": "Rating out of 5", children: [1, 2, 3, 4, 5].map((n) => (_jsx(Button, { variant: n <= value ? "primary" : "outline", size: "sm", disabled: disabled, "aria-label": `${n} star${n === 1 ? "" : "s"}`, "aria-pressed": n === value, onClick: () => onChange(n), children: _jsx(Icon, { name: "review", filled: n <= value }) }, n))) }));
82
82
  }
83
- export function OrderCompletionPanel({ orderId, endpoint, completedAt, completedBy, isDelivered, isContested, onCompleted, }) {
83
+ export function OrderCompletionPanel({ endpoint, completedAt, completedBy, isDelivered, isContested, onCompleted, }) {
84
84
  const [lines, setLines] = useState([]);
85
85
  const [loading, setLoading] = useState(true);
86
86
  const [loadError, setLoadError] = useState(null);
@@ -58,7 +58,7 @@ export function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, })
58
58
  * previous kind's reasons — which is the one thing that must never happen
59
59
  * here, because a mismatched reason is refused by the server.
60
60
  */
61
- const loadReasons = useCallback(async (query, page) => {
61
+ const loadReasons = useCallback(async (query) => {
62
62
  if (!kind)
63
63
  return { items: [], hasMore: false };
64
64
  /*
@@ -29,7 +29,7 @@ import { SectionForm, useSectionFormNav } from "../../shell/index.js";
29
29
  import { z } from "zod";
30
30
  import { REFUND_COPY } from "../../../_internal/shared/features/orders/refund-copy.js";
31
31
  import { resolvePublishedCopy, resolvePublishedValues, } from "../../../_internal/shared/content/published-values.js";
32
- import { RETURN_REASON_LABEL, selectableReturnReasons, } from "../../../_internal/shared/features/orders/return-reasons.js";
32
+ import { RETURN_REASON_LABEL, selectableReturnReasons } from "../../../_internal/shared/features/orders/return-reasons.js";
33
33
  import { formatCurrency } from "../../../utils/number.formatter.js";
34
34
  import { normalizeError } from "../../../errors/normalize.js";
35
35
  import { toUserMessage } from "../../../errors/error-display-map.js";