@mohasinac/appkit 4.3.0 → 4.3.2

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 (41) hide show
  1. package/dist/_internal/client/features/layout/navActive.d.ts +17 -0
  2. package/dist/_internal/client/features/layout/navActive.js +33 -0
  3. package/dist/_internal/server/features/products/service.d.ts +1 -1
  4. package/dist/_internal/server/features/products/service.js +3 -3
  5. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -1
  6. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -1
  7. package/dist/features/account/components/UserSidebar.js +21 -9
  8. package/dist/features/admin/components/AdminGuideHubView.js +15 -1
  9. package/dist/features/admin/components/AdminPaymentsGuideView.d.ts +2 -0
  10. package/dist/features/admin/components/AdminPaymentsGuideView.js +36 -0
  11. package/dist/features/admin/components/AdminSidebar.js +18 -9
  12. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +2 -0
  13. package/dist/features/admin/components/AdminWhatsAppGuideView.js +35 -0
  14. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +10 -10
  15. package/dist/features/auctions/components/CollapsibleBidHistory.js +3 -2
  16. package/dist/features/events/components/EventCard.js +2 -2
  17. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  18. package/dist/features/layout/AppLayoutShell.js +43 -9
  19. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  20. package/dist/features/products/components/MarketplaceBundleCard.js +1 -1
  21. package/dist/features/products/components/ProductGalleryClient.js +2 -2
  22. package/dist/features/products/constants/action-defs.d.ts +1 -2
  23. package/dist/features/products/constants/action-defs.js +0 -3
  24. package/dist/features/products/schemas/firestore.d.ts +4 -3
  25. package/dist/features/seller/components/SellerSidebar.js +20 -9
  26. package/dist/features/stores/components/StoreGuideHubView.js +7 -1
  27. package/dist/features/stores/components/StoreNavTabs.js +4 -3
  28. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +3 -0
  29. package/dist/features/stores/components/StoreWhatsAppGuideView.js +12 -0
  30. package/dist/features/stores/components/index.d.ts +2 -0
  31. package/dist/features/stores/components/index.js +1 -0
  32. package/dist/features/tester/seed-data/products-tester-seed-data.js +4 -3
  33. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +8 -6
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.js +6 -0
  36. package/dist/next/routing/route-map.d.ts +7 -2
  37. package/dist/next/routing/route-map.js +3 -1
  38. package/dist/seed/products-standard-seed-data.js +2 -4
  39. package/dist/styles.css +1 -1
  40. package/dist/tailwind-utilities.css +1 -1
  41. package/package.json +1 -1
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared "which nav item/group is active" algorithm for the Admin/Store/User
3
+ * dashboard sidebars and the public AppLayoutShell sidebar. Longest matching
4
+ * href wins (rather than every prefix-matching item lighting up independently)
5
+ * so a short root route like "/store" or "/user" (each sidebar's own
6
+ * "Dashboard" item) never lights up alongside a real, more specific match —
7
+ * see CLAUDE.md Recurrent Root Cause Patterns for the incident this fixes.
8
+ */
9
+ export declare function findActiveNavItem<Item extends {
10
+ href: string;
11
+ }>(items: Item[], activeHref: string): Item | undefined;
12
+ export declare function findActiveNavGroup<Item extends {
13
+ href: string;
14
+ }, Group extends {
15
+ title: string;
16
+ items: Item[];
17
+ }>(groups: Group[], activeHref: string): Group | undefined;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared "which nav item/group is active" algorithm for the Admin/Store/User
3
+ * dashboard sidebars and the public AppLayoutShell sidebar. Longest matching
4
+ * href wins (rather than every prefix-matching item lighting up independently)
5
+ * so a short root route like "/store" or "/user" (each sidebar's own
6
+ * "Dashboard" item) never lights up alongside a real, more specific match —
7
+ * see CLAUDE.md Recurrent Root Cause Patterns for the incident this fixes.
8
+ */
9
+ function isPrefixMatch(href, activeHref) {
10
+ return activeHref === href || activeHref.startsWith(`${href}/`);
11
+ }
12
+ export function findActiveNavItem(items, activeHref) {
13
+ let best;
14
+ for (const item of items) {
15
+ if (!isPrefixMatch(item.href, activeHref))
16
+ continue;
17
+ if (!best || item.href.length > best.href.length)
18
+ best = item;
19
+ }
20
+ return best;
21
+ }
22
+ export function findActiveNavGroup(groups, activeHref) {
23
+ let bestGroup;
24
+ let bestLen = -1;
25
+ for (const group of groups) {
26
+ const item = findActiveNavItem(group.items, activeHref);
27
+ if (item && item.href.length > bestLen) {
28
+ bestGroup = group;
29
+ bestLen = item.href.length;
30
+ }
31
+ }
32
+ return bestGroup;
33
+ }
@@ -17,7 +17,7 @@ export declare function assertPrizeDrawNotLocked(product: Pick<ProductDocument,
17
17
  * `locked` state, but that's UI-only — this is the enforcement). Prevents a
18
18
  * seller from swapping in a cheaper item after a buyer has already won.
19
19
  */
20
- export declare function assertPrizeDrawWonItemsImmutable(currentProduct: Pick<ProductDocument, "prizeDrawItems">, incomingItems: unknown): void;
20
+ export declare function assertPrizeDrawWonItemsImmutable(currentProduct: Pick<ProductDocument, "prizeDrawItems">, input: unknown): void;
21
21
  /** Assert the product has available stock for a purchase. */
22
22
  export declare function assertInStock(product: ProductDocument, quantity?: number): void;
23
23
  /** Derive effective price (respects currentBid for auctions, base price otherwise). */
@@ -46,14 +46,14 @@ export function assertPrizeDrawNotLocked(product, action) {
46
46
  * `locked` state, but that's UI-only — this is the enforcement). Prevents a
47
47
  * seller from swapping in a cheaper item after a buyer has already won.
48
48
  */
49
- export function assertPrizeDrawWonItemsImmutable(currentProduct, incomingItems) {
50
- if (!Array.isArray(incomingItems))
49
+ export function assertPrizeDrawWonItemsImmutable(currentProduct, input) {
50
+ if (!Array.isArray(input))
51
51
  return;
52
52
  const current = currentProduct.prizeDrawItems ?? [];
53
53
  for (const currentItem of current) {
54
54
  if (!currentItem.isWon)
55
55
  continue;
56
- const incoming = incomingItems.find((it) => it.itemNumber === currentItem.itemNumber);
56
+ const incoming = input.find((it) => it.itemNumber === currentItem.itemNumber);
57
57
  if (!incoming)
58
58
  continue;
59
59
  const changed = incoming.title !== currentItem.title ||
@@ -39,7 +39,7 @@ export async function runPrizeDrawExpiryReveal(ctx) {
39
39
  continue; // out of scope — separate mechanic
40
40
  const orders = await ctx.db
41
41
  .collection(ORDER_COLLECTION)
42
- .where("prizeDrawProductId", "==", doc.id)
42
+ .where(ORDER_FIELDS.PRIZE_DRAW_PRODUCT_ID, "==", doc.id)
43
43
  .where(ORDER_FIELDS.PAYMENT_STATUS, "==", ORDER_FIELDS.PAYMENT_STATUS_VALUES.PAID)
44
44
  .where(ORDER_FIELDS.STATUS, "in", [
45
45
  ORDER_FIELDS.STATUS_VALUES.PENDING,
@@ -37,7 +37,7 @@ export async function handlePrizeDrawSoldOut(input, ctx) {
37
37
  return;
38
38
  const orders = await ctx.db
39
39
  .collection(ORDER_COLLECTION)
40
- .where("prizeDrawProductId", "==", productId)
40
+ .where(ORDER_FIELDS.PRIZE_DRAW_PRODUCT_ID, "==", productId)
41
41
  .where(ORDER_FIELDS.PAYMENT_STATUS, "==", ORDER_FIELDS.PAYMENT_STATUS_VALUES.PAID)
42
42
  .where(ORDER_FIELDS.STATUS, "in", [
43
43
  ORDER_FIELDS.STATUS_VALUES.PENDING,
@@ -8,13 +8,11 @@ import { Button, ConfirmDeleteModal, Div, IconButton, Input, Li, Nav, Row, Span,
8
8
  import { BottomSheet } from "../../layout/BottomSheet";
9
9
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
10
10
  import { useSidebarSearch } from "../../../_internal/client/features/layout/useSidebarSearch";
11
+ import { findActiveNavGroup, findActiveNavItem } from "../../../_internal/client/features/layout/navActive";
11
12
  const __O = {
12
13
  hidden: "overflow-hidden",
13
14
  yAuto: "overflow-y-auto",
14
15
  };
15
- function isNavItemActive(item, activeHref) {
16
- return activeHref === item.href || activeHref.startsWith(item.href + "/");
17
- }
18
16
  function NavLink({ item, isActive, onClick }) {
19
17
  const [showConfirm, setShowConfirm] = useState(false);
20
18
  const handleClick = (e) => {
@@ -35,13 +33,27 @@ function NavLink({ item, isActive, onClick }) {
35
33
  }, onClose: () => setShowConfirm(false) }))] }));
36
34
  }
37
35
  function DrawerContent({ groups, items, activeHref, onItemClick, }) {
38
- // Accordion — only one group open at a time.
36
+ // Accordion — only one group open at a time. Initial pick: the group
37
+ // containing the active path, else the first group with defaultOpen.
39
38
  const [openGroup, setOpenGroup] = useState(() => {
40
39
  if (!groups)
41
40
  return null;
42
- const match = groups.find((g) => g.defaultOpen === true || g.items.some((i) => activeHref === i.href || activeHref.startsWith(i.href + "/")));
43
- return match?.title ?? null;
41
+ const active = findActiveNavGroup(groups, activeHref);
42
+ if (active)
43
+ return active.title;
44
+ return groups.find((g) => g.defaultOpen === true)?.title ?? null;
44
45
  });
46
+ // Re-sync whenever the route changes — the sidebar stays mounted across
47
+ // client-side navigation, so the lazy initializer above only fires once.
48
+ useEffect(() => {
49
+ if (!groups)
50
+ return;
51
+ const active = findActiveNavGroup(groups, activeHref);
52
+ if (active)
53
+ setOpenGroup(active.title);
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, [activeHref]);
56
+ const activeItem = findActiveNavItem(groups ? groups.flatMap((g) => g.items) : items, activeHref);
45
57
  const { query, setQuery, isSearching, filteredGroups } = useSidebarSearch(groups ?? []);
46
58
  const toggle = useCallback((title) => {
47
59
  if (isSearching)
@@ -50,16 +62,16 @@ function DrawerContent({ groups, items, activeHref, onItemClick, }) {
50
62
  }, [isSearching]);
51
63
  if (!groups || groups.length === 0) {
52
64
  return (_jsx(Nav, { "aria-label": "User navigation", padding: "y-sm", children: _jsx(Ul, { paddingX: "x-sm", spacing: "2xs", children: items.map((item) => {
53
- const isActive = activeHref === item.href || activeHref.startsWith(item.href + "/");
65
+ const isActive = activeItem?.href === item.href;
54
66
  return (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isActive, onClick: onItemClick }) }, item.href));
55
67
  }) }) }));
56
68
  }
57
69
  return (_jsxs(Nav, { "aria-label": "User navigation", padding: "y-xs", children: [_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search navigation\u2026", "aria-label": "Search navigation" }) }), isSearching && filteredGroups.length === 0 && (_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsxs(Span, { size: "xs", color: "muted", children: ["No matches for \u201C", query, "\u201D."] }) })), filteredGroups.map((group) => {
58
70
  const isOpen = isSearching || openGroup === group.title;
59
- const hasActive = group.items.some((i) => activeHref === i.href || activeHref.startsWith(i.href + "/"));
71
+ const hasActive = !!findActiveNavItem(group.items, activeHref);
60
72
  return (_jsxs(Div, { className: "mb-0.5", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => toggle(group.title), paddingX: "md", paddingY: "sm", weight: "semibold", rounded: "none", className: `w-full text-[0.6875rem] uppercase tracking-widest transition-colors ${hasActive && !isOpen
61
73
  ? "text-primary-600 dark:text-primary-400"
62
- : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isNavItemActive(item, activeHref), onClick: onItemClick }) }, item.href))) }))] }, group.title));
74
+ : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: activeItem?.href === item.href, onClick: onItemClick }) }, item.href))) }))] }, group.title));
63
75
  })] }));
64
76
  }
65
77
  function DrawerPanel({ title, onClose, children, }) {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, } from "lucide-react";
2
+ import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, MessageSquare, CreditCard, } from "lucide-react";
3
3
  import { Alert, Div, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui";
4
4
  import { ROUTES } from "../../../next/routing/route-map";
5
5
  const CLS_WARN_PANEL = "rounded-2xl border border-amber-200 bg-warning-surface dark:border-amber-800 p-[var(--appkit-space-6)]";
@@ -69,6 +69,20 @@ const GUIDE_CARDS = [
69
69
  href: String(ROUTES.ADMIN.GUIDE_TRUST),
70
70
  permission: "admin:moderation:read",
71
71
  },
72
+ {
73
+ Icon: MessageSquare,
74
+ title: "WhatsApp Integration",
75
+ description: "Connect Meta's WhatsApp Business Cloud API — Business Manager setup, phone registration, access tokens, and message templates.",
76
+ href: String(ROUTES.ADMIN.GUIDE_WHATSAPP),
77
+ permission: "admin:site:read",
78
+ },
79
+ {
80
+ Icon: CreditCard,
81
+ title: "Payments (Razorpay)",
82
+ description: "Enable and configure Razorpay — API keys, webhooks, and going live from test mode.",
83
+ href: String(ROUTES.ADMIN.GUIDE_PAYMENTS),
84
+ permission: "admin:site:read",
85
+ },
72
86
  ];
73
87
  export function AdminGuideHubView({ permissions = [], isFullAdmin = false }) {
74
88
  const hasPermission = (perm) => isFullAdmin || permissions.includes(perm);
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminPaymentsGuideView(): React.JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CreditCard, KeyRound, Webhook, Settings, Power, Rocket, Code2 } from "lucide-react";
3
+ import { Code, Div, Heading, Li, Row, Section, Span, Stack, Text, Ul } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminPaymentsGuideView() {
6
+ return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(CreditCard, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "Payments (Razorpay)" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Razorpay is disabled by default on this platform \u2014 manual UPI/bank transfer and Cash on Delivery are the default payment methods. Enable Razorpay only once you have real (or test-mode) API keys." })] }), [
7
+ {
8
+ Icon: CreditCard, title: "Create a Razorpay Account",
9
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: ["Sign up at ", _jsx(Span, { weight: "bold", children: "razorpay.com" }), "."] }), _jsxs(Li, { children: ["For development, use ", _jsx(Span, { weight: "bold", children: "Test Mode" }), " keys \u2014 no real money moves and no KYC is required to start integrating."] })] })),
10
+ },
11
+ {
12
+ Icon: KeyRound, title: "Get Your API Keys",
13
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsx(Li, { children: "Dashboard \u2192 Settings \u2192 API Keys \u2192 Generate Key." }), _jsxs(Li, { children: ["This gives you a ", _jsx(Span, { weight: "bold", children: "Key ID" }), " (public, safe to expose to the client) and a ", _jsx(Span, { weight: "bold", children: "Key Secret" }), " (private, server-only)."] })] })),
14
+ },
15
+ {
16
+ Icon: Webhook, title: "Configure a Webhook",
17
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsx(Li, { children: "Dashboard \u2192 Settings \u2192 Webhooks \u2192 Add New Webhook." }), _jsxs(Li, { children: ["Point it at ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "https://<your-domain>/api/payment/webhook" }), "."] }), _jsxs(Li, { children: ["Copy the ", _jsx(Span, { weight: "bold", children: "Webhook Secret" }), " shown after creation \u2014 it's used to verify that webhook calls actually came from Razorpay (HMAC signature check)."] })] })),
18
+ },
19
+ {
20
+ Icon: Settings, title: "Enter Credentials in the Admin UI",
21
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Site Settings \u2192 Integrations" }), ": ", _jsx(Span, { weight: "bold", children: "Client ID" }), " \u2192 your Razorpay Key ID, ", _jsx(Span, { weight: "bold", children: "Client Secret" }), " \u2192 your Razorpay Key Secret."] }), _jsx(Li, { children: "Webhook secret is configured the same way, under the same credentials block." })] })),
22
+ },
23
+ {
24
+ Icon: Power, title: "Enable the Payment Method",
25
+ content: (_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: [_jsx(Span, { weight: "bold", children: "Site Settings \u2192 Shipping tab \u2192 Payment methods \u2192 \"Razorpay (online card/UPI) enabled\"" }), ". This is off by default \u2014 manual payment stays the platform default even after Razorpay is configured, so you can test Razorpay in isolation before flipping it on for real buyers."] })),
26
+ },
27
+ {
28
+ Icon: Rocket, title: "Go Live",
29
+ content: (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Switch from Test Mode to Live Mode keys in the Razorpay dashboard once you're ready for real transactions (requires KYC/business verification on Razorpay's side). Update the same two credential fields in Site Settings with the live keys." })),
30
+ },
31
+ {
32
+ Icon: Code2, title: "How It's Used in This Codebase",
33
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "POST /api/payment/create-order" }), " computes the exact amount server-side from the buyer's live cart (never trusts a client-supplied amount) and creates a Razorpay order."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "POST /api/payment/verify" }), " verifies the payment signature, decrements stock, and places the order(s)."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "POST /api/payment/webhook" }), " is a fast, bounded fallback signal handler \u2014 signature-verified, no heavy work."] }), _jsxs(Li, { children: ["None of these routes need code changes to go live \u2014 only the credentials + the ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "razorpayEnabled" }), " toggle."] })] })),
34
+ },
35
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", children: [_jsxs(Row, { className: "border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", padding: "inlineLg", align: "center", gap: "3", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, size: "base", weight: "semibold", children: title })] }), _jsx(Div, { paddingY: "y-md-lg", padding: "x-lg", children: content })] }, title)))] }));
36
+ }
@@ -7,25 +7,34 @@ import { Button, Div, IconButton, Input, Li, Nav, Row, Span, Stack, Ul } from ".
7
7
  import { BottomSheet } from "../../layout/BottomSheet";
8
8
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
9
9
  import { useSidebarSearch } from "../../../_internal/client/features/layout/useSidebarSearch";
10
+ import { findActiveNavGroup, findActiveNavItem } from "../../../_internal/client/features/layout/navActive";
10
11
  const __O = {
11
12
  hidden: "overflow-hidden",
12
13
  yAuto: "overflow-y-auto",
13
14
  };
14
- function isNavItemActive(item, activePath) {
15
- return activePath === item.href || activePath.startsWith(item.href + "/");
16
- }
17
15
  function NavLink({ item, isActive, onClick }) {
18
16
  return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center justify-end gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
19
17
  ? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"
20
18
  : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "truncate", children: item.label })] }));
21
19
  }
22
20
  function GroupsContent({ groups, activePath, onItemClick, }) {
23
- // Accordion — only one group open at a time. Initial pick: the first group
24
- // whose defaultOpen is true, or that contains the active path.
21
+ // Accordion — only one group open at a time. Initial pick: the group
22
+ // containing the active path, else the first group with defaultOpen.
25
23
  const [openGroup, setOpenGroup] = useState(() => {
26
- const match = groups.find((g) => g.defaultOpen ?? g.items.some((i) => activePath === i.href || activePath.startsWith(i.href + "/")));
27
- return match?.title ?? null;
24
+ const active = findActiveNavGroup(groups, activePath);
25
+ if (active)
26
+ return active.title;
27
+ return groups.find((g) => g.defaultOpen)?.title ?? null;
28
28
  });
29
+ // Re-sync whenever the route changes — the sidebar stays mounted across
30
+ // client-side navigation, so the lazy initializer above only fires once.
31
+ useEffect(() => {
32
+ const active = findActiveNavGroup(groups, activePath);
33
+ if (active)
34
+ setOpenGroup(active.title);
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps
36
+ }, [activePath]);
37
+ const activeItem = findActiveNavItem(groups.flatMap((g) => g.items), activePath);
29
38
  const { query, setQuery, isSearching, filteredGroups } = useSidebarSearch(groups);
30
39
  const toggle = useCallback((title) => {
31
40
  if (isSearching)
@@ -34,10 +43,10 @@ function GroupsContent({ groups, activePath, onItemClick, }) {
34
43
  }, [isSearching]);
35
44
  return (_jsxs(Nav, { "aria-label": "Admin navigation", padding: "y-xs", children: [_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search navigation\u2026", "aria-label": "Search navigation" }) }), isSearching && filteredGroups.length === 0 && (_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsxs(Span, { size: "xs", color: "muted", children: ["No matches for \u201C", query, "\u201D."] }) })), filteredGroups.map((group) => {
36
45
  const isOpen = isSearching || openGroup === group.title;
37
- const hasActive = group.items.some((i) => activePath === i.href || activePath.startsWith(i.href + "/"));
46
+ const hasActive = !!findActiveNavItem(group.items, activePath);
38
47
  return (_jsxs(Div, { className: "mb-0.5", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => toggle(group.title), paddingX: "md", paddingY: "sm", weight: "semibold", rounded: "none", className: `w-full text-[0.6875rem] uppercase tracking-widest transition-colors ${hasActive && !isOpen
39
48
  ? "text-[var(--appkit-color-text-muted)]"
40
- : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isNavItemActive(item, activePath), onClick: onItemClick }) }, item.href))) }))] }, group.title));
49
+ : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: activeItem?.href === item.href, onClick: onItemClick }) }, item.href))) }))] }, group.title));
41
50
  })] }));
42
51
  }
43
52
  function DrawerPanel({ title, onClose, children, }) {
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminWhatsAppGuideView(): React.JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Building2, Smartphone, KeyRound, MessageSquare, Power, FlaskConical } from "lucide-react";
3
+ import { Alert, Code, Div, Heading, Li, Row, Section, Span, Stack, Table, Tbody, Td, Text, Th, Thead, Tr, Ul } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminWhatsAppGuideView() {
6
+ return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(MessageSquare, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "WhatsApp Integration" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Connecting a real Meta WhatsApp Business Cloud API account so the platform-level order-notification addon and admin purchase announcements actually deliver. Do these steps outside the codebase, then paste the resulting values into Site Settings \u2192 WhatsApp." })] }), [
7
+ {
8
+ Icon: Building2, title: "Meta Business Manager & App",
9
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: ["Create or verify a Business account at ", _jsx(Span, { weight: "bold", children: "business.facebook.com" }), ". Complete Business Verification (legal name, address, phone) \u2014 most message-template categories require a verified business before they'll be approved."] }), _jsxs(Li, { children: ["In ", _jsx(Span, { weight: "bold", children: "developers.facebook.com" }), ", create an App and add the ", _jsx(Span, { weight: "bold", children: "WhatsApp" }), " product. This gives you a test phone number for development immediately."] })] })),
10
+ },
11
+ {
12
+ Icon: Smartphone, title: "Register the Real Business Phone Number",
13
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsx(Li, { children: "WhatsApp Manager \u2192 Phone Numbers \u2192 Add phone number \u2192 verify via SMS or voice OTP." }), _jsx(Li, { children: "Complete Display Name review \u2014 Meta approves the shown business name before it goes live." })] })),
14
+ },
15
+ {
16
+ Icon: KeyRound, title: "System User & Permanent Access Token",
17
+ content: (_jsxs(_Fragment, { children: [_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: ["Business Settings \u2192 Users \u2192 System Users \u2192 create a System User with ", _jsx(Span, { weight: "bold", children: "Admin" }), " role."] }), _jsxs(Li, { children: ["Assign it the WhatsApp Business Account asset with ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "whatsapp_business_messaging" }), " + ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "whatsapp_business_management" }), " permissions, then generate a token. System User tokens don't expire the way personal User tokens do, so this is the one to use in production."] })] }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-3", size: "sm", children: ["Copy the token into ", _jsx(Span, { weight: "bold", children: "Site Settings \u2192 WhatsApp \u2192 Cloud API System User Token" }), ", and the ", _jsx(Span, { weight: "bold", children: "Phone Number ID" }), " (WhatsApp Manager \u2192 API Setup) into ", _jsx(Span, { weight: "bold", children: "Site Settings \u2192 WhatsApp \u2192 Phone Number ID" }), "."] })] })),
18
+ },
19
+ {
20
+ Icon: MessageSquare, title: "Message Templates",
21
+ content: (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)] mb-3", size: "sm", children: "Meta's Cloud API only allows free-form text messages within a 24-hour window after the customer has messaged the business first. Any business-initiated proactive notification \u2014 order placed, shipped, delivered, cancelled, refund initiated \u2014 needs a pre-approved Message Template." }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mb-3", size: "sm", children: ["WhatsApp Manager \u2192 Message Templates \u2192 create one template per notification type, category ", _jsx(Span, { weight: "bold", children: "UTILITY" }), " (transactional order updates get cheaper, faster approval than MARKETING):"] }), _jsx(Div, { overflow: "x-auto", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { children: _jsxs(Tr, { className: "border-b border-[var(--appkit-color-border)]", children: [_jsx(Th, { align: "left", paddingSide: "pr-md", className: "text-[var(--appkit-color-text)]", padding: "xs-tall", weight: "semibold", children: "Notification type" }), _jsx(Th, { align: "left", className: "text-[var(--appkit-color-text)]", padding: "xs-tall", weight: "semibold", children: "Suggested template name" })] }) }), _jsx(Tbody, { size: "sm", color: "muted", children: [
22
+ ["Order placed", "order_placed_update"],
23
+ ["Order confirmed", "order_confirmed_update"],
24
+ ["Order shipped", "order_shipped_update"],
25
+ ["Order delivered", "order_delivered_update"],
26
+ ["Order cancelled", "order_cancelled_update"],
27
+ ["Refund initiated", "refund_initiated_update"],
28
+ ].map(([type, name]) => (_jsxs(Tr, { className: "border-b border-[var(--appkit-color-border)]/50", children: [_jsx(Td, { paddingSide: "pr-md", padding: "xs-tall", children: type }), _jsx(Td, { padding: "xs-tall", children: _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: name }) })] }, type))) })] }) }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-3", size: "sm", children: ["Give each template two ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "{{1}}" }), "/", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "{{2}}" }), " body variables \u2014 the runner substitutes the notification title and message text in that order. Submit for review; approval usually takes minutes to ~24 hours. Don't go live on a template until its status shows ", _jsx(Span, { weight: "bold", children: "APPROVED" }), "."] }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-2", size: "sm", children: ["Once each template is approved, copy its exact template name (not the display label you gave it in Meta's UI) into ", _jsx(Span, { weight: "bold", children: "Site Settings \u2192 WhatsApp \u2192 [type] template name" }), ", and set the approved language code (e.g. ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "en" }), ") in ", _jsx(Span, { weight: "bold", children: "Template language code" }), "."] })] })),
29
+ },
30
+ {
31
+ Icon: Power, title: "Enable the Addon & Test",
32
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Site Settings \u2192 Fees \u2192 \"Offer the WhatsApp order-updates addon at checkout\"" }), " \u2014 turn this on once credentials + at least one template are configured. Set the fee amount (default \u20B910)."] }), _jsx(Li, { children: "Place a real test order with the addon checked. Confirm the buyer receives the templated WhatsApp message. Until real credentials + an approved template exist, the async delivery job will fail at the Meta API call with a clear credential/template error \u2014 that's expected, not a code bug." })] })),
33
+ },
34
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", children: [_jsxs(Row, { className: "border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", padding: "inlineLg", align: "center", gap: "3", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, size: "base", weight: "semibold", children: title })] }), _jsx(Div, { paddingY: "y-md-lg", padding: "x-lg", children: content })] }, title))), _jsx(Alert, { variant: "info", children: _jsxs(Row, { align: "center", gap: "sm", children: [_jsx(FlaskConical, { className: "w-4 h-4 flex-shrink-0" }), _jsx(Text, { size: "sm", children: "Store-level Meta Commerce Catalog sync (product catalog on WhatsApp) uses separate, per-store credentials \u2014 see the Seller Guide's \"WhatsApp Catalog Sync\" page (Store dashboard \u2192 Guides). It shares the same Meta Business Manager account but not the same access token. The inbound-reply webhook (buyer messages the business number within 24h) uses the free-text send path and doesn't need a template." })] }) })] }));
35
+ }
@@ -2,16 +2,16 @@ import React from "react";
2
2
  import type { BidHistoryEntry } from "../../products/components/BidHistory";
3
3
  import { type BidListResponse } from "../hooks/useBids";
4
4
  interface RawBidLike {
5
- id?: unknown;
6
- userId?: unknown;
7
- bidderId?: unknown;
8
- userName?: unknown;
9
- bidderName?: unknown;
10
- bidAmount?: unknown;
11
- amount?: unknown;
12
- bidDate?: unknown;
13
- createdAt?: unknown;
14
- bidAt?: unknown;
5
+ id?: string | number;
6
+ userId?: string;
7
+ bidderId?: string;
8
+ userName?: string;
9
+ bidderName?: string;
10
+ bidAmount?: number;
11
+ amount?: number;
12
+ bidDate?: string | Date;
13
+ createdAt?: string | Date;
14
+ bidAt?: string | Date;
15
15
  }
16
16
  /** Shared shape mapper — SSR's raw bid docs and useBids()'s client-fetched
17
17
  * pages both go through this so every page (SSR page 1, client pages 2+)
@@ -12,12 +12,13 @@ const __O = {
12
12
  * pages both go through this so every page (SSR page 1, client pages 2+)
13
13
  * renders identically. */
14
14
  export function toBidHistoryEntry(b) {
15
+ const placedAtRaw = b.bidDate ?? b.createdAt ?? b.bidAt ?? "";
15
16
  return {
16
17
  id: String(b.id ?? ""),
17
18
  bidderId: String(b.userId ?? b.bidderId ?? ""),
18
- bidderName: (b.bidderName ?? b.userName),
19
+ bidderName: b.bidderName ?? b.userName,
19
20
  amount: typeof b.bidAmount === "number" ? b.bidAmount : typeof b.amount === "number" ? b.amount : 0,
20
- placedAt: (b.bidDate ?? b.createdAt ?? b.bidAt ?? ""),
21
+ placedAt: placedAtRaw instanceof Date ? placedAtRaw.toISOString() : placedAtRaw,
21
22
  };
22
23
  }
23
24
  export function CollapsibleBidHistory({ productId, initialData, currency, pageSize = 5 }) {
@@ -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";
4
- import { Article, BaseListingCard, Button, Div, Heading, RichText, Row, Span, Stack, TextLink } from "../../../ui";
4
+ import { BaseListingCard, Button, Div, Heading, RichText, Row, Span, Stack, TextLink } from "../../../ui";
5
5
  import { MediaImage } from "../../media/MediaImage";
6
6
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
7
7
  import { EVENT_FIELDS } from "../schemas";
@@ -32,6 +32,6 @@ export function EventCard({ event, labels = {}, onParticipate, className = "", s
32
32
  const msLeft = endsAt.getTime() - now.getTime();
33
33
  const daysLeft = Math.max(0, Math.ceil(msLeft / (1000 * 60 * 60 * 24)));
34
34
  const detailHref = String(ROUTES.PUBLIC.EVENT_DETAIL(event.slug ?? event.id));
35
- return (_jsxs(Article, { border: "default", rounded: "xl", shadow: "hover-md", className: `group relative flex h-full flex-col overflow-hidden bg-[var(--appkit-color-surface)] ${isSelected ? "border-primary outline outline-2 outline-primary" : " "} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(event.id, !isSelected); }, label: isSelected ? "Deselect event" : "Select event", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsx(Link, { href: detailHref, className: "block flex-shrink-0", children: event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" }) })) : (_jsx(Row, { surface: "muted", className: "aspect-video", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-40", size: "5xl", "aria-hidden": "true", children: TYPE_ICONS[event.type] }) })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { className: "mb-2", align: "start", justify: "between", gap: "sm", children: [_jsx(Span, { size: "lg", "aria-hidden": "true", children: TYPE_ICONS[event.type] }), _jsx(EventStatusBadge, { status: event.status })] }), _jsx(Link, { href: detailHref, className: "block", children: _jsx(Heading, { level: 3, className: "text-[var(--appkit-color-text)] leading-snug mb-1 group-hover:text-primary transition-colors", size: "base", weight: "semibold", children: safeTitle }) }), _jsx(RichText, { html: normalizeRichTextHtml(event.description ?? ""), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mb-3 line-clamp-3 text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)]" }), _jsxs(Row, { color: "muted", textSize: "xs", className: "mb-3 mt-auto", align: "center", justify: "between", children: [event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE &&
35
+ return (_jsxs(Stack, { as: "article", border: "default", rounded: "xl", shadow: "hover-md", gap: "none", className: `group relative h-full overflow-hidden bg-[var(--appkit-color-surface)] ${isSelected ? "border-primary outline outline-2 outline-primary" : " "} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(event.id, !isSelected); }, label: isSelected ? "Deselect event" : "Select event", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity" })), _jsx(Link, { href: detailHref, className: "block flex-shrink-0", children: event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { className: `relative aspect-video ${__O.hidden}`, children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" }) })) : (_jsx(Row, { surface: "muted", className: "aspect-video", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-40", size: "5xl", "aria-hidden": "true", children: TYPE_ICONS[event.type] }) })) }), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { className: "mb-2", align: "start", justify: "between", gap: "sm", children: [_jsx(Span, { size: "lg", "aria-hidden": "true", children: TYPE_ICONS[event.type] }), _jsx(EventStatusBadge, { status: event.status })] }), _jsx(Link, { href: detailHref, className: "block", children: _jsx(Heading, { level: 3, className: "text-[var(--appkit-color-text)] leading-snug mb-1 group-hover:text-primary transition-colors", size: "base", weight: "semibold", children: safeTitle }) }), _jsx(RichText, { html: normalizeRichTextHtml(event.description ?? ""), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mb-3 line-clamp-3 text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)]" }), _jsxs(Row, { color: "muted", textSize: "xs", className: "mb-3 mt-auto", align: "center", justify: "between", children: [event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE &&
36
36
  daysLeft > 0 && _jsxs(Span, { children: ["\u23F1 ", daysLeft, "d remaining"] }), _jsxs(Span, { children: ["\uD83D\uDC65 ", event.stats.totalEntries, " ", labels.entries ?? "entries"] })] }), event.status === EVENT_FIELDS.STATUS_VALUES.ACTIVE && onParticipate ? (_jsx(Button, { rounded: "lg", type: "button", onClick: () => onParticipate(event), className: "w-full bg-primary py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-medium text-white transition-colors hover:bg-primary-600", children: labels.participate ?? "Participate" })) : (_jsxs(TextLink, { rounded: "lg", paddingX: "sm", paddingY: "xs", href: detailHref, layout: "inline-flex", align: "center", justify: "center", gap: "xs", className: "w-full border border-[var(--appkit-color-border)] transition-colors hover:bg-[var(--appkit-color-bg)]", color: "primary", size: "sm", weight: "medium", children: [labels.viewDetails ?? "View details", " \u2192"] }))] })] }));
37
37
  }
@@ -41,5 +41,5 @@ export function SectionCarousel({ title, description, headingVariant = "editoria
41
41
  headingClass,
42
42
  ]
43
43
  .filter(Boolean)
44
- .join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: loop, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, className: `inline-flex items-[center] gap-[0.375rem] transition-colors bg-primary text-white hover:bg-primary-600 ${useLightText ? "shadow-lg" : ""}`, size: "sm", weight: "semibold", children: viewMoreLabel }) }))] })] }));
44
+ .join(" "), children: title }), headingVariant === "editorial" && (_jsxs(Row, { align: "center", justify: "center", gap: "sm", textSize: "xs", color: "faint", className: "mt-1 select-none", "aria-hidden": "true", children: [_jsx(Span, { className: "h-px w-6 bg-current" }), _jsx(Span, { size: "xs", children: "\u2736" }), _jsx(Span, { className: "h-px w-6 bg-current" })] })), description && (_jsx(Text, { className: `${descVariant} mt-2`, size: "base", children: description }))] }), isLoading ? (_jsx(CarouselSkeleton, { count: skeletonCount })) : (_jsx(HorizontalScroller, { items: items, renderItem: renderItem, perView: perView, gap: gap, autoScroll: autoScroll, autoScrollInterval: autoScrollInterval, loop: loop, keyExtractor: keyExtractor, rows: rows, minItemWidth: minItemWidth, showArrows: true, snapToItems: true, showFadeEdges: true, showScrollbar: false, pauseOnHover: true })), viewMoreHref && !isLoading && (_jsx(Row, { className: "mt-6", justify: "start", children: _jsx(TextLink, { rounded: "lg", paddingX: "xl", paddingY: "sm", href: viewMoreHref, shadow: useLightText ? "lg" : "none", className: "inline-flex items-[center] gap-[0.375rem] transition-colors bg-primary text-white hover:bg-primary-600", size: "sm", weight: "semibold", children: viewMoreLabel }) }))] })] }));
45
45
  }
@@ -28,22 +28,43 @@ import { TitleBar } from "./TitleBar";
28
28
  import { BackToTop } from "./BackToTop";
29
29
  import { useDashboardNav } from "./DashboardNavContext";
30
30
  import { usePathname } from "next/navigation";
31
+ import { findActiveNavItem } from "../../_internal/client/features/layout/navActive";
31
32
  import { OVERLAY_FALLBACK_COLOR, SEED_DARK_BG as DEFAULT_DARK_BG, SEED_LIGHT_BG as DEFAULT_LIGHT_BG } from "./background-seed-defaults";
32
33
  const CLS_STAT_BOX = "flex flex-col items-center gap-[var(--appkit-space-1)] p-[var(--appkit-space-2)] bg-[var(--appkit-color-surface)] rounded-lg text-center";
33
34
  const CLS_STAT_LABEL = "text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)]";
34
35
  const CLS_LOGOUT_BTN = "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-error transition-colors hover:bg-error-surface hover:text-error dark:text-error dark:hover:bg-error-surface dark:hover:text-error";
35
36
  /** Collapsible accordion section for the public sidebar. */
36
- function CollapsibleNavGroup({ title, children, }) {
37
- const [open, setOpen] = useState(false);
37
+ function CollapsibleNavGroup({ title, hasActive = false, children, }) {
38
+ const [open, setOpen] = useState(hasActive);
39
+ // Re-sync whenever the route changes — the sidebar stays mounted across
40
+ // client-side navigation, so the lazy initial state above only fires once.
41
+ useEffect(() => {
42
+ if (hasActive)
43
+ setOpen(true);
44
+ }, [hasActive]);
38
45
  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: title }), _jsx("svg", { className: `w-3.5 h-3.5 transition-transform duration-200 ${open ? "rotate-180" : ""}`, 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(Stack, { gap: "none", className: "", children: children })] }));
39
46
  }
40
- function CollapsibleSidebarSection({ section, navItemClass, }) {
41
- const [open, setOpen] = useState(section.defaultOpen ?? false);
47
+ function CollapsibleSidebarSection({ section, navItemClass, navItemActiveClass, activeHref, }) {
48
+ const activeItem = findActiveNavItem(section.items, activeHref);
49
+ const hasActive = !!activeItem;
50
+ const [open, setOpen] = useState((section.defaultOpen ?? false) || hasActive);
51
+ // Re-sync whenever the route changes — the sidebar stays mounted across
52
+ // client-side navigation, so the lazy initial state above only fires once.
53
+ useEffect(() => {
54
+ if (hasActive)
55
+ setOpen(true);
56
+ }, [hasActive]);
42
57
  const hasTitle = !!section.title;
43
58
  if (!hasTitle) {
44
- return (_jsx(Ul, { spacing: "2xs", children: section.items.map((item) => (_jsx(Li, { children: _jsxs(TextLink, { href: item.href, variant: "none", className: navItemClass, 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}`))) }));
59
+ return (_jsx(Ul, { spacing: "2xs", children: section.items.map((item) => {
60
+ const isActive = activeItem?.href === item.href;
61
+ 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}`));
62
+ }) }));
45
63
  }
46
- 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" : ""}`, 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(Ul, { spacing: "2xs", children: section.items.map((item) => (_jsx(Li, { children: _jsxs(TextLink, { href: item.href, variant: "none", className: navItemClass, 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}`))) }))] }));
64
+ 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" : ""}`, 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(Ul, { spacing: "2xs", children: section.items.map((item) => {
65
+ const isActive = activeItem?.href === item.href;
66
+ 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}`));
67
+ }) }))] }));
47
68
  }
48
69
  /** Sidebar header when a user is logged in — avatar + display name + close button. */
49
70
  function SidebarUserHeader({ user, onClose, }) {
@@ -61,7 +82,7 @@ function SidebarUserHeader({ user, onClose, }) {
61
82
  function SidebarGuestHeader({ sidebarTitle, onClose, }) {
62
83
  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)] text-[var(--appkit-color-text-muted)] hover:bg-zinc-200 hover:text-zinc-900 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-zinc-100 transition-all hover:rotate-90", children: _jsx("svg", { className: "w-4 h-4", 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" }) }) })] }));
63
84
  }
64
- function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions, user, profileHref, userOrdersHref, userWishlistHref, userSettingsHref, adminHref, storeHref, sellerHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, theme, activeThemeName, toggleTheme, onLogout, onAfterLogout, }) {
85
+ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions, user, activeHref, profileHref, userOrdersHref, userWishlistHref, userSettingsHref, adminHref, storeHref, sellerHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, theme, activeThemeName, toggleTheme, onLogout, onAfterLogout, }) {
65
86
  const hasLegacyItems = sidebarItems.length > 0;
66
87
  const hasSections = !!(sidebarSections && sidebarSections.length > 0);
67
88
  const isAuthenticated = !!user;
@@ -80,6 +101,19 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
80
101
  logout: sidebarProfileLabels?.logout ?? "Logout",
81
102
  };
82
103
  const navItemClass = "flex w-full items-center justify-end gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-zinc-700 transition-colors hover:bg-primary-50 hover:text-primary-800 text-[var(--appkit-color-text-muted)] dark:hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-300";
104
+ const navItemActiveClass = "flex w-full items-center justify-end gap-[var(--appkit-space-2)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-medium bg-primary-50 text-primary-800 dark:bg-primary-900/25 dark:text-primary-300";
105
+ const profileLinks = [
106
+ { href: profileHref },
107
+ ...(userOrdersHref ? [{ href: userOrdersHref }] : []),
108
+ ...(userWishlistHref ? [{ href: userWishlistHref }] : []),
109
+ ...(userSettingsHref ? [{ href: userSettingsHref }] : []),
110
+ ];
111
+ const activeProfileLink = findActiveNavItem(profileLinks, activeHref);
112
+ const dashboardLinks = [
113
+ ...(adminHref ? [{ href: adminHref }] : []),
114
+ ...(resolvedStoreHref ? [{ href: resolvedStoreHref }] : []),
115
+ ];
116
+ const activeDashboardLink = findActiveNavItem(dashboardLinks, activeHref);
83
117
  const normalizedSections = hasSections
84
118
  ? sidebarSections
85
119
  : hasLegacyItems
@@ -90,7 +124,7 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
90
124
  action.variant === "outline"
91
125
  ? "border border-zinc-300 text-zinc-700 hover:bg-zinc-50 border-[var(--appkit-color-border)] text-[var(--appkit-color-text)] hover:bg-[var(--appkit-color-surface-elevated)]"
92
126
  : "bg-primary text-white hover:bg-primary-600 dark:bg-primary dark:hover:bg-primary-600 btn-glow",
93
- ].join(" "), children: action.label }, `${action.href}-${action.label}`))) })), isAuthenticated && (_jsx(CollapsibleNavGroup, { title: labels.sectionTitle, children: _jsxs(Ul, { spacing: "2xs", children: [_jsx(Li, { children: _jsx(TextLink, { href: profileHref, variant: "none", className: navItemClass, children: labels.profile }) }), userOrdersHref && (_jsx(Li, { children: _jsx(TextLink, { href: userOrdersHref, variant: "none", className: navItemClass, children: labels.orders }) })), userWishlistHref && (_jsx(Li, { children: _jsx(TextLink, { href: userWishlistHref, variant: "none", className: navItemClass, children: labels.wishlist }) })), userSettingsHref && (_jsx(Li, { children: _jsx(TextLink, { href: userSettingsHref, variant: "none", className: navItemClass, children: labels.settings }) }))] }) })), isAuthenticated && user?.stats && (_jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2", children: [user.stats.totalOrders != null && (_jsxs(Div, { 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 && (_jsxs(Div, { 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" })] })), user.stats.auctionsWon != null && (_jsxs(Div, { 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" })] })), user.stats.itemsSold != null && (_jsxs(Div, { className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.itemsSold }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Items Sold" })] }))] })), isAuthenticated && isAdminOrSeller && (adminHref || resolvedStoreHref) && (_jsx(CollapsibleNavGroup, { title: labels.dashboardSectionTitle, children: _jsxs(Ul, { spacing: "2xs", children: [adminHref && role === "admin" && (_jsx(Li, { children: _jsx(TextLink, { href: adminHref, variant: "none", className: navItemClass, children: labels.adminDashboard }) })), resolvedStoreHref && isAdminOrSeller && (_jsx(Li, { children: _jsx(TextLink, { href: resolvedStoreHref, variant: "none", className: navItemClass, children: labels.storeDashboard }) }))] }) })), normalizedSections.map((section, sectionIndex) => (_jsx(CollapsibleSidebarSection, { section: section, navItemClass: navItemClass }, `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-50 hover:text-primary-800", 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 }))] }))] }));
127
+ ].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 && (_jsxs(Div, { 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 && (_jsxs(Div, { 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" })] })), user.stats.auctionsWon != null && (_jsxs(Div, { 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" })] })), user.stats.itemsSold != null && (_jsxs(Div, { className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.itemsSold }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Items Sold" })] }))] })), 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-50 hover:text-primary-800", 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 }))] }))] }));
94
128
  }
95
129
  // ─── Main component ────────────────────────────────────────────────────────────
96
130
  export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, onTourStart, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
@@ -139,7 +173,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
139
173
  const hasBottomActions = bottomActionsState.actions.length > 0 ||
140
174
  !!(bottomActionsState.bulk && bottomActionsState.bulk.selectedCount > 0) ||
141
175
  !!bottomActionsState.infoLabel;
142
- const sidebarContent = (_jsx(SidebarContent, { sidebarItems: sidebarItems, sidebarSections: sidebarSections, sidebarPrimaryActions: sidebarPrimaryActions, user: user, profileHref: profileHref, userOrdersHref: userOrdersHref, userWishlistHref: userWishlistHref, userSettingsHref: userSettingsHref, adminHref: adminHref, storeHref: storeHref, sellerHref: sellerHref, sidebarLocaleSlot: sidebarLocaleSlot, showThemeToggleInSidebar: showThemeToggleInSidebar, sidebarProfileLabels: sidebarProfileLabels, theme: theme, activeThemeName: activeTheme.name, toggleTheme: toggleTheme, onLogout: onLogout, onAfterLogout: () => setSidebarOpen(false) }));
176
+ const sidebarContent = (_jsx(SidebarContent, { sidebarItems: sidebarItems, sidebarSections: sidebarSections, sidebarPrimaryActions: sidebarPrimaryActions, user: user, activeHref: pathname ?? "", profileHref: profileHref, userOrdersHref: userOrdersHref, userWishlistHref: userWishlistHref, userSettingsHref: userSettingsHref, adminHref: adminHref, storeHref: storeHref, sellerHref: sellerHref, sidebarLocaleSlot: sidebarLocaleSlot, showThemeToggleInSidebar: showThemeToggleInSidebar, sidebarProfileLabels: sidebarProfileLabels, theme: theme, activeThemeName: activeTheme.name, toggleTheme: toggleTheme, onLogout: onLogout, onAfterLogout: () => setSidebarOpen(false) }));
143
177
  const normalizedLightBackground = {
144
178
  type: lightBackground.type,
145
179
  value: lightBackground.value,